【发布时间】:2014-12-05 21:02:39
【问题描述】:
我的域模型有很多复杂的财务数据,这些数据是对各种实体的多个属性进行相当复杂计算的结果。我通常将这些作为[NotMapped] 属性包含在适当的域模型上(我知道,我知道 - 关于将业务逻辑放入您的实体中有很多争论 - 务实,它与 AutoMapper 配合得很好,让我定义可重用的 DataAnnotations - 讨论这是否好不是我的问题)。
只要我想具体化整个实体(以及任何其他依赖实体,通过.Include() LINQ 调用或通过具体化后的其他查询),然后在查询后将这些属性映射到视图模型,这工作正常。当尝试通过投影到视图模型而不是物化整个实体来优化有问题的查询时,就会出现问题。
考虑以下领域模型(显然是简化的):
public class Customer
{
public virtual ICollection<Holding> Holdings { get; private set; }
[NotMapped]
public decimal AccountValue
{
get { return Holdings.Sum(x => x.Value); }
}
}
public class Holding
{
public virtual Stock Stock { get; set; }
public int Quantity { get; set; }
[NotMapped]
public decimal Value
{
get { return Quantity * Stock.Price; }
}
}
public class Stock
{
public string Symbol { get; set; }
public decimal Price { get; set; }
}
以及以下视图模型:
public class CustomerViewModel
{
public decimal AccountValue { get; set; }
}
如果我尝试像这样直接投影:
List<CustomerViewModel> customers = MyContext.Customers
.Select(x => new CustomerViewModel()
{
AccountValue = x.AccountValue
})
.ToList();
我最终得到以下NotSupportedException:Additional information: The specified type member 'AccountValue' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
这是预期的。我明白了 - 实体框架无法将属性获取器转换为有效的 LINQ 表达式。但是,如果我使用完全相同的代码进行投影,但在投影中,它可以正常工作:
List<CustomerViewModel> customers = MyContext.Customers
.Select(x => new CustomerViewModel()
{
AccountValue = x.Holdings.Sum(y => y.Quantity * y.Stock.Price)
})
.ToList();
因此我们可以得出结论,实际逻辑可以转换为 SQL 查询(即,没有什么像从磁盘读取、访问外部变量等奇特的东西)。
所以问题来了: 有没有办法让 应该 可转换为可在 LINQ 到实体投影中重用的 SQL 的逻辑?
考虑到这种计算可以在许多不同的视图模型中使用。在每个动作中将其复制到投影中既麻烦又容易出错。如果计算更改为包含乘数怎么办?我们必须在使用它的任何地方手动定位和更改它。
我尝试过的一件事是将逻辑封装在 IQueryable 扩展中:
public static IQueryable<CustomerViewModel> WithAccountValue(
this IQueryable<Customer> query)
{
return query.Select(x => new CustomerViewModel()
{
AccountValue = x.Holdings.Sum(y => y.Quantity * y.Stock.Price)
});
}
可以这样使用:
List<CustomerViewModel> customers = MyContext.Customers
.WithAccountValue()
.ToList();
在这样一个简单的人为情况下,这已经足够了,但它不是可组合的。因为扩展的结果是IQueryable<CustomerViewModel> 而不是IQueryable<Customer>,所以不能将它们链接在一起。如果我在一个视图模型中有两个这样的属性,一个在另一个视图模型中,然后另一个在第三个视图模型中,我将无法对所有三个视图模型使用相同的扩展 - 这会破坏整个目的。使用这种方法,要么全有,要么全无。每个视图模型都必须具有完全相同的一组计算属性(这种情况很少见)。
很抱歉这个冗长的问题。我更愿意提供尽可能多的细节,以确保人们理解问题并可能帮助其他人。我只是觉得我在这里遗漏了一些可以让所有这些都成为焦点的东西。
【问题讨论】:
标签: c# linq entity-framework linq-to-entities