2014-12-17 21 views
5

Si consideri il seguente query LINQ to Entities:Utilizzando Dove (Expression <Func <T, bool>>) in IGrouping

return (from lead in db.Leads 
    join postcodeEnProvincie in postcodeEnProvincies 
    on lead.Postcode equals postcodeEnProvincie.Postcode 
    where (lead.CreationDate >= range.StartDate) && (lead.CreationDate <= range.EndDate) 
    group lead by postcodeEnProvincie.Provincie into g 
    select new Web.Models.GroupedLeads() { 
     GroupName = g.Key, 
     HotLeads = g.Count(l => l.Type == Data.LeadType.Hot), 
     Leads = g.Count(), 
     PriorityLeads = g.Count(l => l.Type == Data.LeadType.Priority), 
     Sales = g.Count(l => l.Sold), 
     ProductA = g.Count(l => l.Producten.Any(a => ((a.Name.Equals("productA", StringComparison.CurrentCultureIgnoreCase)) || (a.Parent.Name.Equals("productA", StringComparison.CurrentCultureIgnoreCase))))), 
     ProductB = g.Count(l => l.Producten.Any(a => ((a.Name.Equals("productB", StringComparison.CurrentCultureIgnoreCase)) || (a.Parent.Name.Equals("productB", StringComparison.CurrentCultureIgnoreCase))))), 
     ProductC = g.Count(l => l.Producten.Any(a => ((a.Name.Equals("productC", StringComparison.CurrentCultureIgnoreCase)) || (a.Parent.Name.Equals("productC", StringComparison.CurrentCultureIgnoreCase))))), 
     ProductC = g.Count(l => l.Producten.Any(a => ((a.Name.Equals("productD", StringComparison.CurrentCultureIgnoreCase)) || (a.Parent.Name.Equals("productD", StringComparison.CurrentCultureIgnoreCase))))) 
}).ToList(); 

Se siete come me, le dita dei piedi arricciarsi la ripetizione della logica di selezione del prodotto. Questo schema si ripete anche in un altro punto. Ho prima tentato di sostituirlo con un metodo di estensione su IEnumerable, che ovviamente non funziona: Linq to Entities ha bisogno di un'espressione per analizzare e tradurre.
così ho creato questo metodo:

public static System.Linq.Expressions.Expression<Func<Data.Lead, bool>> ContainingProductEx(string productName) 
    { 
     var ignoreCase = StringComparison.CurrentCultureIgnoreCase; 

     return (Data.Lead lead) => 
      lead.Producten.Any(
       (product => 
        product.Name.Equals(productName, ignoreCase) || 
        product.Parent.Name.Equals(productName, ignoreCase) 
       )); 
    } 

La seguente selezione ora funziona perfettamente bene:

var test = db.Leads.Where(Extensions.ContainingProductEx("productA")).ToList(); 

Tuttavia, questo non si compila, perché IGrouping non contiene un override di Dove che accetta un espressione:

return (from lead in db.Leads 
     join postcodeEnProvincie in postcodeEnProvincies 
     on lead.Postcode equals postcodeEnProvincie.Postcode 
     where (lead.CreationDate >= range.StartDate) && (lead.CreationDate <= range.EndDate) 
     group lead by postcodeEnProvincie.Provincie into g 
     select new Web.Models.GroupedLeads() 
     { 
      GroupName = g.Key, 
      HotLeads = g 
       .Where(l => l.Type == Data.LeadType.Hot) 
       .Count(), 
      Leads = g.Count(), 
      PriorityLeads = g 
       .Where(l => l.Type == Data.LeadType.Priority) 
       .Count(), 
      Sales = g 
       .Where(l => l.Sold) 
       .Count(), 
      ProductA = g 
       .Where(Extensions.ContainingProductEx("productA")) 
       .Count(), 
      ProductB = g 
       .Where(Extensions.ContainingProductEx("productB")) 
       .Count(), 
      ProductC = g 
       .Where(Extensions.ContainingProductEx("productC")) 
       .Count(), 
      ProductD = g 
       .Where(Extensions.ContainingProductEx("productD")) 
       .Count() 
     }).ToList(); 

Casting g di IQueryable compila, ma poi produce un "errore di provider di dati .NET Framework interno 1025.".

C'è un modo per avvolgere questa logica nel proprio metodo?

+0

LINQ to EF è un abstracti su SQL. Se non puoi scrivere ciò che vuoi in SQL, non importa ciò che EF fa. In realtà, è molto più semplice scrivere query complesse in SQL (ad es. Usando viste e UDF) piuttosto che cercare di approssimare lo stesso in LINQ. Ad esempio, quante query vengono eseguite per quello che pensi sia una singola query LINQ? Usa SQL Profiler per vedere cosa sta succedendo. BTW, A 'Where()' dopo un 'Group' è l'equivalente di' HAVING' in T-SQL. Solo gli aggregati sono consentiti nella clausola 'HAVING' –

+1

L'espressione di query Linq iniziale viene compilata correttamente con SQL. I miei tentativi successivi stanno solo cercando di non ripetere più e più volte le stesse espressioni.Il problema esiste solo nell'astrazione, non in ciò che astrae. –

+0

L'originale può essere compilato ma sarà SQL complesso e molto lento. Ad ogni modo, il problema è l'astrazione che perde. EF tradurrà l'intero albero delle espressioni in SQL, quindi il tuo metodo restituirà un albero Expression diverso dall'originale e confonderà EF. I confronti tra stringhe BTW in SQL * non * fanno distinzione tra maiuscole e minuscole, quindi è necessario rimuovere le chiamate a 'String.Equals' - rendono il codice difficile da leggere e inducono l'utente a pensare che abbiano qualche effetto. Inoltre, da dove proviene 'Parent'? Il modo in cui appare all'interno di "Any", potrebbe costringere altre query all'interno dei tuoi aggregati. –

risposta

-1

Invece di preoccuparsi di creare un puntatore/espressione di funzione all'interno della query a cui è possibile fare riferimento (potrebbe non essere possibile), perché non creare un metodo privato separato che prende uno IEnumerable<Lead>, una stringa e restituisce un riferimento e un riferimento il gruppo metodo nella tua query? Penso che la tua confusione derivi dal tentativo di creare un metodo di estensione sulla collezione invece di creare un metodo che nella collezione e il valore che stai cercando.

Qualcosa di simile:

ProductA = GetLeadsForProduct(g, "productA")

private int GetLeadsForProduct(IEnumerable<Lead> leads, string productType) 
{ 
    return leads.Count(l => l.Producten.Any(a => ((a.Name.Equals(productType, StringComparison.CurrentCultureIgnoreCase)) || (a.Parent.Name.Equals(productType, StringComparison.CurrentCultureIgnoreCase))))) 
} 
+0

Questo non può essere tradotto in SQL dal provider di query, provocando l'emissione di un'eccezione. – Servy

+0

@Servy ha senso perché è una chiamata al metodo, non un 'Expression' che rappresenta il risultato. Dal momento che il provider barfs sulle chiamate di metodo, ma un metodo è richiesto per l'astrazione, pensi che sarebbe utile utilizzare il metodo che ho scritto, ma invece di inserire i richiami di metodo nella query, definire l'espressione vars al di fuori della query che contiene il metodo differente chiama e fa riferimento a quei vars all'interno della query? Non carina, ma risolve la duplicazione del problema logico. – moarboilerplate

+0

Questo è esattamente ciò che l'OP sta tentando di fare, ma non riesce a farlo, il che non sorprende, perché non è un compito banale da realizzare. Se hai scritto una risposta che in realtà l'ha fatto, e l'hai fatto in modo che la query finale produca un'espressione che il provider di query può comprendere (vale a dire che è indistinguibile dalla query di lavoro dell'OP), devi pubblicarla. – Servy

2

Questo è un problema che può essere risolto utilizzando LINQKit. Permette alle espressioni di essere invocate da altre espressioni e inline l'espressione invocata all'interno del suo chiamante. Purtroppo, supporta solo una manciata di situazioni molto specifiche, quindi dovremo adattare un po 'il tuo metodo di generazione delle espressioni.

Invece di passare il nome del prodotto per il metodo di generazione di espressione, avremo che si tratti di un parametro dell'espressione restituita:

public static Expression<Func<Data.Lead, string, bool>> ContainingProductEx() 
{ 
    var ignoreCase = StringComparison.CurrentCultureIgnoreCase; 

    return (lead, productName) => 
     lead.Producten.Any(
      (product => 
       product.Name.Equals(productName, ignoreCase) || 
       product.Parent.Name.Equals(productName, ignoreCase) 
      )); 
} 

successivo avremo bisogno di chiamare il metodo prima di dichiarare la query:

var predicate = Extensions.ContainingProductEx(); 

vostra query può ora può essere scritta come:

from lead in db.Leads.AsExpandable() 
//... 
    ProductA = g 
     .Where(lead => predicate.Invoke(lead, "productA")) 
     .Count(), 
    ProductB = g 
     .Where(lead => predicate.Invoke(lead, "productB")) 
     .Count(), 
    ProductC = g 
     .Where(lead => predicate.Invoke(lead, "productC")) 
     .Count(), 
    ProductD = g 
     .Where(lead => predicate.Invoke(lead, "productD")) 
     .Count() 
Problemi correlati