Este código arroja una excepción:
var query = services .SomeQuery(bar).select(x => (Foo)x) .Where(x.PropertyOfFoo == FooState.SomeState); var result = query.ToList();La excepción:
Unable to cast the type... LINQ to Entities only supports casting EDM primitive or enumeration types.Este código funciona:
var query = services .SomeQuery(bar).select(x => x as Foo) .Where(x.PropertyOfFoo == FooState.SomeState); var result = query.ToList(); ¿Por qué as permite la conversión y cast no?
Entiendo que as devolverá nulo y cast generará una excepción si alguna de las llamadas falla. Bueno. Pero cuando ejecuto este código:
var query = services .SomeQuery(bar); var result = query.ToList();Obtengo un resultado de consulta mucho más grande. ¿Por qué?
LINQ to Entities no es lo mismo que LINQ to Objects. Mientras que las funciones de LINQ to Objects pueden tomar cualquier delegado coincidente e invocarlo ciegamente como código C# normal, LINQ to Entities trata sus lambdas como árboles de expresión porque necesita comprender la semántica de esas lambdas (no solo su firma) y transformarlas en un equivalente consulta para su backend EF. Naturalmente, esto significa que LINQ to Entities no puede manejar todas las operaciones que LINQ to Objects sí puede.
Ahora, como dijiste, una diferencia entre lanzar y usar as es que lanzar lanza una excepción en caso de falla y as devuelve null . Pero hay una diferencia aún más importante entre los dos: una conversión aplicará cualquier conversión explícita personalizada potencial, mientras as simplemente intentará reinterpretar la referencia como otra cosa, ignorando cualquier conversión potencial.
Como comprenderá, una conversión es mucho más complicada que as , porque una conversión puede invocar un método personalizado (el explicit operator ) que el proveedor de LINQ no puede resolver fácilmente. Es probable que el proveedor simplemente no pueda examinar el código de la posible conversión personalizada (no sé lo suficiente sobre las limitaciones de los árboles de expresión), y mucho menos traducirlo para la fuente subyacente. Por lo tanto, LINQ to Entities elige permitir solo los casos as conversión más simples (básicamente, permite los casos en los que la lógica de conversión se conocía con anticipación y no puede ser un código de usuario personalizado).
Hay una diferencia importante entre las dos declaraciones que muestra que, más que nada (como las reglas de CLR), Entity Framework está profundamente involucrado aquí.
x as Foo se traduce a SQL. EF puede hacer eso porque sabe que siempre devolverá un resultado, ya sea Foo o nulo. El SQL generado es monstruoso por cierto, pero hace el trabajo.
(Foo)x no se traduce a SQL. EF sabe que no hay forma de crear objetos Foo para todas las x , como exige la semántica de conversión, y lanza una excepción.
Tenga en cuenta que EF aceptará foos.Select(f => (Bar)f) , porque siempre es posible crear un tipo base a partir de un subtipo. La conversión real se realiza después de que se recibe el resultado de SQL, no afecta al propio SQL.
También aceptará bars.OfType<Foo>().Select(x => (Foo)x) (aunque esto es bastante inútil).
Así que el mensaje de error...
LINQ to Entities solo admite la conversión de tipos primitivos o de enumeración de EDM.
... no es del todo cierto. EF acepta moldes cuando son factibles. Entonces, dentro del traductor de consultas de EF, solo hay un montón de lógica para decidir si vale la pena o no generar una declaración SQL.
Es una diferencia en cómo funciona la conversión directa frente a cómo funciona el operador as . La conversión directa no está disponible porque no está utilizando un tipo de valor; solo funciona para primitivos.
Ahora, su lambda está tratando de seleccionar y proyectar; podría dividir eso en dos operaciones, entonces:
var result = services.SomeQuery(bar).select(x => new Foo() { SomeProperty = x.SomeProperty, SomeOtherProperty = x.SomeOtherProperty, ... }).ToList() En cuanto a la mayor cantidad de resultados: te falta una cláusula where allí.