LINQ to Entities 无法识别该方法(在相关实体上)

Posted

技术标签:

【中文标题】LINQ to Entities 无法识别该方法(在相关实体上)【英文标题】:LINQ to Entities does not recognize the method (on a related entity) 【发布时间】:2016-09-23 05:56:23 【问题描述】:

我正在尝试使一些代码更具可读性,但并没有完全掌握如何构建扩展方法和/或表达式来做到这一点。我们目前有许多实体上都有RecordStatusTypeId(从IRecordStatus 的接口实现)

public interface IRecordStatus

    int RecordStatusTypeId  get; set; 
    RecordStatusType RecordStatusType  get; set; 

这里的目标是将.Where(RecordStatusTypeId != (int)RecordStatusTypes.Deleted) 之类的语句替换为.ActiveRecords() 之类的扩展方法

我可以使用以下扩展方法来完成此操作:

public static IQueryable<T> ActiveRecords<T>(this DbSet<T> entitySet)
    where T : class, IRecordStatus

    return entitySet.Where(e => e.RecordStatusTypeId != (int)RecordStatusTypes.Deleted);

*我有DbSet&lt;T&gt;IQueryable&lt;T&gt;ICollection&lt;T&gt;IEnumerable&lt;T&gt;的扩展方法

这对于MyDbContext.Entities.Where(e =&gt; e.RecordStatusTypeId != (int)RecordStatusTypes.Deleted) 之类的语句非常有效,但如果我尝试替换以下内容,则会收到错误“LINQ to Entities 无法识别该方法”:

MyDbContext.Entities.Where(e => e.RelatedEntity.Where(re => re.RecordStatusTypeId != (int)RecordStatusTypes.Deleted));

我想做的事:

MyDbContext.Entities.Where(e => e.RelatedEntity.ActiveRecords().Any());

如何更改我的扩展方法(或添加表达式),以便过滤 DbSet 以及 linq 子句内的相关实体上的活动记录?

【问题讨论】:

Entity Framework Plus 可以做到这一点:github.com/zzzprojects/EntityFramework-Plus @CyrilIselin,谢谢,我去看看。 您的方法无法转换为 T-SQL,Linq to Entities 无法识别。将AsEnumerable 添加到您的Entities 并重试:MyDbContext.Entities.AsEnumerable().Where... AsEnumerable 会将所有实体从数据库存储中拉到内存中。对于任何大型商店,您都不想这样做。 阅读此***.com/questions/18813671/… 【参考方案1】:

看来您的转换可能有误。 应该:

MyDbContext.Entities.Where(e => e.RelatedEntity.Where(re => re.RecordStatusTypeId != (int)RecordStatusTypes.Deleted));

转换成这个:

MyDbContext.Entities.Where(e => e.RelatedEntity.ActiveRecords().Any());

【讨论】:

是的,你是对的。不幸的是,这只是我试图简化帖子的一个错误。我已经解决了这个问题。【参考方案2】:

您收到异常是因为 Queryable.Where 需要一个可以翻译成 SQL 的表达式,而 ActiveRecords 不能翻译成 SQL。

您需要做的是更新表达式以将对ActiveRecords 的调用扩展为对.Where(e =&gt; e.RecordStatusTypeId != (int)RecordStatusTypes.Deleted 的调用。

我将为可行的解决方案提供草稿。它对您提供的示例非常具体。您可能应该努力使其具有通用性。

以下表达式访问者基本上会将对 ActiveRecords 的调用更改为对 Where 的调用,并使用适当的表达式作为参数:

public class MyVisitor : ExpressionVisitor

    protected override Expression VisitMethodCall(MethodCallExpression m)
    
        if (m.Method.Name == "ActiveRecords")
        
            var entityType = m.Method.GetGenericArguments()[0];
            var whereMethod = genericWhereMethod.MakeGenericMethod(entityType);

            var param = Expression.Parameter(entityType);
            var expressionToPassToWhere =
                Expression.NotEqual(
                    Expression.Property(param, "RecordStatusTypeId"),
                    Expression.Constant((int)RecordStatusTypes.Deleted));

            Expression newExpression =
                Expression.Call(
                    whereMethod,
                    m.Arguments[0],
                    Expression.Lambda(
                        typeof(Func<,>).MakeGenericType(entityType, typeof(bool)),
                        expressionToPassToWhere,
                        param));

            return newExpression;
        

        return base.VisitMethodCall(m);
    

    //This is reference to the open version of `Enumerable.Where`
    private static MethodInfo genericWhereMethod;

    static MyVisitor()
    
        genericWhereMethod = typeof (Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static)
            .Where(x => x.Name == "Where" && x.GetGenericArguments().Length == 1)
            .Select(x => new Method = x, Parameters = x.GetParameters())
            .Where(x => x.Parameters.Length == 2 &&
                        x.Parameters[0].ParameterType.IsGenericType &&
                        x.Parameters[0].ParameterType.GetGenericTypeDefinition() == typeof (IEnumerable<>) &&
                        x.Parameters[1].ParameterType.IsGenericType &&
                        x.Parameters[1].ParameterType.GetGenericTypeDefinition() == typeof (Func<,>))
            .Select(x => x.Method)
            .Single();
    

然后您可以创建一个特殊的WhereSpecial 方法来访问表达式,然后再将其传递给Queryable 的真正Where 方法:

public static class ExtentionMethods

    public static IQueryable<T> WhereSpecial<T>(this IQueryable<T> queryable, Expression<Func<T,bool>> expression )
    
        MyVisitor visitor = new MyVisitor();

        var newBody = visitor.Visit(expression.Body);

        expression = expression.Update(newBody, expression.Parameters);

        return queryable.Where(expression);
    

然后你可以像这样使用它:

var result = MyDbContext.Entities.WhereSpecial(e => e.RelatedEntity.ActiveRecords().Any());

【讨论】:

这一切似乎违背了首先使用辅助 ActiveRecords 方法的全部目的:) @Evk,这可能是真的 :) 也许一个更通用的访问者接受方法名称和相应的 lambda 表达式的字典会让所有这些都值得。 @YacoubMassad,我还没有时间尝试探索这样的事情是否值得努力,但感谢您的回答。当我有时间重访时,我会回来更新。【参考方案3】:

尝试将查询的主要部分设为 IQueryable,然后在 where 子句上添加标签...

类似:

IQueryable temp = from x in MyDbContext.Entities
                  select x;

temp = temp.Where(e => e.RelatedEntity.ActiveRecords().Any());

或评估主要部分,然后执行内存中的位置。

【讨论】:

感谢您的建议,但我已将此接口应用于一组不同的实体对象,因此如果不先过滤 ActiveRecords,我无法撤回查询的内容(至少在所有情况)。

以上是关于LINQ to Entities 无法识别该方法(在相关实体上)的主要内容,如果未能解决你的问题,请参考以下文章