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

Cha*_*les 22 c# linq asp.net-mvc 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; }
    public Decimal Price { get; set; }
    public Int32 Quantity { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

此类是从db模型生成的.
我有另一个类,增加了一个属性:

public partial class Line
{
    public Decimal Total
    {
        get
        {
            return this.Price * this.Quantity
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,从我的客户控制器,我想做这样的事情:

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
                         }
        )
Run Code Online (Sandbox Code Playgroud)

但我不能感谢臭名昭着的

The specified type member 'Total' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Run Code Online (Sandbox Code Playgroud)

重构这个有效的最佳方法是什么?

我尝试过LinqKit,i.Lines.AsEnumerable(),并将i.Lines放在我的InvoiceIndex模型中并让它计算视图的总和.

最后一个解决方案"有效",但我不能对这些数据进行排序.我最终想要做的是

var invoices = ( from c in _repository.Customers
                         ...
        ).OrderBy( i => i.Total )
Run Code Online (Sandbox Code Playgroud)

另外我想分页我的数据,所以我不想浪费时间将整个c.Invoices转换为带有.AsEnumerable()的列表

赏金

我知道这对某些人来说一定是个大问题.经过几个小时的网上搜索,我得出结论,没有得出令人满意的结论.但我相信对于那些尝试使用ASP MVC进行分页和排序的人来说,这必然是一个相当常见的障碍.我知道该属性不能映射到sql,因此你不能在分页之前对它进行排序,但我正在寻找的是一种获得我想要的结果的方法.

完美解决方案的要求:

  • 干,这意味着我的总计算将存在于一个地方
  • 支持排序和分页,并按此顺序
  • 不要使用.AsEnumerable或.AsArray将整个数据表拉入内存

我真的很高兴能找到一种在扩展的分部类中指定Linq to entities 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

第一个技巧是.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 );
Run Code Online (Sandbox Code Playgroud)

很酷.

有一个catch,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 );
Run Code Online (Sandbox Code Playgroud)

我认为这有点傻.所以我修改了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
Run Code Online (Sandbox Code Playgroud)

事实上,我几乎可以保证这段代码会破坏某些东西,但它现在可以正常运行,这已经足够了.:)

Jac*_*goń 22

还有另一种方法,它有点复杂,但是你可以封装这个逻辑.

public partial class Line
{
    public static Expression<Func<Line,Decimal>> TotalExpression
    {
        get
        {
            return l => l.Price * l.Quantity
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后重写查询

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.AsQueryable().Sum(Line.TotalExpression),
                         Posted = i.Created,
                         Salesman = i.Salesman.Name
                     }
               )
Run Code Online (Sandbox Code Playgroud)

它对我有用,在服务器端执行查询并符合DRY规则.


Ant*_*ram 5

您的额外属性只是对模型数据的计算,但该属性不是 EF 可以自然转换的。SQL 完全有能力执行相同的计算,而 EF可以转换计算。如果您在查询中需要它,请使用它代替属性。

Total = i.Lines.Sum( l => l.Price * l.Quantity)
Run Code Online (Sandbox Code Playgroud)

  • @Charles:不,无法将自定义计算映射到属性。您的 linq-to-entities 查询只能包含映射(持久)属性。自定义计算可以包裹在[模型定义的函数](http://stackoverflow.com/questions/5612827/entity-framework-where-do-i-extend-the-csdl-msl/5613642#5613642)但它更多复杂的。 (2认同)