Incluir con FromSqlRaw y procedimiento almacenado en EF Core 3.1

8

Así que aquí está 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 DbSet<Entity> Entitiesaccedo de la 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 Entitiesllamada spGetEntities:

_context.Entities.FromSqlRaw("spGetEntities")

¿adivina qué? Esto tambien funciona. Me da el mismo resultado pero sin unirse a AnotherEntity, obviamente. Sin embargo, si trato de agregar Incluir como este:

_context.Entities.FromSqlRaw("spGetEntities").Include(e => e.AnotherEntity)

Estoy obteniendo:

FromSqlRaw o FromSqlInterpolated se llamó con SQL no composable y con una consulta que lo componía. Considere llamar AsEnumerable despué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 de la posibilidad de este enfoque, sería bueno.

Además, si hay otra forma de obtener entidades unidas utilizando un procedimiento almacenado, probablemente lo acepte como la solución de mi problema.

Gleb
fuente
2
No es EF lo que no puede hacer esto. Es el propio SQL ("SQL no composable"), así que mucho menos EF podría.
Gert Arnold
@GertArnold por favor agrégalo como respuesta. Ayudará a otros usuarios también.
Lutti Coelho
_context.Something.FromSqlRaw ("EXECUTE dbo.spCreateSomething @Id, @Year", sqlParameters) .IgnoreQueryFilters (). AsNoTracking (). AsEnumerable (). FirstOrDefault (); Esto funciona para mí, puede usar ToList () también sobre .AsEnumerable (). FirstOrDefault () para obtener muchos.
Chris Go

Respuestas:

6

En breve, no puede hacer eso (al menos para SqlServer). La explicación está contenida en la documentación de EF Core - Consultas SQL sin procesar - Composición con LINQ :

Componer con LINQ requiere que su consulta SQL sin procesar sea componible ya que EF Core tratará el SQL suministrado como una subconsulta. Las consultas SQL que pueden componerse comienzan con la SELECTpalabra clave. Además, el SQL aprobado no debe contener ningún carácter u opción que no sea válido 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 ORDER BYcláusula que no se utiliza OFFSET 0 OR TOP 100 PERCENTen la SELECTcláusula

SQL Server no permite componer 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 AsEnumerableo AsAsyncEnumerablemétodo inmediatamente después FromSqlRawo FromSqlInterpolatedmétodos para asegurarse de que EF Core no intente componer sobre un procedimiento almacenado.

Además, dado que Include/ ThenIncluderequire EF Core IQueryable<>, AsEnumerable/ AsAsyncEnumerableetc. no es una opción. Realmente necesita SQL composable, 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 son componibles ( select * from TVF(params)o select * from db_view).

Ivan Stoev
fuente
Esto no funciona en mi caso porque estoy usando un tipo derivado. Cuando se usa el tipo derivado del tipo que se usa en el modelo, la consulta se compone incluso si llama a AsEnumerable justo después de FromSqlRaw. No veo otra solución que hacer que ese tipo no se derive, separado con todas las propiedades del tipo base, lo que no es conveniente.
Hrvoje Batrnek
1
@HrvojeBatrnek, supongo que tienes en mente stackoverflow.com/questions/61070935/…
Ivan Stoev
2

En mi caso, estaba convirtiendo EF de trabajo FromSql()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));

¿Dónde AccountSumsestá 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 me perdí eso al principio, así que incluyo esto aquí.

Flores
fuente