嵌套表达式<func <>>的Linq树-存在(选择* ...)

时间:2019-02-07 19:43:53

标签: c# sql linq

atm im在我的应用程序中编写通用过滤器模块。 我在创建适当的Expression>时遇到问题。我一般的SQL查询看起来像这样:

SELECT distinct ROW_NUMBER
FROM dbo.VIEW_ITEM item
WHERE CODE ='MyName'
  AND EXISTS (SELECT *
              FROM dbo.VIEW_ITEM
              WHERE ROW_NUMBER = item.ROW_NUMBER
                AND CODE='MyName' 
                AND (COL_NUMBER=1 AND DISPLAY='UserName')) 
  AND EXISTS (SELECT * 
              FROM VIEW_ITEM
              WHERE ROW_NUMBER = item.ROW_NUMBER
              AND CODE='MyName'
              AND (COL_NUMBER=3 and DISPLAY='2261'))
ORDER BY ROW_NUMBER

(我认为)这是获取我需要的所有记录的最佳方法。我不能使用联接选项,因为我正在检查与主要查询相同的表 AND EXISTS

所以我的linq看起来像:

dboMVI.Where(mvi => mvi.Code == "MyCode")
    .Where(mvi => dboMVI.Where(innerMvi => innerMvi.RowNumber == mvi.RowNumber && innerMvi.Code == "MyCode" && innerMvi.ColNumber == 1 && innerMvi.Display == "UserName").Any())
    .Where(mvi => dboMVI.Where(innerMvi => innerMvi.RowNumber == mvi.RowNumber && innerMvi.Code == "MyCode" && innerMvi.ColNumber == 3 && innerMvi.Display == "2261").Any())
    .Select(mvi => mvi.RowNumber)
    .OrderBy(rn => rn)
    .Distinct();

那应该返回我所有通过筛选的行数。 我设法创建了Expression,但我确信有更好的方法可以使其更通用,并将其放入我无法将其传递给DbContext的过滤模块中。

Expression<Func<ViewItem, bool>> filter =mvi =>
 dboMVI.Where(innerMvi => innerMvi.RowNumber == mvi.RowNumber 
&& innerMvi.Code == "MyCode" && innerMvi.ColNumber == 3 && 
innerMvi.Display == "2261").Any()

对于第二个过滤器:

Expression<Func<ViewItem, bool>> filter =mvi =>
     dboMVI.Where(innerMvi => innerMvi.RowNumber == mvi.RowNumber 
    && innerMvi.Code == "MyCode" && innerMvi.ColNumber == 1 && 
    innerMvi.Display == "UserName").Any()

我的问题是如何为该LINQ查询创建通用表达式树?

我在任何地方都找不到创建这种树的示例。 我在join语句中找到了传递参数的示例。 但是在我的情况下,im从当前行的主查询号传递过来,而在子查询中,它会检查此特定行是否满足任何条件


编辑:经过一些评论后,我注意到我在将查询值从真实值重写为演示时确实犯了错误。抱歉,现在希望已定:) 一般来说,它的工作解决方案只是在寻找更好的方法。


EDIT2: 我的问题在这里:

im试图从LINQ SQL查询中生成可以被EF Core使用的查询。 在我的SQL中,使用AND EXISTS的方式是在正文中引用与主查询中相同的表。在子查询中,IM还使用主查询中的ROW_NUMBER。 我怎么了我不知道如何创建表达式func(负责我的子查询),因为我不知道如何将我正在检查的当前ROW_NUMBER传递给它。我知道如何使表达式树成为简单的例子。但是我的身体里有常数或弦。但是在这种情况下,我的const每次都会更改为不同的值,因此无法进行硬编码。

答案

我设法解决了这个问题。首先必须简化linq查询。

dboMVI.Where(mvi => mvi.Code == "MyCode")
.Where(mvi => dboMVI.Any(innerMvi => innerMvi.RowNumber == mvi.RowNumber && innerMvi.Code == "MyCode" && innerMvi.ColNumber == 1 && innerMvi.Display == "UserName"))
.Where(mvi => dboMVI.Any(innerMvi => innerMvi.RowNumber == mvi.RowNumber && innerMvi.Code == "MyCode" && innerMvi.ColNumber == 3 && innerMvi.Display == "2261"))
.Select(mvi => mvi.RowNumber)
.OrderBy(rn => rn)
.Distinct()

不必为任何表达式中的元素创建表达式树:

IQueryable<MaterializedViewItem> MyDtoList = Enumerable.Empty<MaterializedViewItem>().AsQueryable();
        var insideProperty = Expression.Parameter(typeof(MaterializedViewItem), "mviAny");
        var baseProperty = Expression.Parameter(typeof(MaterializedViewItem), "mviBaseAny");

        MemberExpression condition0Code = Expression.Property(baseProperty, "MvCode");
        ConstantExpression condition0CodeValue = Expression.Constant("ARAPP");
        var condition0 = Expression.Equal(condition0Code, condition0CodeValue);
        var predicateFirstElement = Expression.Lambda<Func<T, bool>>(condition0, baseProperty);

        MemberExpression conditionACode = Expression.Property(insideProperty, "MvCode");
        ConstantExpression conditionACodeValue = Expression.Constant("MyCode");
        var conditionA = Expression.Equal(conditionACode, conditionACodeValue);

        MemberExpression conditionACol = Expression.Property(insideProperty, "ColNumber");
        ConstantExpression conditionAColValue = Expression.Constant((byte)1);
        var conditionB = Expression.Equal(conditionACol, conditionAColValue);

        MemberExpression conditionDisplay = Expression.Property(insideProperty, "ValueDisplay");
        ConstantExpression conditionDisplayValue = Expression.Constant("UserName");
        var conditionC = Expression.Equal(conditionDisplay, conditionDisplayValue);

        MemberExpression conditionRow = Expression.Property(insideProperty, "RowNumber");
        var newValueToCompare = Expression.PropertyOrField(baseProperty, "RowNumber");
        ConstantExpression conditionRowValue = Expression.Constant(0);
        var conditionD = Expression.Equal(conditionRow, newValueToCompare);

        var condition = Expression.AndAlso(conditionA, conditionB);
        var condition2 = Expression.AndAlso(conditionC, conditionD);
        var condition3 = Expression.AndAlso(condition, condition2);

        var predicate = Expression.Lambda<Func<MaterializedViewItem, bool>>(condition3, insideProperty);

        var callCondtions = BuildAny<MaterializedViewItem>(predicate, MyDtoList.Expression);
        var myPredicate = Expression.Lambda<Func<T, bool>>(callCondtions, baseProperty);

        MemberExpression conditionCol2 = Expression.Property(insideProperty, "ColNumber");
        ConstantExpression conditionCol2Value = Expression.Constant((byte)3);
        var conditionE = Expression.Equal(conditionCol2, conditionCol2Value);

        MemberExpression conditionColDisplay2 = Expression.Property(insideProperty, "ValueDisplay");
        ConstantExpression conditionColDisplay2Value = Expression.Constant("2261");
        var conditionF = Expression.Equal(conditionColDisplay2, conditionColDisplay2Value);
        var condition22 = Expression.AndAlso(conditionA, conditionD);
        var condition23 = Expression.AndAlso(conditionE, conditionF);
        var condition2Final = Expression.AndAlso(condition22, condition23);
        var predicate2 = Expression.Lambda<Func<T, bool>>(condition2Final, insideProperty);

        var callCondtions2 = BuildAny<T>(predicate2, MyDtoList.Expression);

需要为我最终构建带有所有参数的Any功能

public static Expression BuildAny<T>(Expression<Func<T, bool>> predicate, Expression expression)
    {
        var overload = typeof(Queryable).GetMethods().FirstOrDefault(method => method.Name == "Any" && method.GetParameters().Count() == 2);
        var specificMethod = overload.MakeGenericMethod(typeof(T));

        var call = Expression.Call(
            specificMethod,
            expression,
            predicate);

        return call;
    }

要记住的重要一点是,我们正在基于临时对象构建IQueryable。后来它不得不用真正的数据库表代替。可以通过以下方式完成:

IQueryable<T> queryList = this.DbSet;
        var filtersForDbSet = ExpressionTreeConstantReplacer.CopyAndReplace<DbSet<T>, T>(condition, typeof(EnumerableQuery<T>), this.DbSet);
        class ExpressionTreeConstantReplacer
            {
                internal static Expression<Func<T2, bool>> CopyAndReplace<T, T2>(Expression<Func<T2, bool>> expression, Type originalType, T replacementConstant)
                {
                    var modifier = new ExpressionTreeConstantReplacer<T>(originalType, replacementConstant);
                    var newLambda = modifier.Visit(expression) as LambdaExpression;

                    return Expression.Lambda<Func<T2, bool>>(newLambda.Body, newLambda.Parameters.FirstOrDefault());
                }
        }

        class ExpressionTreeConstantReplacer<T> : ExpressionVisitor
    {
        Type originalType;
        T replacementConstant;

        internal ExpressionTreeConstantReplacer(Type originalType, T replacementConstant)
        {
            this.originalType = originalType;
            this.replacementConstant = replacementConstant;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            return c.Type == originalType ? Expression.Constant(replacementConstant) : c;
        }
    }

如果有人在表达式树中有类似的问题。查询的构建方式与普通查询相同。要将一些虚词从主表达式传递到内部表达式,您只需要证明您将它们进行比较即可:

MemberExpression conditionRow = Expression.Property(insideProperty, "RowNumber");
        var newValueToCompare = Expression.PropertyOrField(baseProperty, "RowNumber");
        var conditionD = Expression.Equal(conditionRow, newValueToCompare

1 个答案:

答案 0 :(得分:1)

正如我在问题评论中提到的那样,您的查询可以简化。

[初始注释]

根据评论中的讨论,我认为您的查询仍然可以改进。

[版本1] -外观

SELECT DISTINCT ROW_NUMBER
FROM dbo.VIEW_ITEM item
WHERE CODE ='MyName'AND COL_NUMBER IN(1, 3) AND DISPLAY IN ('UserName', '2261')
ORDER BY ROW_NUMBER

根据您的SQL查询,Linq版本可能类似于:

int[] nums = {1, 3};
string[] disp = {"UserName", "2261"}; 

var result = dboMVI
    .Where(mvi=> mvi.Code == 'MyName' && 
        nums.Any(n=> n.Contains(mvi.ColNumber)) &&
        disp.Any(d=> d.Contains(mvi.Display))
    .OrderBy(x=>x.RowNumber)
    .Select(x=>.RowNumber);

如果上述查询不符合您的条件,则尝试将条件与括号结合起来

[版本2] -外观

SELECT DISTINCT ROW_NUMBER
FROM dbo.VIEW_ITEM item
WHERE (CODE ='MyName'AND COL_NUMBER IN(1, 3)) AND 
    (CODE ='MyName' AND DISPLAY IN ('UserName', '2261'))
ORDER BY ROW_NUMBER

等效于Linq:

var result = dboMVI
    .Where(mvi=> (mvi.Code == 'MyName' && 
        nums.Any(n=> n.Contains(mvi.ColNumber))) &&
        (mvi.Code == 'MyName' && 
           disp.Any(d=> d.Contains(mvi.Display)))
    .OrderBy(x=>x.RowNumber)
    .Select(x=>.RowNumber);

如您所见,返回数据必须同时满足两个条件。

[EDIT#2]

关于表达式...我认为它应该像这样:

Expression<Func<ViewItem, string, int, string, bool>> filter = (mvi, code, colNo, disp) => 
        dboMVI.Any(innerMvi =>
            innerMvi.RowNumber == mvi.RowNumber  &&
            innerMvi.Code==code && 
            innerMvi.ColNumber == colNo && 
            innerMvi.Display == disp);

[最后的音符]

注意:我无法访问您的数据,并且我无法保证100%的上述查询将满足您的条件。