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