Este es el trato: actualmente estoy usando EF Core 3.1 y digamos que tengo una entidad:
public class Entity { public int Id { get; set; } public int AnotherEntityId { get; set; } public virtual AnotherEntity AnotherEntity { get; set; } } Cuando accedo a DbSet<Entity> Entities de manera normal, incluyo AnotherEntity como:
_context.Entities.Include(e => e.AnotherEntity)
y esto funciona ¿Por qué no, verdad? Entonces voy con:
_context.Entities.FromSqlRaw("SELECT * FROM Entities").Include(e => e.AnotherEntity)
y esto también funciona. Ambos me devuelven la misma colección de objetos unidos con AnotherEntity. Luego uso un procedimiento almacenado que consiste en la misma consulta SELECT * FROM Entities denominadas spGetEntities:
_context.Entities.FromSqlRaw("spGetEntities")
¿adivina qué? Esto también funciona. Me da el mismo resultado pero sin unirse a AnotherEntity, obviamente. Sin embargo, si trato de agregar el Incluir de esta manera:
_context.Entities.FromSqlRaw("spGetEntities").Include(e => e.AnotherEntity)
Estoy obteniendo:
Se llamó a FromSqlRaw o FromSqlInterpolated con SQL no componible y con una consulta componiendo sobre él. Considere llamar a
AsEnumerabledespués del método FromSqlRaw o FromSqlInterpolated para realizar la composición en el lado del cliente.
Aunque la salida de _context.Entities.FromSqlRaw("SELECT * FROM Entities") y _context.Entities.FromSqlRaw("spGetEntities") es idéntica.
No pude encontrar una prueba de que puedo o no puedo hacer esto con EF Core 3.1, pero si alguien pudiera darme alguna pista sobre la posibilidad de este enfoque, sería bueno.
Además, si hay otra forma de unir entidades mediante el procedimiento almacenado, probablemente lo aceptaría como la solución a mi problema.
En breve, no puede hacer eso (al menos para SqlServer). La explicación se encuentra en la documentación de EF Core - Consultas SQL sin procesar - Composición con LINQ :
Componer con LINQ requiere que su consulta de SQL sin formato sea componible, ya que EF Core tratará el SQL proporcionado como una subconsulta. Las consultas SQL que se pueden componer comienzan con la palabra clave
SELECT. Además, el SQL pasado no debe contener ningún carácter u opción que no sea válida en una subconsulta, como:
- Un punto y coma final
- En SQL Server, una sugerencia de nivel de consulta final (por ejemplo,
OPTION (HASH JOIN))- En SQL Server, una cláusula
ORDER BYque no se usa conOFFSET 0 OR TOP 100 PERCENTen la cláusulaSELECTSQL Server no permite la composición sobre llamadas a procedimientos almacenados, por lo que cualquier intento de aplicar operadores de consulta adicionales a dicha llamada dará como resultado un SQL no válido. Use el método
AsEnumerableoAsAsyncEnumerablejusto después de los métodosFromSqlRawoFromSqlInterpolatedpara asegurarse de que EF Core no intente componer sobre un procedimiento almacenado.
Además, dado que Include / ThenInclude requiere EF Core IQueryable<> , AsEnumerable / AsAsyncEnumerable etc. no es una opción. Realmente necesita SQL componible, por lo tanto, los procedimientos almacenados no son una opción.
Sin embargo, en lugar de procedimientos almacenados, puede usar funciones con valores de tabla (TVF) o vistas de base de datos porque se pueden componer ( select * from TVF(params) o select * from db_view ).
En mi caso, estaba convirtiendo EF FromSql() en funcionamiento con un código de procedimiento almacenado 2.1 a 3.1. Al igual que:
ctx.Ledger_Accounts.FromSql("AccountSums @from, @until, @administrationId", new SqlParameter("from", from), new SqlParameter("until", until), new SqlParameter("administrationId", administrationId)); Donde AccountSums es un SP.
Lo único que tenía que hacer era usar FromSqlRaw() y agregar IgnoreQueryFilters() para que volviera a funcionar. Al igual que:
ctx.Ledger_Accounts.FromSqlRaw("AccountSums @from, @until, @administrationId", new SqlParameter("from", from), new SqlParameter("until", until), new SqlParameter("administrationId", administrationId)).IgnoreQueryFilters();Esto se menciona en los comentarios, pero al principio me lo perdí, así que incluyo esto aquí.
La solución para mí fue agregar .AsEnumerable() antes de realizar cualquier operación en el resultado de la consulta sql sin formato.
esto falla
var results = ExecuteMyRawSqlQuery(); results.Select(r=> r.MyColumn).ToList();esto funciona
var results = ExecuteMyRawSqlQuery(); results.AsEnumerable().Select(r=> r.MyColumn).ToList();