All streams
Search
Write a publication
Pull to refresh
7
0
Сергей @ioserg

User

Send message

При маленьком количестве запросов можно и константы. Проблемы начинаются при большой нагрузке. Получилось что один не самый эффективный метод "из коробки" поменяли на другой не самый эффективный метод. При этом основной причиной называется универсальность такого подхода для "all modern" баз данных.

Так что сейчас подправят универсальность, а затем может и до IN с параметрами дело дойдет. Но выглядет так, что если все таки сделают, то выбирать его придется вручную. Как сейчас IN с константами или OPENJSON. А может это будет уже не Contains().

Пожалуйста! Но надо учитывать, что это "сырой" вариант. В нем нет бакетирования (bucketing, padding) переменных. А для MS SQL будет еще ограничение на 2100 параметров в запросе. Так как фильтр по двум ИД сразу, то максимальное количество фильтров 1050. Это если в этом же запросе не будет еще других условий, которые транслируются в параметры

Не научился, добавили больше способов отключить OPENJSON. Заняты с их точки зрения более приоритетными задачами. Хотя странно, что для своего же продукта оптимизацию не сделали.

При небольшом отношении (количество ИД) / (всего записей) быстрее работает IN с параметрами. По мере увеличения этого соотношения OPENJSON выравнивается с ним по скорости, а затем бывает даже чуть быстрее.

В идеале нужен гибридный подход, использующий заполнение параметров при небольших значениях, а OPENJSON для большого количества параметров. Но разработчики EFCORE считают, что архитектура запросов и кэширования EF делает это нетривиальной задачей.

Как только все текущие баги исправят, так обязательно запилят)

то можно взять такой вариант FilterByItems() :

public static IQueryable<T> FilterByItems<T, TItem>(
    this IQueryable<T> query,
    IEnumerable<TItem> items,
    Expression<Func<T, TItem, bool>> filterPattern, bool isOr)
{
    Expression? predicate = null;
    foreach (var item in items)
    {
        var itemExpr = Expression.Constant(item);
        var itemCondition = ExpressionReplacer.Replace(filterPattern.Body, filterPattern.Parameters[1], itemExpr);
        predicate = predicate == null
            ? itemCondition
            : Expression.MakeBinary(isOr ? ExpressionType.OrElse : ExpressionType.AndAlso, predicate,
                itemCondition);
    }

    predicate ??= Expression.Constant(false);
    var filterLambda = Expression.Lambda<Func<T, bool>>(predicate, filterPattern.Parameters[0]);

    return query.Where(filterLambda);
}

class ExpressionReplacer(IDictionary<Expression, Expression> replaceMap) : ExpressionVisitor
{
    readonly IDictionary<Expression, Expression> _replaceMap = replaceMap ??
        throw new ArgumentNullException(nameof(replaceMap));

    [return: NotNullIfNotNull(nameof(node))]
    public override Expression? Visit(Expression? node)
    {
        if (node != null && _replaceMap.TryGetValue(node, out var replacement))
        {
            return replacement;
        }

        return base.Visit(node);
    }

    public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr) =>
        new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);

    public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap) =>
        new ExpressionReplacer(replaceMap).Visit(expr);

    public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
    {
        if (lambda.Parameters.Count != toReplace.Length)
        {
            throw new InvalidOperationException();
        }

        return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count).
            ToDictionary(i => (Expression)lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
    }
}

FilterByItems() отсюда, с небольшими изменениями https://stackoverflow.com/questions/67666649/lambda-linq-with-contains-criteria-for-multiple-keywords/67666993#67666993

Если надо так:

  var filters = new[]
  {
      new { CompanyId = 2, Number = 40 },
      new { CompanyId = 3, Number = 80 },
  };

  var departments = await dbcontext.Set<Department>().FilterByItems(
      filters,
      (dep, fr) => dep.CompanyId == fr.CompanyId && dep.Number == fr.Number,
      true).ToListAsync();
exec sp_executesql N'SELECT [d].[Id], [d].[CompanyId], [d].[Description], [d].[Name], [d].[Number]
FROM [Departments] AS [d]
WHERE (([d].[CompanyId] = @__CompanyId_0) AND ([d].[Number] = @__Number_1)) OR (([d].[CompanyId] = @__CompanyId_2) AND ([d].[Number] = @__Number_3))',N'@__CompanyId_0 int,@__Number_1 int,@__CompanyId_2 int,@__Number_3 int',
@__CompanyId_0=2,@__Number_1=40,@__CompanyId_2=3,@__Number_3=80

Information

Rating
Does not participate
Registered
Activity

Specialization

Backend Developer, Fullstack Developer
Lead
C#
.NET
Visual Studio
OOP
SQL
Database
REST
Apache Kafka