2009-12-30 14 views
10

tengo este LINQ consulta:¿Por qué no funciona este enunciado LINQ join?

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

que está generando este error:

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] Aquí está el código de WeightedItem:

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

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

Puedes ver ¿Qué he hecho mal? El código se compila perfectamente y tanto _ctx.Items como itemScores contienen los valores adecuados.

+0

Puede publicar el código para WeightedItem – Lazarus

+0

Aparentemente WeightedItem no es un tipo primitivo. – DOK

+0

Lazarus, ya está hecho. DOK, lo que significa? – Mickel

Respuesta

21

Sí, compilaría bien, el problema es que no puede traducirlo a SQL. Cuando hace referencia a valores "locales", el marco de la entidad tiene que determinar qué hacer con ellos cuando necesita crear una consulta SQL. Básicamente no puede hacer frente a una unión entre una colección en memoria y una tabla de base de datos.

Una cosa que podría sería utilizar Contains en su lugar. No sé si LinkedList<T> funcionará para esto, pero creo List<T> hace, al menos en LINQ a 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); 

Ahora que está haciendo una combinación en la consulta en la memoria, que es algo innecesario. En su lugar, podría utilizar un diccionario:

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

tiene sentido, por lo que .AsEnumerable() ejecuta la consulta y guarda el resultado en la memoria? Si no, ¿qué parte del código hace? – Mickel

+2

@Mickel: 'AsEnumerable' no ejecuta la consulta de inmediato, pero devuelve un' IEnumerable 'en lugar de' IQueryable ', por lo que el resto de la consulta se realizará utilizando' Enumerable.xxx' en lugar de 'Queryable. xxx'. Cuando la consulta finalmente necesite ejecutarse, hará la primera parte en la base de datos, y la segunda parte en la memoria. –

3

No se puede unir entre una lista en la memoria y un objeto consultable. Que tiene que hacer algo como esto:

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 me ganó :) –

+1

Siempre gana ... – Ragepotato

+0

Jon Skeet es el Chuck Norris de StackOverflow –

1

Sólo en caso de la tabla representada por _ctx.Items no es un grande y no se preocupan por la carga toda la tabla en la memoria y luego filtrarla en la memoria , sólo tiene que cambiar el orden de los elementos en la instrucción de combinación, como en el siguiente fragmento:

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

en la declaración original se invoca el método de extensión consultables:

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 
) 

mientras que en el intercambiado se invoca el método de extensión 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 
) 

por lo que en la última declaración de la tabla _ctx.Items completo se carga en la memoria y luego se unió, a través de LINQ a objetos, a la lista itemScores (No sé sobre LinkedList, lo probé con List).

Agregué esta respuesta principalmente porque alguien podría escribir la unión en el orden inverso y hacer que funcione sin siquiera darse cuenta de lo que sucederá en la base de datos.

No recomendaría unirme de esta manera, aunque puede ser útil para aplicaciones de backoffice cuando las tablas involucradas están compuestas de pocos registros y la aplicación no sufre un empeoramiento relevante del rendimiento. Esta solución, después de todo, mantiene el código más limpio.

Cuestiones relacionadas