Динамический Linq с «.Any» в разделе «Где» (C # / .Net Core / EF Core) - PullRequest
0 голосов
/ 19 октября 2018

Я пытаюсь выполнить фильтрацию статей по свойствам, которые хранятся в другом БД.Я использую некоторые классы:

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

Но теперь я хочу создать последний Гдединамически, как это (dataFilter - это Dictionary > с некоторыми свойствами фильтра):

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

«Запрос» соответствует ожидаемому, а «Где» - нетwork, error:

System.Linq.Dynamic.Core.Exceptions.ParseException: 'No applicable aggregate method 'Any' exists'

Это происходит только тогда, когда есть 2 оператора Any (разделенных на &&, так же, как когда я делаю это жестко).Я не знаю почему ...

Ответы [ 2 ]

0 голосов
/ 19 октября 2018

Динамический LINQ имеет собственный язык выражений .Лямбда-выражения не начинаются с a => или ap =>, есть нечто, называемое текущей областью действия, которое упрощает некоторые запросы, но в целом проблематично с доступом к параметрам внешнего уровня.Все запрашиваемые расширения определяют один параметр области действия, называемый it, который может быть опущен.

Вскоре Dynamic LINQ не очень подходит для сложных запросов с вложенным лямбда-выражением, обращающимся к внешним параметрам лямбды.

Цель может быть достигнута относительно легко с помощью комбинации выражений времени компиляции и времени выполнения.Идея проста.

Сначала вы создаете лямбда-выражение времени компиляции с дополнительными параметрами, которые служат заполнителями.Затем вы заменяете заполнители фактическими выражениями, используя следующий простой посетитель выражений:

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

0 голосов
/ 19 октября 2018

Вместо использования строки просто используйте запрос напрямую:

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