繁体   English   中英

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

[英]LINQ to Entities does not recognize the method (on a related entity)

我试图让一些代码更具可读性,并且我不太了解如何构造扩展方法和/或表达式来实现它。 我们目前有许多实体上有RecordStatusTypeId (从IRecordStatus的接口IRecordStatus

public interface IRecordStatus
{
    int RecordStatusTypeId { get; set; }
    RecordStatusType RecordStatusType { get; set; }
}

这里的目标是使用.ActiveRecords()等扩展方法替换.Where(RecordStatusTypeId != (int)RecordStatusTypes.Deleted)类的.ActiveRecords()

我可以使用以下Extension方法完成此操作:

public static IQueryable<T> ActiveRecords<T>(this DbSet<T> entitySet)
    where T : class, IRecordStatus
{
    return entitySet.Where(e => e.RecordStatusTypeId != (int)RecordStatusTypes.Deleted);
}

*我有DbSet<T>IQueryable<T>ICollection<T>IEnumerable<T>扩展方法

这适用于像MyDbContext.Entities.Where(e => 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子句中的相关实体?

看起来您可能已经错误地完成了转换。 应该:

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

转换为:

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

您将获得异常,因为Queryable.Where需要一个可以转换为SQL的表达式,并且ActiveRecords无法转换为SQL。

您需要做的是更新表达式以将对ActiveRecords的调用扩展为对.Where(e => e.RecordStatusTypeId != (int)RecordStatusTypes.Deleted的调用.Where(e => 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());

尝试使查询IQueryable的主要部分,然后标记在where子句上...

就像是:

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM