2014-01-30 6 views
2

я следующие таблицы сопоставляются с Entity Framework:комплекс LINQ 'Любой' запрос

счетов-фактур (InvoiceID, INVOICENUMBER, IsPaid)
частей (PartID, PARTNUMBER, OrganisationID, IsActive)
Invoices_Parts (InvoiceID, PartID , Количество, IsClaimed)

Я пытаюсь получить List из InvoiceIDs из таблицы Invoices_Parts с рядом условий. Условие, с которым у меня проблема, есть «где любойPart от Invoices_Parts is active».

код до сих пор:

IQueryable<int> partIds = db.Parts.Where(x => 
    x.OrganisationID == loggedInUser.OrganisationID).Select(y => y.PartID); 
    // && x.IsActive 

List<string> invoiceIds = db.Invoices_Parts.Where(x => 
    !x.IsClaimed && x.Invoice.IsPaid && partIds.Contains(x.PartID) 
    .DistinctBy(y => y.InvoiceID) 
    .Select(z => z.InvoiceID.ToString()).ToList(); 

Я закомментирована «& & x.IsActive», потому что я хочу, чтобы список InvoiceIDs быть таким, что по крайней мере одна часть должна удовлетворять условию IsActive - I не хотите отфильтровывать все части, где IsActive является ложным. Как достичь этого в LINQ без ручного управления коллекциями и добавления/удаления элементов?

Примечание: В случае, если кто-то интересно, я использую следующий вспомогательный метод для DistinctBy:

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) 
{ 
    HashSet<TKey> seenKeys = new HashSet<TKey>(); 
    foreach (TSource element in source) 
    { 
     if (seenKeys.Add(keySelector(element))) 
     { 
      yield return element; 
     } 
    } 
} 

Редактировать: У меня есть следующие свойства на каждой сущности:

счета-фактуры :

public virtual ICollection<Invoices_Parts> Invoices_Parts { get; set; } 

Часть:

public virtual ICollection<Invoices_Parts> Invoices_Parts { get; set; } 

Invoices_Parts:

public virtual Invoice Invoice { get; set; } 
public virtual Part Part { get; set; } 
+0

У вас есть свойства навигации в ваших сущностях? –

+2

Возможно, вы знаете об этом, но я должен указать, что ваш IQueryable будет срабатывать, когда он дойдет до 'foreach' в вашем расширении' DistinctBy'. Я бы сказал, что для большей ясности вы должны поместить 'ToList()' перед вызовом 'DistinctBy()' в цепочке, чтобы четко указать, где находится запрос. –

+2

@GeorgeMauer Добавление «ToList» безошибочно оценивает весь запрос немедленно, заставляет весь результат быть оцененным и имеет накладные расходы на создание/заполнение списка, который будет полностью проигнорирован. 'AsEnumerable' выполняет желаемую семантику удобочитаемости без каких-либо дополнительных накладных расходов. – Servy

ответ

4

Люди часто усложнять LINQ запросы, потому что они так привыкли думать в терминах таблиц SQL, присоединяется, и тому подобное. Обычно, если вы ставите свои фактические требования на простом английском языке, вы можете найти более простой запрос, который будет читаться почти точно так же.

Я хочу, чтобы список счетов-фактур идентификаторов из оплаченных счетов-фактур, где какая-либо часть по этому счету является активным, и ... и т.д.

Как об этом:

from invoice in db.Invoices 
where invoice.IsPaid 
where invoice.Invoices_Parts.Any(ip => !ip.IsClaimed && 
    ip.Part.IsActive && 
    ip.OrganisationID == loggedInUser.OrganisationID) 
select invoice.InvoiceId 

Или если вы предпочитаете метод синтаксис:

db.Invoices.Where(i => i.IsPaid) 
.Where(i => i.Invoices_Parts.Any(ip => !ip.IsClaimed && 
    ip.Part.IsActive && 
    ip.OrganisationID == loggedInUser.OrganisationID) 
.Select(i => i.InvoiceId) 
.ToList(); 

PS - вы можете сделать .ToString() если вы ш ant, но по моему опыту разумнее оставить идентификаторы строго типизированными.

PPS - вы можете сделать метод DistinctBy, который будет играть хорошо с Entity Framework (не форсирует преждевременные оценки) следующим образом:

public static IQueryable<TSource> DistinctBy<TSource, TKey>(this IQueryable<TSource> source, Excpression<Func<TSource, TKey>> keySelector) { 
    return source.GroupBy(keySelector).Select(i => i.FirstOrDefault()); 
} 
+0

+1, потому что хорошие практические советы и хороший запрос для загрузки. –

+0

Итак, если предположить, что EF, будет ли вложенный .Any создать внутреннее соединение здесь или применить Cross? –

+0

@GeorgeMauer: Обычно это запрос 'WHERE EXISTS', который позволяет коротко замыкаться, как только он находит совпадение. – StriplingWarrior

0

Что вы можете сделать здесь присоединиться счета-фактуры с частями, чтобы создать коллекцию всех частей для каждого счета-фактуры. После того, как вы есть, что сбор определения, если любой элемент в нем активен достаточно легко:

List<string> invoiceIds = 
    (from invoice in db.Invoices_Parts 
    where !invoice.IsClaimed && invoice.Invoice.IsPaid 
    join part in partIds 
    on invoice.PartId equals part into parts 
    where parts.Any(part => part.IsActive) 
    select invoice.InvoiceID) 
    .Distinct() 
    .ToList(); 
1

OK, при условии, что вы хотите, чтобы все счета-фактуры, где по крайней мере одна часть О том, СЧЕТ активен, я бы сделал это так:

IQueryable<int> partIds = db.Parts 
    .Where(x => x.OrganisationID == loggedInUser.OrganisationID && x.IsActive) 
    .Select(y => y.PartID); 


List<string> invoiceIds = db.Invoices_Parts 
    .Where(x => !x.IsClaimed && x.Invoice.IsPaid && partIds.Contains(x.PartID)) 
    .Select(y => y.InvoiceID.ToString()) 
    .Distinct() 
    .ToList(); 

Если это выглядит очень похоже на то, что у вас есть, это потому, что она есть. Насколько я понял этот вопрос, у вас уже было 99,99%.

+0

Я, вероятно, не объяснил себя должным образом в вопросе. Я также использовал произвольные имена полей, потому что на реальных наборах данных было много полей. – user982119

Смежные вопросы