我正在尝试编写一种非常通用的方法来批量加载EF实体,使用Contains方法生成SQL IN语句。如果我传递整个表达式,我已经完成了它,但是当我尝试动态构建表达式时,我得到一个“LINQ to Entities中不支持LINQ表达式节点类型'Invoke'。”所以我知道这意味着EF认为我正在调用一个任意方法而且它无法将其转换为SQL,但我无法弄清楚如何让它理解底层表达式。
所以如果我做这样的事情(只显示相关的片段):
功能声明:
public static List<T> Load<T>(IQueryable<T> entityQuery, int[] entityIds, Func<T, int> entityKey, int batchSize = 500, Func<T, bool> postFilter = null) where T : EntityObject
{
var retList = new List<T>();
// Append a where clause to the query passed in, that will use a Contains expression, which generates a SQL IN statement. So our SQL looks something like
// WHERE [ItemTypeId] IN (1921,1920,1922)
// See http://rogeralsing.com/2009/05/21/entity-framework-4-where-entity-id-in-array/ for details
Func<int[], Expression<Func<T, bool>>> containsExpression = (entityArray => (expr => entityArray.Contains(entityKey(expr))));
// Build a new query with the current batch of IDs to retrieve and add it to the list we are returning
newQuery = entityQuery.Where<T>(containsExpression(entityIds));
retList.AddRange(newQuery.ToList());
return retList;
}
通话功能:
var entities = BatchEntity.Load<ItemType>(from eItemType in dal.Context.InstanceContainer.ItemTypes
select eItemType
, itemTypeData
, (ek => ek.ItemTypeId)
);
我得到“LINQ to Entities中不支持LINQ表达式节点类型'Invoke'。”
但是,如果我将其更改为:
功能声明:
public static List<T> Load<T>(IQueryable<T> entityQuery, int[] entityIds, Func<int[], Expression<Func<T, bool>>> containsExpression, int batchSize = 500, Func<T, bool> postFilter = null) where T : EntityObject
{
var retList = new List<T>();
// Build a new query with the current batch of IDs to retrieve and add it to the list we are returning
newQuery = entityQuery.Where<T>(containsExpression(entityIds));
retList.AddRange(newQuery.ToList());
return retList;
}
通话功能:
var entities = BatchEntity.Load<ItemType>(from eItemType in dal.Context.InstanceContainer.ItemTypes
select eItemType
, itemTypeData
, (entityArray => (ek => entityArray.Contains(ek.ItemTypeId)))
);
工作正常。有什么方法可以让EF了解更通用的版本吗?
答案 0 :(得分:5)
正如您所描述的那样,问题是第一个示例中的entityKey函数是不透明的,因为它的类型为Func而不是Expression。但是,您可以通过实现Compose()方法来组合两个表达式来获得所需的行为。我在这个问题中发布了实现撰写的代码:use Expression<Func<T,X>> in Linq contains extension。
实现Compose()后,您的功能可以实现如下:
public static List<T> Load<T>(this IQueryable<T> entityQuery,
int[] entityIds,
// note that this is an expression now
Expression<Func<T, int>> entityKey,
int batchSize = 500,
Expression<Func<T, bool>> postFilter = null)
where T : EntityObject
{
Expression<Func<int, bool>> containsExpression = id => entityIds.Contains(id);
Expression<Func<T, bool>> whereInEntityIdsExpression = containsExpression.Compose(entityKey);
IQueryable<T> filteredById = entityQuery.Where(whereInEntityIdsExpression);
// if your post filter is compilable to SQL, you might as well do the filtering
// in the database
if (postFilter != null) { filteredById = filteredById.Where(postFilter); }
// finally, pull into memory
return filteredById.ToList();
}