Я пытаюсь найти лучший способ сделать то, что, как я думал, будет легко. У меня есть модель базы данных под названием "Линия", которая представляет строку в счете-фактуре.
Это выглядит примерно так:
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()
Bounty
Я знаю, что для некоторых людей это должно быть несколько большая проблема. После нескольких часов очистки Интернета я пришел к выводу, что никакого счастливого заключения не было сделано. Тем не менее, я считаю, что это должно быть довольно распространенным препятствием для тех, кто пытается выполнить подкачку и сортировку с помощью ASP MVC. Я понимаю, что свойство не может быть сопоставлено с sql, и поэтому вы не можете сортировать его перед поисковым вызовом, но то, что я ищу, - это способ получить желаемый результат.
Требования к идеальному решению:
- DRY, что означает, что мои общие вычисления будут существовать в 1-м месте.
- Поддержка сортировки и подкачки и в этом порядке
- Не вытащить всю таблицу данных в память с помощью .AsEnumerable или .AsArray
То, что мне было бы очень приятно найти, - это способ указать Linq для сущностей SQL в моем расширенном частичном классе. Но мне сказали, что это невозможно. Обратите внимание, что для решения не нужно напрямую использовать свойство Total. Вызов этого свойства из IQueryable вообще не поддерживается. Я ищу способ добиться того же результата с помощью другого метода, но в равной степени простой и ортогональный.
Победителем награды будет решение с наибольшим количеством голосов в конце, если кто-то не опубликует идеальное решение:)
Игнорировать ниже, пока вы не прочитаете ответ (ы):
{1} Используя решение Jacek, я сделал еще один шаг и сделал свойства invokable с помощью 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.
С этими тремя частичными классами вы можете теперь делать трюки, например
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 для нас. Существует 3 изменения строки, которые я сделал для TransformExpr в ExpressionExpander.cs. Вероятно, это не самый безопасный код и может сломать другие вещи, но сейчас он работает, и я уведомил автора об этом недостатке.
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
На самом деле я в значительной степени гарантирую, что этот код сломает некоторые вещи, но он работает на данный момент, и это достаточно хорошо.:)