【问题标题】:LINQ Expression Tree: Call Any method against a DbSetLINQ 表达式树:针对 DbSet 调用任何方法
【发布时间】:2019-09-27 15:49:19
【问题描述】:

我正在尝试将以下 LINQ 查询转换为表达式树

var queryActivity = uow.PromoActivityMeasuresRepository.ToQueryable();
var queryMeasure = uow.PromoMeasuresRepository.ToQueryable();
queryActivity.Where(pa => pa.WorkSpaceId == 28 && 
                          !queryMeasure.Any(pm => pm.WorkSpaceId == pa.WorkSpaceId && 
                                                  pm.Organization == pa.Organization && 
                                                  pm.MeasureCode == pa.MeasureCode));

我已经能够获得与简单约束相关的表达式,但现在我被困在如何创建与 queryMeasure.Any 相关的表达式

有代码

//I've translated pm.WorkSpaceId == pa.WorkSpaceId
var childParameter = Expression.Parameter(childEntityType, "pa");
var parentParameter = Expression.Parameter(validatingEntityType, "pm");
var parentWorkSpace = Expression.Property(parentParameter, "WorkSpaceId");
var childWorkSpace = Expression.Property(childParameter, "WorkSpaceId");
var parentChildWorkSpaceConstraint = Expression.Equal(parentWorkSpace, childWorkSpace); 

还有代码

// I've translated pm => pm.WorkSpaceId == pa.WorkSpaceId && 
//                       pm.Organization == pa.Organization && 
//                       pm.MeasureCode == pa.MeasureCode)

Expression logicalAnd = null;
foreach (var field in FKChildEntity.Value.Fields)
{
    var parentLeft = Expression.Property(parentParameter, field);
    var childRight = Expression.Property(childParameter, field);
    var parentChildConstraint = Expression.Equal(parentLeft, childRight);
    if (logicalAnd == null)
    {
        logicalAnd = Expression.AndAlso(parentChildWorkSpaceConstraint, parentChildConstraint);
        continue;
    }
    //parentConstraints.Add(parentChildConstraint);
    logicalAnd = Expression.AndAlso(logicalAnd, parentChildConstraint);
}

问题在这里...

我无法理解如何在 NegateExpression 中调用 queryMeasure.Any 来使用

var parentDelegateType = typeof(Func<,>).MakeGenericType(validatingEntityType, typeof(bool));
var parentPredicate = Expression.Lambda(parentDelegateType, logicalAnd, parentParameter);
var promoMeasuresParameter = Expression.Constant(dataRepository, dataRepository.GetType());

var AnyMethod = Expression.MakeMemberAccess(promoMeasuresParameter, promoMeasuresParameter.GetType().GetMember("Any").FirstOrDefault());

var parentQueryable = dataRepository.GetType().InvokeMember("ToQueryable", BindingFlags.InvokeMethod, null, dataRepository, null);
var collectionParameter = Expression.Parameter(parentQueryable.GetType(), "parentCollection");

var AnyMethodExpression = Expression.Call(parentQueryable.GetType(), "Any", null , parentPredicate);
var negateExpression = Expression.Negate(AnyMethodExpression);

我可以问你们一些关于如何进行的提示吗?

谢谢大家

【问题讨论】:

  • 我强烈推荐使用 LINQPad 并使用 Dump 方法。您可以在变量中创建LambdaExpression&lt;&gt;,将其转储出来,看看它是如何形成的。
  • 你想用AnyMethod完成什么?这不是您为方法获得MethodInfo 的方式(扩展方法是类中的静态方法)。您需要为Queryable.Any 致电GetMethod
  • 你为什么打电话给ToQueryable()uow.PromoActivityMeasuresRepository 不是已经是 IQueryable 了吗?
  • Any()Enumerable/Queryable 类上的静态扩展方法,而不是parentQueryable 类型上的方法。

标签: c# .net linq lambda expression-trees


【解决方案1】:

您要调用的Any 方法是通用的(Where 也是通用的):

public static bool Any<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);

您需要使用构造的MethodInfo,将第二个可查询的元素类型作为类型参数。

通过名称查找反射对象很棘手,尤其是对于重载和/或泛型。这就是为什么我总是使用我的GetMethod 辅助函数(见下文)并让编译器查找/重载解析/类型推断所需的MethodInfo

解决了这个问题后,编写一个可查询的扩展方法WhereNotAnyOtherThatEqualsOnCorrespondingFields 来做你想做的事情会更直接(随意重命名它:))。

public static class MyExtensions
{
    /// <summary>
    /// Helper method to get a <see cref="MethodInfo"/>.
    /// </summary>
    public static MethodInfo GetMethod<TSource, TResult>(this Expression<Func<TSource, TResult>> lambda)
        => ((MethodCallExpression)(lambda.Body)).Method;

    // some well known methods
    private static readonly MethodInfo _miAny1 = GetMethod((IQueryable<int> q) => q.Any()).GetGenericMethodDefinition();
    private static readonly MethodInfo _miAny2 = GetMethod((IQueryable<int> q) => q.Any(x => false)).GetGenericMethodDefinition();
    private static readonly MethodInfo _miWhere = GetMethod((IQueryable<int> q) => q.Where(x => false)).GetGenericMethodDefinition();

    public static IQueryable<TSource> WhereNotAnyOtherThatEqualsOnCorrespondingFields<TSource, TOther>(
        this IQueryable<TSource> source,
        Expression<Func<IQueryable<TOther>>> other,
        params string[] fieldNames)
    {
        var pThis = Expression.Parameter(typeof(TSource), "pa");
        var pOther = Expression.Parameter(typeof(TOther), "pm");

        Expression anyPredicateBody = null;
        foreach (var field in fieldNames)
        {
            var equal = Expression.Equal(Expression.PropertyOrField(pThis, field), Expression.PropertyOrField(pOther, field)); 
            anyPredicateBody = anyPredicateBody == null ? equal : Expression.AndAlso(anyPredicateBody, equal);
        }

        Expression any;
        if (anyPredicateBody == null)
            any = Expression.Call(_miAny1.MakeGenericMethod(typeof(TOther)), other.Body);
        else
        {
            var anyPredicate = Expression.Quote(Expression.Lambda<Func<TOther, bool>>(anyPredicateBody, pOther));
            any = Expression.Call(_miAny2.MakeGenericMethod(typeof(TOther)), other.Body, anyPredicate);
        }

        var whereNotAnyPredicate = Expression.Quote(Expression.Lambda<Func<TSource, bool>>(Expression.Not(any), pThis));
        var whereNotAny = Expression.Call(_miWhere.MakeGenericMethod(typeof(TSource)), source.Expression, whereNotAnyPredicate);

        return source.Provider.CreateQuery<TSource>(whereNotAny);
    }
}

这是测试(因为懒惰而使用匿名类型):

var dbMock = new
{
    PromoActivityMeasures = new[]
    {
        new { Id = 1, WorkSpaceId = 27, Organization = "Org1", MeasureCode = 4711 },
        new { Id = 2, WorkSpaceId = 28, Organization = "Org1", MeasureCode = 4711 },
        new { Id = 3, WorkSpaceId = 28, Organization = "Org2", MeasureCode = 4711 },
    }.AsQueryable(),

    PromoMeasures = new[]
    {
        new { WorkSpaceId = 28, Organization = "Org1", MeasureCode = 4711 },
    }.AsQueryable(),
};

var result = dbMock.PromoActivityMeasures
    .Where(pa => pa.WorkSpaceId == 28)
    .WhereNotAnyOtherThatEqualsOnCorrespondingFields(() => dbMock.PromoMeasures, "WorkSpaceId", "Organization", "MeasureCode")
    .Select(pa => pa.Id)
    .Single();
// 3

【讨论】:

    猜你喜欢
    • 2020-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-02
    • 2016-10-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多