代码之家  ›  专栏  ›  技术社区  ›  MJK

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

  •  1
  • MJK  · 技术社区  · 7 年前

    我已经实现了以下特定模式 vkhorikov/SpecificationPattern

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

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

    但是,当我执行时,我得到了以下错误

    系统NotSupportedException无法比较类型的元素 '系统。收藏。通用的ICollection`1[[数据.TableObjects.WarehouseProduct, 数据,版本=1.0.0.0,区域性=中性,PublicKeyToken=null]]'。只有 支持基元类型、枚举类型和实体类型。

    我真的很难为ICollection创建规范。有没有办法做到这一点? 仅供参考,我正在使用EF6连接到SQLServer数据库。

    已更新

    //回复第一条评论。。

    我在repository上使用了规范,因此以下代码出错

       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
    
    1 回复  |  直到 7 年前
        1
  •  2
  •   Evk    7 年前

    您必须始终记住,表达式是通过实体框架转换为SQL查询的。例如,思考一下

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

    是否可以转换为SQL?它不能,因为 expr.Compile() 基本上是。NET代码-它不再是表达式树。您可以使用第三方库,如 LinqKit ,以便能够将一个表达式集成到另一个表达式中,但如果不这样做,它将无法正常工作。但在您的特定情况下,这是不需要的。

    首先,你需要清理你的表情。如果列表 _ids 为空-您不需要筛选任何内容。因此,返回仅返回true(匹配所有项)的表达式:

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