where子句的通用表达式-"LINQ to Entities不支持LINQ表达式节点类型'Invoke'. [英] Generic expression for where clause - "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities."

查看:97
本文介绍了where子句的通用表达式-"LINQ to Entities不支持LINQ表达式节点类型'Invoke'.的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试编写一种真正通用的方法,使用Contains方法生成SQL IN语句,以批量加载EF实体.如果传入整个表达式,则可以正常工作,但是当我尝试动态构建表达式时,出现"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了解更通用的版本?

正如您所描述的,问题在于第一个示例中的entityKey函数是不透明的,因为它是Func类型而不是Expression类型.但是,您可以通过实现Compose()方法来组合两个表达式来获得所需的行为.我在以下问题中发布了用于实现撰写的代码:使用Expression< Func< T, X>> Linq中包含扩展名.

实现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();
} 

I am trying to write a really generic way to load EF entities in batches, using the Contains method to generate a SQL IN statement. I've got it working if I pass the entire expression in, but when I try to build the expression dynamically, I am getting a "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities." So I know this means that EF thinks I'm calling an arbitrary method and it can't translate it into SQL, but I can't figure out how to get it to understand the underlying expression.

So If I do something like this (just showing the relevant snippets):

Function declaration:

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;
} 

Call function:

var entities = BatchEntity.Load<ItemType>(from eItemType in dal.Context.InstanceContainer.ItemTypes
select eItemType
, itemTypeData
, (ek => ek.ItemTypeId)
);

I get "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities."

But if I change it to be this:

Function declaration:

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;
} 

Call function:

var entities = BatchEntity.Load<ItemType>(from eItemType in dal.Context.InstanceContainer.ItemTypes
select eItemType
, itemTypeData
, (entityArray => (ek => entityArray.Contains(ek.ItemTypeId)))
);

It works fine. Is there any way I can make EF understand the more generic version?

解决方案

The problem, as you describe, is that the entityKey function in the first example is opaque since it is of type Func rather than Expression. However, you can get the behavior you want by implementing a Compose() method to combine two expressions. I posted the code to implement compose in this question: use Expression<Func<T,X>> in Linq contains extension.

With Compose() implemented, your function can be implemented as below:

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();
} 

这篇关于where子句的通用表达式-"LINQ to Entities不支持LINQ表达式节点类型'Invoke'.的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆