Используя свойство частичного класса внутри оператора LINQ

Я пытаюсь придумать лучший способ сделать то, что, как мне казалось, будет легко. У меня есть модель базы данных под названием 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
                         }
        )

Но я не могу, благодаря печально известному

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, это означает, что мои общие вычисления будут существовать в одном месте
  • Поддерживать как сортировку, так и разбиение на страницы, и в этом порядке
  • Не загружать всю таблицу данных в память с помощью .AsEnumerable или .AsArray

Что я был бы действительно счастлив find - это способ указать 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 для пустого набора. Вы получаете сообщение об ошибке о материализации 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 );

Очень круто.

Есть уловка, 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 за нас. Я сделал 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

На самом деле я в значительной степени гарантирую, что этот код что-то сломает, но в настоящий момент он работает, и этого достаточно. :)

22
задан Charles 4 August 2011 в 14:48
поделиться