【问题标题】:Linq tree for nested Expression<Func<>> - exists(select*...)嵌套表达式的 Linq 树<Func<>> - exists(select*...)
【发布时间】:2019-02-07 19:43:53
【问题描述】:

atm 我在我的应用程序中编写通用过滤器模块。 我在创建正确的表达式时遇到问题>。我一般的 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 语句中传递参数的示例。 但在我的情况下,我从当前行的主查询编号传递,并在子查询中检查此特定行是否满足任何条件


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


编辑2: 我的问题是什么:

我正在尝试从 EF Core 可以使用的 LINQ SQL 查询生成。 在我的 SQL 中使用 AND EXISTS 其中在正文中我指的是与主查询中相同的表。使用主查询中的 ROW_NUMBER 在子查询中还有什么。 我的问题是什么?我不知道如何创建表达式 func(负责我的子查询),因为我不知道如何将我当前正在检查的 ROW_NUMBER 传递给它。我知道如何为简单的例子制作表达式树。但是我的身体里有常数 fe int 或 string 。但在这种情况下,我的 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()

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

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);

需要额外的函数来为我构建包含所有参数的 final 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

【问题讨论】:

  • 你为什么要连续打三个电话Where?并在过滤器中嵌套更多Where
  • 您使用的是mvi.Code = "MyCode" 而不是mvi.Code == "MyCode"
  • 为什么在 LINQ 中使用 ValueDisplay 而在 SQL 中使用 DISPLAY
  • 您的查询可以这样简化: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
  • 您想概括查询的哪一部分,这样做有什么问题?从外部创建和传递Expression&lt;Func&lt;ViewItem, bool&gt;&gt; filter,但无法访问dboMVI

标签: c# sql linq


【解决方案1】:

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

[初始注释]

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

[版本 #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);

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

[编辑#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% 上述查询符合您的条件。

【讨论】:

  • 感谢您的快速评论,您想知道它是打字错误的那部分。它现在应该固定在原始线程中。都是innerMvi.Display。明天我会检查这个调整后的 sql 查询:)
  • 我检查了这个查询,但它并没有解决我的问题,首先它选择了所有匹配这些条件的所有行,而我只需要得到匹配这两个条件的行。其次,它不允许我在过滤器类型之间轻松操作。我的意思是在某些情况下它的提交是相等的,而在其他情况下它必须包含或不相等。在存在查询中,完成此任务要容易得多。但无论如何,谢谢你的回答在其他类型的查询中可能会非常有用。
  • 我在答案中添加了额外的信息。请检查一下。
  • 它仍然没有返回正确的数据,我不确定但支持它尝试匹配从 Col_Number 1 到“UserName”的任何内容,并且对于 Col_Number 3 也是如此,而我只需要从 Col_Number 1 获取 UserName 并且仅Col_Number 3 中的“2261”。Col_Number 3 中可以有值“UserName”,我不想要这条记录。
猜你喜欢
  • 2017-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多