【发布时间】: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<Func<ViewItem, bool>> filter,但无法访问dboMVI?