EF 型内核的内部查询

Inner query OfType EFcore

我有一个看起来像这样的模型:

Product
 -DeleteProduct
  └─PreviousProduct (of type SubProduct, not DeleteProduct)
      
 -SubProduct of type SubProduct1, SubProduct2

所以换句话说,产品可以是 DeleteProduct 类型或 SubProduct 类型,如果它是 DeleteProduct 则它有一个 属性 PreviousProduct(SubProduct 类型)

现在我有如下 EF Core linq 查询:

var queryable = context
                .Set<Product>()
                .OfTypes(new[] { typeof(SubProduct1), typeof(DeleteProduct) })
                .Where(p => p.CustomerId == customerId && op.CustomerId != null)
                .Where(p => op is SubProduct1 || (op is DeleteProduct && op.PreviousProduct is AccessProduct))
                .Select(p => p.ProductId);

使用一些扩展方法,(with thanks to Drew):

public static IQueryable<TEntity> NotOfTypes<TEntity>(this IQueryable<TEntity> query, IEnumerable<Type>? typesEnumerable) where TEntity : class
{
    return AddWhere(query, typesEnumerable, types => GetNotTypesPredicate(typeof(TEntity), types));
}

public static IQueryable<TEntity> OfTypes<TEntity>(this IQueryable<TEntity> query, IEnumerable<Type>? typesEnumerable) where TEntity : class
{
    return AddWhere(query, typesEnumerable, types => GetOfOnlyTypesPredicate(typeof(TEntity), types));
}

private static IQueryable<TEntity> AddWhere<TEntity>(
    this IQueryable<TEntity> query,
    IEnumerable<Type>? typesEnumerable,
    Func<IReadOnlyList<Type>, LambdaExpression> getNotTypesPredicate) where TEntity : class
{
    if (typesEnumerable is null)
    {
        return query;
    }

    var types = typesEnumerable.ToArray();

    if (!types.Any())
    {
        return query;
    }

    var lambda = getNotTypesPredicate(types);

    return query.OfType<TEntity>().Where(lambda as Expression<Func<TEntity, bool>> ??
                                         throw new InvalidOperationException("Could not translate to types"));
}

private static LambdaExpression GetNotTypesPredicate(Type baseType, IReadOnlyList<Type> excluded)
{ 
    var param = Expression.Parameter(baseType, "notOfTypeParam");
    Expression merged = Expression.Not(Expression.TypeIs(param, excluded[0]));

    for (var i = 1; i < excluded.Count; i++)
    {
        merged = Expression.AndAlso(merged, Expression.Not(Expression.TypeIs(param, excluded[i])));
    }

    return Expression.Lambda(merged, param);
}

private static LambdaExpression GetOfOnlyTypesPredicate(Type baseType, IReadOnlyList<Type> allowed)
{
    var param = Expression.Parameter(baseType, "typeonlyParam");
    Expression merged = Expression.TypeIs(param, allowed[0]);

    for (var i = 1; i < allowed.Count; i++)
    {
        merged = Expression.OrElse(merged, Expression.TypeIs(param, allowed[i]));
    }

    return Expression.Lambda(merged, param);
}

EntityFrameworkCore 提出以下查询(我通过删除不需要的括号和强制转换简化了查询):

DECLARE @__customerId_0 int = 1;

SELECT [p].[ProductId]
FROM [dbo].[Product] AS [p]
LEFT JOIN (
 SELECT [p0].[ProductId], [p0].[ProductTypeId]
 FROM [dbo].[Product] AS [p0]
 WHERE [p0].[ProductTypeId] IN (1, 2, 3, 9, 10, 20, 21, 22, 30, 31)
 ) AS [t] ON [p].[PreviousProductId] = [t].[ProductId]
WHERE 
(
 [p].[ProductTypeId] IN (1, 0)
 AND [p].[CustomerId] = @__customerId_0
 )
 AND 
 (
     [p].[ProductTypeId] = 1
     OR ([p].[ProductTypeId] = 0 AND [t].[ProductTypeId] = 1)
 )

正如您已经看到的 OfTypes 和 [ProductTypeId] IN (1, 0)

我想去掉不需要的 [ProductTypeId] IN (1, 2, 3, 9, 10, 20, 21, 22, 30, 31) 并将其更改为 ProductTypeId = 1ProductTypeId in (1)

我该怎么做?也许 LinqKit 可以做到这一点?使用嵌套表达式左右?

如果您添加 DbContext 作为附加参数,这是可能的。 DbContext 需要获取 Model 信息。

用法几乎相同:

var queryable = context
    .Set<Product>()
    .OfTypes(context, new[] { typeof(SubProduct1), typeof(DeleteProduct) })
    .Where(p => p.CustomerId == customerId && op.CustomerId != null)
    .Where(p => op is SubProduct1 || (op is DeleteProduct && op.PreviousProduct is AccessProduct))
    .Select(p => p.ProductId);

和实施:

public static class TPHExtemsions
{
    public static IQueryable<TEntity> OfTypes<TEntity>(this IQueryable<TEntity> query, DbContext context,
        IEnumerable<Type>? typesEnumerable)
    {
        var predicate = BuildPredicate<TEntity>(context.Model, typesEnumerable, false);
        if (predicate == null)
            return query;

        return query.Where(predicate);
    }

    public static IQueryable<TEntity> NotOfTypes<TEntity>(this IQueryable<TEntity> query, DbContext context,
        IEnumerable<Type>? typesEnumerable)
    {
        var predicate = BuildPredicate<TEntity>(context.Model, typesEnumerable, true);
        if (predicate == null)
            return query;

        return query.Where(predicate);
    }

    private static Expression<Func<TEntity, bool>> BuildPredicate<TEntity>(IModel model,
        IEnumerable<Type>? typesEnumerable, bool isNot)
    {
        if (typesEnumerable == null)
            return null;

        // get Discriminator values from model
        var discriminatorValues = typesEnumerable.Select(t => model.FindEntityType(t).GetDiscriminatorValue()).ToList();
        if (discriminatorValues.Count == 0)
            return null;

        var et = model.FindEntityType(typeof(TEntity));

        var discriminator = et.GetDiscriminatorProperty();

        // cast List of objects to discriminator type
        var itemsExpression = Expression.Call(typeof(Enumerable), nameof(Enumerable.Cast),
            new[] { discriminator.ClrType }, Expression.Constant(discriminatorValues));

        var param = Expression.Parameter(typeof(TEntity), "e");

        Expression propExpression;
        if (discriminator.PropertyInfo == null)
        {
            // Discriminator is Shadow property, so call via EF.Property(e, "Discriminator")
            propExpression = Expression.Call(typeof(EF), nameof(EF.Property), new[] { discriminator.ClrType },
                param, Expression.Constant(discriminator.Name));
        }
        else
        {
            propExpression = Expression.MakeMemberAccess(param, discriminator.PropertyInfo);
        }

        // generate Contains
        var predicate = (Expression)Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), new[] { discriminator.ClrType },
            itemsExpression, propExpression);

        // invert if needed
        if (isNot)
            predicate = Expression.Not(predicate);

        // generate lambda from predicate
        var predicateLambda = Expression.Lambda<Func<TEntity, bool>>(predicate, param);

        return predicateLambda;
    }
}