【发布时间】:2011-07-29 22:08:46
【问题描述】:
我正在尝试找出最好的方法来做我认为容易的事情。 我有一个名为 Line 的数据库模型,它代表发票中的一行。
大概是这样的:
public partial class Line
{
public Int32 Id { get; set; }
public Invoice Invoice { get; set; }
public String Name { get; set; }
public String Description { get; set; }
public Decimal Price { get; set; }
public Int32 Quantity { get; set; }
}
这个类是从 db 模型生成的。
我有另一个类增加了一个属性:
public partial class Line
{
public Decimal Total
{
get
{
return this.Price * this.Quantity
}
}
}
现在,通过我的客户控制器,我想做这样的事情:
var invoices = ( from c in _repository.Customers
where c.Id == id
from i in c.Invoices
select new InvoiceIndex
{
Id = i.Id,
CustomerName = i.Customer.Name,
Attention = i.Attention,
Total = i.Lines.Sum( l => l.Total ),
Posted = i.Created,
Salesman = i.Salesman.Name
}
)
但我不能感谢臭名昭著的人
The specified type member 'Total' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
重构它以使其正常工作的最佳方法是什么?
我已经尝试过 LinqKit、i.Lines.AsEnumerable(),并将 i.Lines 放入我的 InvoiceIndex 模型并让它计算视图的总和。
最后一个解决方案“有效”,但我无法对这些数据进行排序。我最终希望能够做到的是
var invoices = ( from c in _repository.Customers
...
).OrderBy( i => i.Total )
我还想分页我的数据,所以我不想浪费时间将整个 c.Invoices 转换为带有 .AsEnumerable() 的列表
赏金
我知道这对某些人来说一定是个大问题。经过数小时的互联网搜索后,我得出的结论是没有得出任何令人满意的结论。然而,我相信对于那些尝试使用 ASP MVC 进行分页和排序的人来说,这一定是一个相当普遍的障碍。 我了解该属性无法映射到 sql,因此您无法在分页之前对其进行排序,但我正在寻找一种获得所需结果的方法。
完美解决方案的要求:
- DRY,这意味着我的总计算将存在于 1 个位置
- 支持排序和分页,并按顺序进行
- 不使用 .AsEnumerable 或 .AsArray 将整个数据表拉入内存
我真的很高兴找到一种在我的扩展部分类中指定 Linq 到实体 SQL 的方法。但有人告诉我这是不可能的。请注意,解决方案不需要直接使用 Total 属性。根本不支持从 IQueryable 调用该属性。我正在寻找一种方法来通过不同的方法实现相同的结果,但同样简单和正交。
赏金的获胜者将是最后得票最高的解决方案,除非有人发布了一个完美的解决方案:)
除非您阅读答案,否则请忽略以下内容:
{1} 使用 Jacek 的解决方案,我更进一步,并使用 LinqKit 使属性可调用。这样,即使 .AsQueryable().Sum() 也包含在我们的部分类中。以下是我现在正在做的一些示例:
public partial class Line
{
public static Expression<Func<Line, Decimal>> Total
{
get
{
return l => l.Price * l.Quantity;
}
}
}
public partial class Invoice
{
public static Expression<Func<Invoice, Decimal>> Total
{
get
{
return i => i.Lines.Count > 0 ? i.Lines.AsQueryable().Sum( Line.Total ) : 0;
}
}
}
public partial class Customer
{
public static Expression<Func<Customer, Decimal>> Balance
{
get
{
return c => c.Invoices.Count > 0 ? c.Invoices.AsQueryable().Sum( Invoice.Total ) : 0;
}
}
}
第一个技巧是 .Count 检查。这些是必需的,因为我猜您不能在空集上调用 .AsQueryable 。您收到关于 Null 实现的错误。
通过布置这 3 个部分类,您现在可以做一些技巧,例如
var customers = ( from c in _repository.Customers.AsExpandable()
select new CustomerIndex
{
Id = c.Id,
Name = c.Name,
Employee = c.Employee,
Balance = Customer.Balance.Invoke( c )
}
).OrderBy( c => c.Balance ).ToPagedList( page - 1, PageSize );
var invoices = ( from i in _repository.Invoices.AsExpandable()
where i.CustomerId == Id
select new InvoiceIndex
{
Id = i.Id,
Attention = i.Attention,
Memo = i.Memo,
Posted = i.Created,
CustomerName = i.Customer.Name,
Salesman = i.Salesman.Name,
Total = Invoice.Total.Invoke( i )
} )
.OrderBy( i => i.Total ).ToPagedList( page - 1, PageSize );
很酷。
有一个问题,LinqKit 不支持属性的调用,你会得到一个关于尝试将 PropertyExpression 转换为 LambaExpression 的错误。有两种方法可以解决这个问题。首先是像这样自己拉表情
var tmpBalance = Customer.Balance;
var customers = ( from c in _repository.Customers.AsExpandable()
select new CustomerIndex
{
Id = c.Id,
Name = c.Name,
Employee = c.Employee,
Balance = tmpBalance.Invoke( c )
}
).OrderBy( c => c.Balance ).ToPagedList( page - 1, PageSize );
我认为这有点愚蠢。所以我修改了 LinqKit 以在遇到属性时提取 get{} 值。它对表达式的操作方式类似于反射,因此编译器不会为我们解析 Customer.Balance。我在 ExpressionExpander.cs 中对 TransformExpr 进行了 3 行更改。它可能不是最安全的代码,并且可能会破坏其他东西,但它现在可以工作,我已经通知了作者这个缺陷。
Expression TransformExpr (MemberExpression input)
{
if( input.Member is System.Reflection.PropertyInfo )
{
return Visit( (Expression)( (System.Reflection.PropertyInfo)input.Member ).GetValue( null, null ) );
}
// Collapse captured outer variables
if( input == null
事实上,我几乎可以保证这段代码会破坏一些东西,但它现在可以工作,这已经足够好了。 :)
【问题讨论】:
-
我认为你的最后一次更新真的应该是它自己的答案。
标签: c# asp.net-mvc linq entity-framework asp.net-mvc-3