C# 在LINQ语句中使用分部类属性
我正试图找出做我认为容易的事情的最佳方法。 我有一个名为Line的数据库模型,它表示发票中的一行 大致上是这样的:C# 在LINQ语句中使用分部类属性,c#,asp.net-mvc,linq,entity-framework,asp.net-mvc-3,C#,Asp.net Mvc,Linq,Entity Framework,Asp.net Mvc 3,我正试图找出做我认为容易的事情的最佳方法。 我有一个名为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; } pub
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
}
)
var invoices =
(from c in _repository.Customers
where c.Id == id
from i in c.Invoices
select new
{
Id = i.Id,
CustomerName = i.Customer.Name,
Attention = i.Attention,
Lines = i.Lines,
Posted = i.Created,
Salesman = i.Salesman.Name
})
.ToArray()
.Select (i => new InvoiceIndex
{
Id = i.Id,
CustomerName = i.CustomerName,
Attention = i.Attention,
Total = i.Lines.Sum(l => l.Total),
Posted = i.Posted,
Salesman = i.Salesman
});
bool exists = db.Employees.Decompile().Any(employee => employee.FullName == "Test User");
但我不能多亏了那个臭名昭著的
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.发票转换为带有.AsEnumerable()的列表
赏金
我知道这对一些人来说一定是个大问题。经过数小时的网上搜索,我得出了一个结论:没有得出令人满意的结论。然而,我相信对于那些试图使用ASP MVC进行分页和排序的人来说,这一定是一个相当常见的障碍。
我知道该属性不能映射到sql,因此您不能在分页之前对其进行排序,但我正在寻找一种获得所需结果的方法
完美解决方案的要求:
- DRY,这意味着我的总计算将存在于1个位置
- 支持排序和分页,并按此顺序
- 不使用.AsEnumerable或.AsArray将整个数据表拉入内存
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;
}
}
}
很酷
有一个问题,LinqKit不支持属性调用,您将在尝试将PropertyExpression强制转换为LambeExpression时出错。有两种方法可以解决这个问题。首先是像这样自己拉这个表达式
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
事实上,我几乎可以保证这段代码会破坏一些东西,但目前它仍然有效,这已经足够好了。:) 试试这样的方法:
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
}
)
var invoices =
(from c in _repository.Customers
where c.Id == id
from i in c.Invoices
select new
{
Id = i.Id,
CustomerName = i.Customer.Name,
Attention = i.Attention,
Lines = i.Lines,
Posted = i.Created,
Salesman = i.Salesman.Name
})
.ToArray()
.Select (i => new InvoiceIndex
{
Id = i.Id,
CustomerName = i.CustomerName,
Attention = i.Attention,
Total = i.Lines.Sum(l => l.Total),
Posted = i.Posted,
Salesman = i.Salesman
});
bool exists = db.Employees.Decompile().Any(employee => employee.FullName == "Test User");
基本上,这会在数据库中查询您需要的记录,将它们放入内存,然后在数据存在后进行求和。您的额外属性只是从模型中计算数据,但该属性不是EF可以自然转换的。SQL完全能够执行相同的计算,EF可以转换计算。如果在查询中需要该属性,请使用它代替该属性
Total = i.Lines.Sum( l => l.Price * l.Quantity)
只是想在这里补充一些我自己的想法。因为我是一个完美主义者,所以我不想把表格的全部内容都放到内存中,这样我就可以进行计算,然后排序,然后翻页。因此,不知何故,表格需要知道总数是多少 我当时的想法是在line表中创建一个名为“CalcTotal”的新字段,其中包含行的计算总数。每次更改行时都会根据.Total的值设置此值 这有两个优点。首先,我可以将查询更改为:
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.CalcTotal ),
Posted = i.Created,
Salesman = i.Salesman.Name
}
)
排序和分页将起作用,因为它是一个db字段。我可以在我的控制器中进行检查(line.CalcTotal==line.Total),以检测任何tom的愚蠢行为。它确实会在我的存储库中造成一点开销,也就是说,当我要保存或创建一个新行时,我必须添加它
line.CalcTotal = line.Total
但我认为这可能是值得的
为什么要麻烦地拥有两个具有相同数据的属性
这是真的,我可以说
line.CalcTotal = line.Quantity * line.Price;
在我的存储库中。但是这不是很正交,如果需要对行总数进行某些更改,那么编辑部分行类比编辑行存储库更有意义。还有另一种方法,它稍微复杂一些,但可以让您封装此逻辑
public partial class Line
{
public static Expression<Func<Line,Decimal>> TotalExpression
{
get
{
return l => l.Price * l.Quantity
}
}
}
它对我有用,在服务器端执行查询,并符合DRY规则。虽然不是对你的主题的回答,但可能是对你问题的回答: 既然您似乎愿意修改数据库,为什么不在数据库中创建一个基本上
create view TotalledLine as
select *, Total = (Price * Quantity)
from LineTable;
然后将数据模型更改为使用TotaledLine而不是LineTable
毕竟,如果您的应用程序需要Total,那么想其他人可能需要Total也不难,而将这种类型的计算集中到数据库中是使用数据库的原因之一。我认为解决此问题最简单的方法是使用(Alexander Zaytsev制作) 它是一个库,能够将委托或方法体反编译为其lambda表示
说明: 我们有一个班级
var employees = (from employee in db.Employees
where (employee.FirstName + " " + employee.LastName) == "Test User"
select employee).ToList();
var employees = (from employee in db.Employees
where employee.FullName.Computed() == "Test User"
select employee).ToList();
bool exists = db.Employees.Decompile().Any(employee => employee.FullName == "Test User");
bool exists = db.Employees.Any(employee => (employee.FirstName + " " + employee.LastName) == "Test User");