использование дерева выражений с Where Any EntityCore - PullRequest
2 голосов
/ 26 апреля 2019

Я пытаюсь отфильтровать объект, имеющий атрибуты (другой объект, вложенный).

Я хочу вернуть родительский объект, который является сущностью, но я хочу фильтровать по всем атрибутам с группой выражений, следовательно, для цикла for.

В настоящее время он не завершен и будет работать вечно?

Проблема в том, что мне нужно сделать ЛЮБОЕ внутри WHERE

IQueryable<Product> entity = _context.Products.AsNoTracking()
    .Include(e => e.Attributes)
    .ThenInclude(e => e.Attribute.ParentAttribute)
    .Include(e => e.Prices);

foreach (var rule in filter.ToRuleGroup().Rules)
{
    var compiled = Rule.CompileRule<ProductAttribute>(rule);
    entity = entity.Where(x => x.Attributes.Any(y => compiled(y.Attribute)));
}

return entity.Take(10).ToArray(); 

Из класса правил

public static Func<T, bool> CompileRule<T>(Rule r)
{
    var paramUser = Expression.Parameter(typeof(T));
    Expression expr = BuildExpr<T>(r, paramUser);
    // build a lambda function User->bool and compile it
    return Expression.Lambda<Func<T, bool>>(expr, paramUser).Compile();
}
public static Expression BuildExpr<T>(Rule r, ParameterExpression param)
{
    var left = MemberExpression.Property(param, r.MemberName);
    var tProp = typeof(T).GetProperty(r.MemberName).PropertyType;
    ExpressionType tBinary;
    // is the operator a known .NET operator?
    if (ExpressionType.TryParse(r.Operator, out tBinary))
    {
        var right = Expression.Constant(Convert.ChangeType(r.TargetValue, tProp));
        // use a binary operation, e.g. 'Equal' -> 'u.Age == 15'
        return Expression.MakeBinary(tBinary, left, right);
    }
    else
    {
        var method = tProp.GetMethod(r.Operator);
        var tParam = method.GetParameters()[0].ParameterType;
        var right = Expression.Constant(Convert.ChangeType(r.TargetValue, tParam));
        // use a method call, e.g. 'Contains' -> 'u.Tags.Contains(some_tag)'
        return Expression.Call(left, method, right);
    }
}

Редактировать версию Lamda:

  public static Expression<Func<T, bool>> ToWhere<T>(Rule r)
        {
            var paramUser = Expression.Parameter(typeof(T));
            Expression expr = BuildExpr<T>(r, paramUser);
            // build a lambda function User->bool and compile it
            return Expression.Lambda<Func<T, bool>>(expr, paramUser);
        }

Редактируйте 2 с любой версией изменений: см. Предложения @Ivan Stoev

   foreach (var rule in filter.ToRuleGroup().Rules){
           entity = entity.Where(Rule.Any((Product p) => p.Attributes.Select(a => a.Attribute), rule));
     }
return entity;

Проблема в том, что она все еще занимает очень много времени.Я не получаю ответ.Вот SQL от сущности:

    SELECT [p].[Id], [p].[IsActive], [p].[IsSyncEnabled], [p].[IsSyncQueued], [p].[LastModifiedDate], [p].[MID], [p].[Sku], [p].[SkuId]
FROM [Product] AS [p]
WHERE (((EXISTS (
    SELECT 1
    FROM [Product_ProductAttribute] AS [a]
    INNER JOIN [Product_Attribute] AS [a.Attribute] ON [a].[AttributeId] = [a.Attribute].[Id]
    WHERE ([a.Attribute].[Type] = N'Raw Sku') AND ([p].[Id] = [a].[ProductId])) AND EXISTS (
    SELECT 1
    FROM [Product_ProductAttribute] AS [a0]
    INNER JOIN [Product_Attribute] AS [a.Attribute0] ON [a0].[AttributeId] = [a.Attribute0].[Id]
    WHERE ([a.Attribute0].[Name] = N'SMALL') AND ([p].[Id] = [a0].[ProductId]))) AND EXISTS (
    SELECT 1
    FROM [Product_ProductAttribute] AS [a1]
    INNER JOIN [Product_Attribute] AS [a.Attribute1] ON [a1].[AttributeId] = [a.Attribute1].[Id]
    WHERE ([a.Attribute1].[Type] = N'Raw Sku') AND ([p].[Id] = [a1].[ProductId]))) AND EXISTS (
    SELECT 1
    FROM [Product_ProductAttribute] AS [a2]
    INNER JOIN [Product_Attribute] AS [a.Attribute2] ON [a2].[AttributeId] = [a.Attribute2].[Id]
    WHERE ([a.Attribute2].[Name] = N'LARGE') AND ([p].[Id] = [a2].[ProductId]))) AND EXISTS (
    SELECT 1
    FROM [Product_ProductAttribute] AS [a3]
    INNER JOIN [Product_Attribute] AS [a.Attribute3] ON [a3].[AttributeId] = [a.Attribute3].[Id]
    WHERE ([a.Attribute3].[Name] = N'BLACK') AND ([p].[Id] = [a3].[ProductId]))
ORDER BY [p].[Id]
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (93ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [p.Attributes].[Id], [p.Attributes].[AttributeId], [p.Attributes].[ProductId], [p.Attribute].[Id], [p.Attribute].[Hiearchy], [p.Attribute].[Name], [p.Attribute].[ParentAttributeId], [p.Attribute].[Type], [p.Attribute].[Value], [p.Attribute.ParentAttribute].[Id], [p.Attribute.ParentAttribute].[Hiearchy], [p.Attribute.ParentAttribute].[Name], [p.Attribute.ParentAttribute].[ParentAttributeId], [p.Attribute.ParentAttribute].[Type], [p.Attribute.ParentAttribute].[Value]
FROM [Product_ProductAttribute] AS [p.Attributes]
INNER JOIN [Product_Attribute] AS [p.Attribute] ON [p.Attributes].[AttributeId] = [p.Attribute].[Id]
LEFT JOIN [Product_Attribute] AS [p.Attribute.ParentAttribute] ON [p.Attribute].[ParentAttributeId] = [p.Attribute.ParentAttribute].[Id]
INNER JOIN (
    SELECT [p0].[Id]
    FROM [Product] AS [p0]
    WHERE (((EXISTS (
        SELECT 1
        FROM [Product_ProductAttribute] AS [a4]
        INNER JOIN [Product_Attribute] AS [a.Attribute4] ON [a4].[AttributeId] = [a.Attribute4].[Id]
        WHERE ([a.Attribute4].[Type] = N'Raw Sku') AND ([p0].[Id] = [a4].[ProductId])) AND EXISTS (
        SELECT 1
        FROM [Product_ProductAttribute] AS [a5]
        INNER JOIN [Product_Attribute] AS [a.Attribute5] ON [a5].[AttributeId] = [a.Attribute5].[Id]
        WHERE ([a.Attribute5].[Name] = N'SMALL') AND ([p0].[Id] = [a5].[ProductId]))) AND EXISTS (
        SELECT 1
        FROM [Product_ProductAttribute] AS [a6]
        INNER JOIN [Product_Attribute] AS [a.Attribute6] ON [a6].[AttributeId] = [a.Attribute6].[Id]
        WHERE ([a.Attribute6].[Type] = N'Raw Sku') AND ([p0].[Id] = [a6].[ProductId]))) AND EXISTS (
        SELECT 1
        FROM [Product_ProductAttribute] AS [a7]
        INNER JOIN [Product_Attribute] AS [a.Attribute7] ON [a7].[AttributeId] = [a.Attribute7].[Id]
        WHERE ([a.Attribute7].[Name] = N'LARGE') AND ([p0].[Id] = [a7].[ProductId]))) AND EXISTS (
        SELECT 1
        FROM [Product_ProductAttribute] AS [a8]
        INNER JOIN [Product_Attribute] AS [a.Attribute8] ON [a8].[AttributeId] = [a.Attribute8].[Id]
        WHERE ([a.Attribute8].[Name] = N'BLACK') AND ([p0].[Id] = [a8].[ProductId]))
) AS [t] ON [p.Attributes].[ProductId] = [t].[Id]
ORDER BY [t].[Id]
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (156ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [p.Prices].[Division], [p.Prices].[ProductId], [p.Prices].[PriceId], [p.Prices].[EntryDate], [p.Prices].[EffectiveDate], [p.Prices].[ExpireDate]
FROM [Product_ProductPrice] AS [p.Prices]
INNER JOIN (
    SELECT [p1].[Id]
    FROM [Product] AS [p1]
    WHERE (((EXISTS (
        SELECT 1
        FROM [Product_ProductAttribute] AS [a9]
        INNER JOIN [Product_Attribute] AS [a.Attribute9] ON [a9].[AttributeId] = [a.Attribute9].[Id]
        WHERE ([a.Attribute9].[Type] = N'Raw Sku') AND ([p1].[Id] = [a9].[ProductId])) AND EXISTS (
        SELECT 1
        FROM [Product_ProductAttribute] AS [a10]
        INNER JOIN [Product_Attribute] AS [a.Attribute10] ON [a10].[AttributeId] = [a.Attribute10].[Id]
        WHERE ([a.Attribute10].[Name] = N'SMALL') AND ([p1].[Id] = [a10].[ProductId]))) AND EXISTS (
        SELECT 1
        FROM [Product_ProductAttribute] AS [a11]
        INNER JOIN [Product_Attribute] AS [a.Attribute11] ON [a11].[AttributeId] = [a.Attribute11].[Id]
        WHERE ([a.Attribute11].[Type] = N'Raw Sku') AND ([p1].[Id] = [a11].[ProductId]))) AND EXISTS (
        SELECT 1
        FROM [Product_ProductAttribute] AS [a12]
        INNER JOIN [Product_Attribute] AS [a.Attribute12] ON [a12].[AttributeId] = [a.Attribute12].[Id]
        WHERE ([a.Attribute12].[Name] = N'LARGE') AND ([p1].[Id] = [a12].[ProductId]))) AND EXISTS (
        SELECT 1
        FROM [Product_ProductAttribute] AS [a13]
        INNER JOIN [Product_Attribute] AS [a.Attribute13] ON [a13].[AttributeId] = [a.Attribute13].[Id]
        WHERE ([a.Attribute13].[Name] = N'BLACK') AND ([p1].[Id] = [a13].[ProductId]))
) AS [t0] ON [p.Prices].[ProductId] = [t0].[Id]
ORDER BY [t0].[Id]

1 Ответ

1 голос
/ 27 апреля 2019

Не используйте делегаты (Func<...>) внутри дерева выражений запросов, потому что они вызывают оценку клиента . В вашем случае вместо Func<T, bool> CompileRule<T>(Rule r) вы должны использовать Expression<Func<T, bool>> ToWhere<T>(Rule r) (рассмотрите возможность изменения имени).

Чтобы иметь возможность использовать Expression<Func<T, bool>> для Enumerable.Any<T>, который ожидает Func<T, bool> и применять к внутреннему аксессору коллекции внутри дерева выражений, вам понадобится вспомогательный метод, подобный этому:

public static partial class ExpressionUtils
{
    public static Expression<Func<TOuter, bool>> Any<TOuter, TInner>(Expression<Func<TOuter, IEnumerable<TInner>>> innerSelector, Expression<Func<TInner, bool>> innerPredicate)
    {
        var parameter = innerSelector.Parameters[0];
        var body = Expression.Call(
            typeof(Enumerable), nameof(Enumerable.Any), new[] { typeof(TInner) },
            innerSelector.Body, innerPredicate);
        return Expression.Lambda<Func<TOuter, bool>>(body, parameter);
    }
}

Выше приведена общая реализация. Специфичная Rule реализация внутри вашего Rule класса будет в основном объединять два:

public static Expression<Func<TOuter, bool>> Any<TOuter, TInner>(Expression<Func<TOuter, IEnumerable<TInner>>> innerSelector, Rule r)
    => innerSelector.Any(ToWhere<TInner>(r));

Использование с вашим образцом будет:

foreach (var rule in filter.ToRuleGroup().Rules)
{
    entity = entity.Where(Rule.Any((Product p) => p.Attributes.Select(a => a.Attribute), rule));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...