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 无法识别该方法(在相关实体上)的主要内容,如果未能解决你的问题,请参考以下文章

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

LINQ to Entities 无法识别方法“System.String ToString()”方法,并且该方法无法转换为存储表达式

我如何修复“LINQ to Entities无法识别方法”错误

LINQ to Entities 无法识别方法“Int32 Parse(System.String)”方法,并且该方法无法转换为存储表达式

LINQ to Entities 无法识别方法“System.Object GetValue(...)”

LINQ to Entities 无法识别“System.String get_Item(Int32)”方法,并且该方法无法转换为存储表达式 [重复]