C# 在LINQ语句中使用分部类属性

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

我正试图找出做我认为容易的事情的最佳方法。 我有一个名为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
                         }
        )
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将整个数据表拉入内存
我非常乐意找到一种在扩展的分部类中指定Linq to实体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;
        }
    }
}
很酷

有一个问题,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");