2009-12-30 14 views
10

Ich habe diese LINQ-Abfrage:Warum funktioniert diese LINQ Join-Anweisung nicht?

// 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(); 

Welche diesen Fehler generiert:

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] Hier ist der Code von WeightedItem:

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

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

Können Sie sehen, was habe ich falsch gemacht? Der Code kompiliert perfekt und sowohl die _ctx.Items als auch die itemScores enthalten korrekte Werte.

+0

Können Sie den Code für WeightedItem – Lazarus

+0

Anscheinend WeightedItem ist kein primitiver Typ. – DOK

+0

Lazarus, es ist fertig. DOK, was bedeutet? – Mickel

Antwort

21

Ja, es würde gut kompilieren - das Problem ist, dass es nicht in SQL übersetzen kann. Wenn Sie auf "lokale" Werte verweisen, muss das Entitätsframework herausfinden, was mit ihnen geschehen soll, wenn eine SQL-Abfrage erstellt werden muss. Es ist grundsätzlich nicht möglich, eine Verbindung zwischen einer speicherinternen Auflistung und einer Datenbanktabelle herzustellen.

Eine Sache, die könnte Arbeit wäre Contains stattdessen zu verwenden. Ich weiß nicht, ob LinkedList<T> wird für diese Arbeit, aber ich glaube List<T> tut, zumindest 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); 

Nun, da tut ein in der In-Memory-Join-Abfrage, die etwas unnötig ist. Sie könnten stattdessen ein Wörterbuch verwenden:

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

macht Sinn, also führt die .AsEnumerable() die Abfrage aus und speichert das Ergebnis im Speicher? Wenn nicht, welcher Teil des Codes? – Mickel

+2

@Mickel: 'AsEnumerable' führt die Abfrage nicht sofort aus - aber es gibt ein 'IEnumerable ' anstatt 'IQueryable ' zurück, so dass der Rest der Abfrage mit 'Enumerable.xxx' anstelle von' Queryable erfolgt. xxx'. Wenn diese Abfrage schließlich ausgeführt werden muss, wird der erste Teil in der Datenbank und der zweite Teil im Speicher ausgeführt. –

3

Sie können nicht zwischen einem in Speicherliste und ein abfragbar Objekt verbinden. Sie brauchen so etwas wie dies zu tun:

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 hat mich geschlagen :) –

+1

Er gewinnt immer ... – Ragepotato

+0

Jon Skeet ist der Chuck Norris von StackOverflow –

1

, falls nur die Tabelle durch _ctx.Items dargestellt ist nicht groß, und Sie kümmern sich nicht um Laden alle in der Tabelle im Speicher und dann im Speicher filtern Sie können einfach die Reihenfolge der Elemente in tauschen die Aussage anschließen, wie im folgenden Ausschnitt:

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(); 

in der ursprünglichen Anweisung der Abfragbare Extension-Methode aufgerufen wurde:

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 
) 

während im vertauscht man die Enumerable Erweiterungsmethode aufgerufen wird:

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 
) 

so in der letzten Anweisung der volle _ctx.Items Tabelle in den Speicher geladen wird, und dann verbunden werden, über Linq zu Objekten, auf die Liste itemScores (Ich kenne LinkedList nicht, ich habe es mit List versucht).

Ich habe diese Antwort hauptsächlich hinzugefügt, weil jemand den Join in umgekehrter Reihenfolge eingeben und arbeiten lassen könnte, ohne zu merken, was in der Datenbank passieren wird.

Ich würde nicht vorschlagen, auf diese Weise beizutreten, obwohl es für Backoffice-Anwendungen nützlich sein kann, wenn die beteiligten Tabellen aus wenigen Datensätzen bestehen und die Anwendung keine relevante Leistungsverschlechterung leidet. Diese Lösung hält den Code sauberer.

Verwandte Themen