规范模式 - 使用 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));
我已经按照 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));