通过点链接 lambda 表达式
Chaining lambda expressions by dot
我有两个表达式,我想将它们链接起来,以便生成的表达式包含两个输入表达式。
Expression<Func<IQueryable<Material>, object>> expression1 = x => x.Include(m => m.MaterialGroup);
Expression<Func<IQueryable<Material>, object>> expression2 = x => x.Include(m => m.MaterialSomething);
var expression3 = expression1.Update(expression2.Body, expression2.Parameters);
现在 expression3
只包含 x => x.Include(m => m.MaterialSomething)
所以它覆盖了第二个表达式。我希望它是 x => x.Include(m => m.MaterialGroup).Include(m => m.MaterialSomething)
.
我打算实现的是以编程方式连接多个包含表达式,以便能够在 EF Core 中构建更高效的预加载系统。
编辑:
这不是 ANDing、ORing 等的问题,因为我希望这些表达式被链接(如点链接),而不是逻辑连接。
丹尼尔
因为Include
是你的表达式的扩展方法
x => x.Include(m => m.MaterialGroup);
实际上是
x => QueryableExtensions.Include(x, m => m.MaterialGroup);
因此,要链接您的表达式,您需要将 Include
的第一个参数替换为对另一个 Include
的调用
x => QueryableExtensions.Include(
QueryableExtensions.Include(x, m => m.MaterialSomething),
m => m.MaterialGroup);
下一个代码将执行此链接
public static Expression<Func<IQueryable<T>, object>> Chain<T>(
params Expression<Func<IQueryable<T>, object>>[] expressions)
{
if (expressions.Length == 0)
throw new ArgumentException("Nothing to chain");
if (expressions.Length == 1)
return expressions[0];
Expression body = expressions[0].Body;
var parameter = expressions[0].Parameters[0];
foreach (var expression in expressions.Skip(1))
{
var methodCall = (MethodCallExpression)expression.Body;
var lambda = (UnaryExpression)methodCall.Arguments[1];
body = Expression.Call(typeof(QueryableExtensions),
"Include",
new []{ typeof(T), ((LambdaExpression)lambda.Operand).Body.Type},
body, lambda
);
}
return Expression.Lambda<Func<IQueryable<T>, object>>(body, parameter);
}
用法:
var expression = Chain(expression1, expression2 /*, expression3 .... */);
可以在线测试here
请注意,为简洁起见,此代码跳过了表达式验证。
我想添加另一种方式来存档链接 lambda 表达式:
在容易访问的地方添加跟随静态方法
public static Expression<Func<T, bool>> ConcatLambdaExpression<T>(Expression<Func<T, bool>> firstExpression, Expression<Func<T, bool>> secondExpression)
{
var invokedThird = Expression.Invoke(secondExpression, firstExpression.Parameters.Cast<Expression>());
var finalExpression = Expression.Lambda<Func<T, bool>>(Expression.AndAlso(firstExpression.Body, invokedThird), firstExpression.Parameters);
return finalExpression;
}
然后就可以这样使用了:
public PersonDTO GetAll()
{
Expression<Func<Person, bool>> expression = x => x != null;
expression = x => x.Name == "John";
Expression<Func<Person, bool>> pred = x => x.LastName == "Doe" || x.LastName == "Wick";
//result of expression would be:
////expression = x.Name == "John" && (x => x.LastName == "Doe" || x.LastName == "Wick")
expression = Utilities.ConcatLambdaExpression(expression, pred);
var result = Context.PersonEntity.Where(expression);
//your code mapping results to PersonDTO
///resultMap...
return resultMap;
}
我有两个表达式,我想将它们链接起来,以便生成的表达式包含两个输入表达式。
Expression<Func<IQueryable<Material>, object>> expression1 = x => x.Include(m => m.MaterialGroup);
Expression<Func<IQueryable<Material>, object>> expression2 = x => x.Include(m => m.MaterialSomething);
var expression3 = expression1.Update(expression2.Body, expression2.Parameters);
现在 expression3
只包含 x => x.Include(m => m.MaterialSomething)
所以它覆盖了第二个表达式。我希望它是 x => x.Include(m => m.MaterialGroup).Include(m => m.MaterialSomething)
.
我打算实现的是以编程方式连接多个包含表达式,以便能够在 EF Core 中构建更高效的预加载系统。
编辑: 这不是 ANDing、ORing 等的问题,因为我希望这些表达式被链接(如点链接),而不是逻辑连接。
丹尼尔
因为Include
是你的表达式的扩展方法
x => x.Include(m => m.MaterialGroup);
实际上是
x => QueryableExtensions.Include(x, m => m.MaterialGroup);
因此,要链接您的表达式,您需要将 Include
的第一个参数替换为对另一个 Include
x => QueryableExtensions.Include(
QueryableExtensions.Include(x, m => m.MaterialSomething),
m => m.MaterialGroup);
下一个代码将执行此链接
public static Expression<Func<IQueryable<T>, object>> Chain<T>(
params Expression<Func<IQueryable<T>, object>>[] expressions)
{
if (expressions.Length == 0)
throw new ArgumentException("Nothing to chain");
if (expressions.Length == 1)
return expressions[0];
Expression body = expressions[0].Body;
var parameter = expressions[0].Parameters[0];
foreach (var expression in expressions.Skip(1))
{
var methodCall = (MethodCallExpression)expression.Body;
var lambda = (UnaryExpression)methodCall.Arguments[1];
body = Expression.Call(typeof(QueryableExtensions),
"Include",
new []{ typeof(T), ((LambdaExpression)lambda.Operand).Body.Type},
body, lambda
);
}
return Expression.Lambda<Func<IQueryable<T>, object>>(body, parameter);
}
用法:
var expression = Chain(expression1, expression2 /*, expression3 .... */);
可以在线测试here
请注意,为简洁起见,此代码跳过了表达式验证。
我想添加另一种方式来存档链接 lambda 表达式:
在容易访问的地方添加跟随静态方法
public static Expression<Func<T, bool>> ConcatLambdaExpression<T>(Expression<Func<T, bool>> firstExpression, Expression<Func<T, bool>> secondExpression)
{
var invokedThird = Expression.Invoke(secondExpression, firstExpression.Parameters.Cast<Expression>());
var finalExpression = Expression.Lambda<Func<T, bool>>(Expression.AndAlso(firstExpression.Body, invokedThird), firstExpression.Parameters);
return finalExpression;
}
然后就可以这样使用了:
public PersonDTO GetAll()
{
Expression<Func<Person, bool>> expression = x => x != null;
expression = x => x.Name == "John";
Expression<Func<Person, bool>> pred = x => x.LastName == "Doe" || x.LastName == "Wick";
//result of expression would be:
////expression = x.Name == "John" && (x => x.LastName == "Doe" || x.LastName == "Wick")
expression = Utilities.ConcatLambdaExpression(expression, pred);
var result = Context.PersonEntity.Where(expression);
//your code mapping results to PersonDTO
///resultMap...
return resultMap;
}