¿Por qué no funcionará esta instrucción LINQ?

c# entity-framework join linq linq-to-entities

Pregunta

Tengo esta consulta LINQ:

    // 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:

No se puede crear un valor constante de tipo 'System.Collections.Generic.IEnumerable`1'.
En este contexto, solo se admiten tipos primitivos ('como Int32, String y Guid').

[EDITAR] 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 lo que he hecho mal? El código se compila perfectamente y tanto el _ctx.Items como el itemScores contienen valores apropiados.

Respuesta aceptada

Sí, se compilaría bien, el problema es que no se puede traducir 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 una combinación entre una colección en memoria y una tabla de base de datos.

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

Ahora eso es hacer una combinación en la consulta en memoria, que es algo innecesario. En su lugar, podrías usar 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]));

Respuesta popular

No puedes unirte entre una lista en memoria y un objeto que se pueda poner Necesitas 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);


Related

Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué