Where 子句中带有“.Any”的动态 Linq (C# / .Net Core / EF Core)
Dynamic Linq with ".Any" in Where clausule (C# / .Net Core / EF Core)
我正在尝试根据存储在另一个数据库集中的属性进行一些文章过滤。我正在使用一些 类:
public class Article
{
public string ArticleCode { get; set; }
public string var1 { get; set; }
public string var2 { get; set; }
public string var3 { get; set; }
public virtual List<ArticleProperty> Properties { get; set; }
}
public class ArticleProperty
{
public string ArticleCode { get; set; }
public string PropertyCode { get; set; }
public string var4 { get; set; }
public string var5 { get; set; }
public string var6 { get; set; }
}
public class ArticleSummary
{
public string ArticleCode { get; set; }
public string var7 { get; set; }
public string var8 { get; set; }
}
public class WebDbContext : DbContext
{
public virtual DbSet<Article> Article{ get; set; }
public virtual DbSet<ArticleProperty> ArticleProperty{ get; set; }
/* some more code */
}
当我创建这样的查询时,它会执行我想执行的操作:
IQueryable<ArticleSummary> Articles = _DbContext.Article
.Where(a => a.var1 == SomeLocalVariable1)
.Where(a => a.var2 == SomeLocalVariable2 || a.var2 == SomeLocalVariable3)
.Where(a =>
a.Properties.Any(ap =>
(
(ap.ArticleCode == a.ArticleCode && ap.var4 == "A" && ap.var5 == "X") ||
(ap.ArticleCode == a.ArticleCode && ap.var4 == "A" && ap.var5 == "Y")
)
)
&&
a.Eigenschappen.Any(ap =>
(
(ap.ArticleCode == a.ArticleCode && ap.var4 == "B" && ap.var5 == "Z")
)
)
)
.OrderByDescending(a => a.var6)
.Select(a => new ArticleSummary
{
ArticleCode = a.ArticleCode ,
var7 = a.var1
var8 = a.var3
});
但现在我想动态地创建最后一个 Where 语句,就像这样(dataFilter 是一个具有一些过滤器属性的 Dictionary< string, Dictionary< string, bool>>):
var query ="";
bool firstA = true;
foreach (KeyValuePair<string, Dictionary<string, bool>> filter in dataFilter)
{
if (firstA)
query += "a => ";
else
query += " && ";
query += "a.Properties.Any(ap =>"
+ "(";
bool firstB = true;
foreach (KeyValuePair<string,bool> filterDetail in filter.Value)
{
if (!firstB)
query += " || ";
query += "(ap.ArticleCode == a.ArticleCode && ap.var4 == \""+filter.Key+"\" && ap.var5 == \""+filterDetail.Key+"\")";
firstB = false;
}
query += ")"
+ ")";
firstA = false;
}
IQueryable<ArticleSummary> Articles = _DbContext.Article
.Where(a => a.var1 == SomeLocalVariable1)
.Where(a => a.var2 == SomeLocalVariable2 || a.var2 == SomeLocalVariable3)
.Where(query)
.OrderByDescending(a => a.var6)
.Select(a => new ArticleSummary
{
ArticleCode = a.ArticleCode ,
var7 = a.var1
var8 = a.var3
});
'query' 符合预期,但 Where 不起作用,错误:
System.Linq.Dynamic.Core.Exceptions.ParseException: 'No applicable aggregate method 'Any' exists'
只有在有2条'Any'-语句时才会出现这种情况(除以&&,和我做的时候一样'hard-coded')。我不知道为什么...
不使用字符串,直接使用查询:
IQueryable<ArticleSummary> Articles = _DbContext.Article
.Where(a => a.var1 == SomeLocalVariable1)
.Where(a => a.var2 == SomeLocalVariable2 || a.var2 == SomeLocalVariable3)
.Where(query);
foreach(...) {
Articles = Articles.Where(...);
}
Articles = Articles.OrderByDescending(a => a.var6)
.Select(a => new ArticleSummary
{
ArticleCode = a.ArticleCode ,
var7 = a.var1
var8 = a.var3
});
动态 LINQ 有自己的 expression language。 Lambda 表达式不以 a =>
或 ap =>
开头,有一个称为当前范围的东西可以简化一些查询,但通常在访问外部级别参数时会出现问题。所有可查询扩展都定义了一个名为 it
的范围参数,可以省略。
简而言之,动态 LINQ 不太适合嵌套 lambda 表达式访问外部 lambda 参数的复杂查询。
结合编译时和运行时表达式可以相对容易地实现目标。这个想法很简单。
首先,您创建一个编译时 lambda 表达式,其中包含用作占位符的附加参数。然后使用以下简单表达式访问者将占位符替换为实际表达式:
public static class ExpressionExtensions
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
=> node == Source ? Target : base.VisitParameter(node);
}
}
与 string.Format
非常相似,但带有表达式。然后你可以使用 Expression.AndAlso
和 Expression.OrElse
来生成 &&
和 ||
部分。
话虽如此,您的情况如下所示:
Expression<Func<Article, string, string, bool>> detailExpr = (a, var4, var5) =>
a.Properties.Any(ap => ap.ArticleCode == a.ArticleCode && ap.var4 == var4 && ap.var5 == var5);
var p_a = detailExpr.Parameters[0];
var p_var4 = detailExpr.Parameters[1];
var p_var5 = detailExpr.Parameters[2];
var body = dataFilter
.Select(filter => filter.Value
.Select(filterDetail => detailExpr.Body
.ReplaceParameter(p_var4, Expression.Constant(filter.Key))
.ReplaceParameter(p_var5, Expression.Constant(filterDetail.Key)))
.Aggregate(Expression.OrElse))
.Aggregate(Expression.AndAlso);
var predicate = Expression.Lambda<Func<Article, bool>>(body, p_a);
然后使用 Where(predicate)
代替您当前的 Where(query)
。
我正在尝试根据存储在另一个数据库集中的属性进行一些文章过滤。我正在使用一些 类:
public class Article
{
public string ArticleCode { get; set; }
public string var1 { get; set; }
public string var2 { get; set; }
public string var3 { get; set; }
public virtual List<ArticleProperty> Properties { get; set; }
}
public class ArticleProperty
{
public string ArticleCode { get; set; }
public string PropertyCode { get; set; }
public string var4 { get; set; }
public string var5 { get; set; }
public string var6 { get; set; }
}
public class ArticleSummary
{
public string ArticleCode { get; set; }
public string var7 { get; set; }
public string var8 { get; set; }
}
public class WebDbContext : DbContext
{
public virtual DbSet<Article> Article{ get; set; }
public virtual DbSet<ArticleProperty> ArticleProperty{ get; set; }
/* some more code */
}
当我创建这样的查询时,它会执行我想执行的操作:
IQueryable<ArticleSummary> Articles = _DbContext.Article
.Where(a => a.var1 == SomeLocalVariable1)
.Where(a => a.var2 == SomeLocalVariable2 || a.var2 == SomeLocalVariable3)
.Where(a =>
a.Properties.Any(ap =>
(
(ap.ArticleCode == a.ArticleCode && ap.var4 == "A" && ap.var5 == "X") ||
(ap.ArticleCode == a.ArticleCode && ap.var4 == "A" && ap.var5 == "Y")
)
)
&&
a.Eigenschappen.Any(ap =>
(
(ap.ArticleCode == a.ArticleCode && ap.var4 == "B" && ap.var5 == "Z")
)
)
)
.OrderByDescending(a => a.var6)
.Select(a => new ArticleSummary
{
ArticleCode = a.ArticleCode ,
var7 = a.var1
var8 = a.var3
});
但现在我想动态地创建最后一个 Where 语句,就像这样(dataFilter 是一个具有一些过滤器属性的 Dictionary< string, Dictionary< string, bool>>):
var query ="";
bool firstA = true;
foreach (KeyValuePair<string, Dictionary<string, bool>> filter in dataFilter)
{
if (firstA)
query += "a => ";
else
query += " && ";
query += "a.Properties.Any(ap =>"
+ "(";
bool firstB = true;
foreach (KeyValuePair<string,bool> filterDetail in filter.Value)
{
if (!firstB)
query += " || ";
query += "(ap.ArticleCode == a.ArticleCode && ap.var4 == \""+filter.Key+"\" && ap.var5 == \""+filterDetail.Key+"\")";
firstB = false;
}
query += ")"
+ ")";
firstA = false;
}
IQueryable<ArticleSummary> Articles = _DbContext.Article
.Where(a => a.var1 == SomeLocalVariable1)
.Where(a => a.var2 == SomeLocalVariable2 || a.var2 == SomeLocalVariable3)
.Where(query)
.OrderByDescending(a => a.var6)
.Select(a => new ArticleSummary
{
ArticleCode = a.ArticleCode ,
var7 = a.var1
var8 = a.var3
});
'query' 符合预期,但 Where 不起作用,错误:
System.Linq.Dynamic.Core.Exceptions.ParseException: 'No applicable aggregate method 'Any' exists'
只有在有2条'Any'-语句时才会出现这种情况(除以&&,和我做的时候一样'hard-coded')。我不知道为什么...
不使用字符串,直接使用查询:
IQueryable<ArticleSummary> Articles = _DbContext.Article
.Where(a => a.var1 == SomeLocalVariable1)
.Where(a => a.var2 == SomeLocalVariable2 || a.var2 == SomeLocalVariable3)
.Where(query);
foreach(...) {
Articles = Articles.Where(...);
}
Articles = Articles.OrderByDescending(a => a.var6)
.Select(a => new ArticleSummary
{
ArticleCode = a.ArticleCode ,
var7 = a.var1
var8 = a.var3
});
动态 LINQ 有自己的 expression language。 Lambda 表达式不以 a =>
或 ap =>
开头,有一个称为当前范围的东西可以简化一些查询,但通常在访问外部级别参数时会出现问题。所有可查询扩展都定义了一个名为 it
的范围参数,可以省略。
简而言之,动态 LINQ 不太适合嵌套 lambda 表达式访问外部 lambda 参数的复杂查询。
结合编译时和运行时表达式可以相对容易地实现目标。这个想法很简单。
首先,您创建一个编译时 lambda 表达式,其中包含用作占位符的附加参数。然后使用以下简单表达式访问者将占位符替换为实际表达式:
public static class ExpressionExtensions
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
=> node == Source ? Target : base.VisitParameter(node);
}
}
与 string.Format
非常相似,但带有表达式。然后你可以使用 Expression.AndAlso
和 Expression.OrElse
来生成 &&
和 ||
部分。
话虽如此,您的情况如下所示:
Expression<Func<Article, string, string, bool>> detailExpr = (a, var4, var5) =>
a.Properties.Any(ap => ap.ArticleCode == a.ArticleCode && ap.var4 == var4 && ap.var5 == var5);
var p_a = detailExpr.Parameters[0];
var p_var4 = detailExpr.Parameters[1];
var p_var5 = detailExpr.Parameters[2];
var body = dataFilter
.Select(filter => filter.Value
.Select(filterDetail => detailExpr.Body
.ReplaceParameter(p_var4, Expression.Constant(filter.Key))
.ReplaceParameter(p_var5, Expression.Constant(filterDetail.Key)))
.Aggregate(Expression.OrElse))
.Aggregate(Expression.AndAlso);
var predicate = Expression.Lambda<Func<Article, bool>>(body, p_a);
然后使用 Where(predicate)
代替您当前的 Where(query)
。