规范模式 - 使用 ICollection 实现表达式

Specification Pattern - Implemeting an expression using ICollection

我已经按照 vkhorikov/SpecificationPattern

实施了特定模式

例如,我有一个产品 table,它与 WarehouseProducts table 有多对多的关系。我需要找到给定的 wearhouses 列表的所有产品。所以,这就是我所拥有的

public class Products
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<WarehouseProduct> Warehouses { get; set; }
}

public class WarehouseProduct
{
    [Key]
    public int Id { get; set; }
    public int WarehouseId { get; set; }
    [ForeignKey("ProductId")]
    public int ProductId { get; set; }
}
public class WarehouseProductSpecification : Specification<Products>
{
    private readonly List<int> _ids;    
    public WarehouseProductSpecification(IEnumerable<int> warehouseIds)
    {
        _ids = warehouseIds.ToList();
    }    
    public override Expression<Func<Products, bool>> ToExpression()
    {
        Expression<Func<WarehouseProduct, bool>> expr =
            (w) => _ids.Contains(w.WarehouseId);

        return
            q => !_ids.Any()
                 || (_ids.Any() && q.Warehouses != null && q.Warehouses.Any(expr.Compile()));
    }
}

但是,当我执行时出现以下错误

System.NotSupportedException Cannot compare elements of type 'System.Collections.Generic.ICollection`1[[Data.TableObjects.WarehouseProduct, Data, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. Only primitive types, enumeration types and entity types are supported.

我真的很难为 ICollection 创建规范。有什么办法可以实现吗? 仅供参考,我正在使用 EF6 连接到 SQLServer 数据库。

已更新

// 回应第一条评论..

我在 repostiory 上使用了规范,所以下面的代码出错

   var products = _context.Products
                .Include("WarehouseProducts")
                .Where(warehouseProductSpec.ToExpression())
                .ToList();

因此 to 列表出现错误

更新 2

我尝试使用@Evk 添加的代码

if (_ids.Count == 0)
    return x => true;
return q => q.Warehouses.Any(w => _ids.Contains(w.WarehouseId));

我在尝试您的代码时遇到以下错误

    Test [0:10.297] Failed: System.ArgumentException: Property 'System.String WearhouseId' is not defined for type 'Data.TableObjects.Products'
System.ArgumentException
Property 'System.String WearhouseId' is not defined for type 'Data.TableObjects.Products'
   at System.Linq.Expressions.Expression.Property(Expression expression, PropertyInfo property)
   at System.Linq.Expressions.ExpressionVisitor.VisitArguments(IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)
   at System.Linq.Expressions.ExpressionVisitor.VisitArguments(IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
   at Infrastructure.Expressions.AndSpecification`1.ToExpression() in C:\..\Expressions\AndSpecification

您必须始终记住,您的表达式已被 entity framework 转换为 SQL 查询。例如,想想这个

q.Warehouses.Any(expr.Compile())

可以翻译成SQL吗?它不能,因为 expr.Compile() 的结果基本上是 .NET 代码——它不再是表达式树。您可以使用第三方库,如 LinqKit,以便能够将一个表达式集成到另一个表达式中,但如果没有它,它将无法正常工作。不过在您的具体情况下不需要。

首先你需要清理你的表情。如果 _ids 列表为空 - 您不需要过滤任何内容。所以 return 表达式 return 为真(匹配所有):

if (_ids.Count == 0)
   return x => true;

现在,在 if 之后,我们知道列表中有 id,因此我们可以跳过与此相关的所有检查。然后,您不需要检查 Warehouses 是否为空。此检查是导致您观察到异常的原因。鉴于此表达式将转换为 SQL 查询,因此没有意义,因此可以删除此检查。表达式中的代码永远不会直接执行(至少在 EF6 中),因此不可能出现空引用异常。

这只剩下一个表达式,它实际上做了有用的工作,所以最终的 ToExpression 将是:

if (_ids.Count == 0)
    return x => true;
return q => q.Warehouses.Any(w => _ids.Contains(w.WarehouseId));