2009-12-30 9 views
10

Ho questa LINQ-query:Perché questa istruzione di join LINQ non funzionerà?

// types... 
    LinkedList<WeightedItem> itemScores = new LinkedList<WeightedItem>(); 

    var result = from i in _ctx.Items 
       join s in itemScores on i.Id equals s._id 
       orderby s._score descending 
       select new ItemSearchResult(i, s._score); 

    // this fails: 
    return result.ToList(); 

che sta generando questo errore:

Unable to create a constant value of type 'System.Collections.Generic.IEnumerable`1'.
Only primitive types ('such as Int32, String, and Guid') are supported in this context.

[EDIT] Ecco il codice di WeightedItem:

public class WeightedItem 
{ 
    public int _id; 
    public decimal? _score; 

    public WeightedItem(int id, decimal? score) 
    { 
     _id = id; 
     _score = score; 
    } 
} 

Riesci a vedere cosa ho fatto di sbagliato? Il codice viene compilato perfettamente e sia _ctx.Items che itemScores contengono valori corretti.

+0

Puoi pubblicare il codice per WeightedItem – Lazarus

+0

Apparentemente WeightedItem non è un tipo primitivo. – DOK

+0

Lazarus, è fatto. DOK, che significa? – Mickel

risposta

21

Sì, si compila bene - il problema è che non può tradurlo in SQL. Quando si fa riferimento a valori "locali", il framework di entità deve capire cosa fare con loro quando è necessario creare una query SQL. Fondamentalmente non può far fronte a un join tra una collezione in memoria e una tabella di database.

Una cosa che potrebbe essere lavoro sarebbe utilizzare Contains invece. Io non so se LinkedList<T> lavorerà per questo, ma credo List<T> fa, almeno in LINQ to SQL:

List<int> requiredScoreIds = itemScores.Select(x => x._id).ToList(); 

var tmp = (from i in _ctx.Items 
      where requiredScoreIds.Contains(i.Id) 
      orderby s._score descending 
      select i).AsEnumerable(); 

// Now do the join in memory to get the score 
var result = from i in tmp 
      join s in itemScores on i.Id equals s._id 
      select new ItemSearchResult(i, s._score); 

Ora che sta facendo un join nella query in memoria, che è un po 'inutile. Si potrebbe invece utilizzare un dizionario:

List<int> requiredScoreIds = itemScores.Select(x => x._id).ToList(); 

var tmp = (from i in _ctx.Items 
      where requiredScoreIds.Contains(i.Id) 
      orderby s._score descending 
      select i).AsEnumerable(); 

// Create a map from score ID to actual score 
Dictionary<int, decimal?> map = itemScores.ToDictionary(x => x._id, 
                 x => x._score); 

var result = tmp.Select(i => new ItemSearchResult(i, map[i.Id])); 
+0

ha senso, quindi l'oggetto .AsEnumerable() esegue la query e salva il risultato in memoria? In caso contrario, quale parte del codice fa? – Mickel

+2

@Mickel: 'AsEnumerable' non esegue immediatamente la query - ma restituisce un' IEnumerable 'piuttosto che' IQueryable ', quindi il resto della query verrà eseguito utilizzando' Enumerable.xxx' anziché 'Queryable. xxx'. Quando questa query deve finalmente essere eseguita, eseguirà la prima parte nel database e la seconda parte in memoria. –

3

Non è possibile partecipare tra un elenco in memoria e un oggetto interrogabile. Hai bisogno di fare qualcosa di simile:

var criteria = itemScores.Select(x => x._id).ToList(); 
var result_tag = (from i in _ctx.Items 
       where criteria.Contains(i.ID) 
       select i).ToList(); 
var result = from i in result_tag 
      join s in itemScores on i.ID equals s._id 
      orderby s._score descending 
      select new ItemSearchResult(i, s._score); 
+5

Ah shucks - Jon Skeet mi ha battuto :) –

+1

Vince sempre ... – Ragepotato

+0

Jon Skeet è il Chuck Norris di StackOverflow –

1

Solo nel caso la tabella rappresentata da _ctx.Items non è un grande e non si preoccupano di carico tutta la tabella in memoria e poi filtrare in memoria , si può semplicemente scambiare l'ordine degli elementi in dichiarazione aderire, come nel seguente frammento di codice:

LinkedList<WeightedItem> itemScores = new LinkedList<WeightedItem>(); 

var result = from s in itemScores 
      join i in _ctx.Items on s._id equals i.Id 
      orderby s._score descending 
      select new ItemSearchResult(i, s._score); 

return result.ToList(); 

nella dichiarazione originale del metodo di estensione Queryable è stato richiamato:

IQueryable<TResult> Queryable.Join<TOuter, TInner, TKey, TResult>(
     this IQueryable<TOuter> outer, 
     IEnumerable<TInner> inner, 
     Expression<Func<TOuter, TKey>> outerKeySelector, 
     Expression<Func<TInner, TKey>> innerKeySelector, 
     Expression<Func<TOuter, TInner, TResult>> resultSelector 
) 

mentre in quello scambiato viene richiamato il metodo di estensione Enumerable:

IEnumerable<TResult> Enumerable.Join<TOuter, TInner, TKey, TResult>(
     this IEnumerable<TOuter> outer, 
     IEnumerable<TInner> inner, 
     Func<TOuter, TKey> outerKeySelector, 
     Func<TInner, TKey> innerKeySelector, 
     Func<TOuter, TInner, TResult> resultSelector 
) 

così nell'ultima istruzione tavolo _ctx.Items completo viene caricato in memoria e poi si è unito, tramite LINQ to Objects, alla lista itemScores (Non so su LinkedList, l'ho provato con List).

Ho aggiunto questa risposta principalmente perché qualcuno potrebbe digitare il join nell'ordine inverso e farlo funzionare senza nemmeno rendersi conto di ciò che accadrà nel database.

Non suggerirei di unire in questo modo, sebbene possa essere utile per le applicazioni di backoffice ogni volta che le tabelle coinvolte sono costituite da pochi record e l'applicazione non subisce un peggioramento delle prestazioni rilevante. Questa soluzione, dopo tutto, mantiene il codice più pulito.

Problemi correlati