c# - stored - Procedimientos almacenados para CUD: ¿Cuál es el propósito de las dos instrucciones SELECT en un procedimiento almacenado Insertar con scaffolded?
entity framework stored procedure return select (2)
Pasando por Tom Dykstra Comenzando con Entity Framework 6 Code First usando MVC 5 tutorial, la parte 9 cubre cómo configurar EF6 para utilizar procedimientos almacenados para CUD.
Cuando se agrega la migración de DepartmentSP
través de la consola del administrador de paquetes, se genera automáticamente la llamada CreateStoredProcedure () para crear el procedimiento almacenado Department_Insert:
CreateStoredProcedure(
"dbo.Department_Insert",
p => new
{
Name = p.String(maxLength: 50),
Budget = p.Decimal(precision: 19, scale: 4, storeType: "money"),
StartDate = p.DateTime(),
InstructorID = p.Int(),
},
body:
@"INSERT [dbo].[Department]([Name], [Budget], [StartDate], [InstructorID])
VALUES (@Name, @Budget, @StartDate, @InstructorID)
DECLARE @DepartmentID int
SELECT @DepartmentID = [DepartmentID]
FROM [dbo].[Department]
WHERE @@ROWCOUNT > 0 AND [DepartmentID] = scope_identity()
SELECT t0.[DepartmentID]
FROM [dbo].[Department] AS t0
WHERE @@ROWCOUNT > 0 AND t0.[DepartmentID] = @DepartmentID"
);
¿Por qué hay dos instrucciones SELECT
en el procedimiento almacenado generado automáticamente?
Probé la siguiente simplificación:
CreateStoredProcedure(
"dbo.Department_Insert",
p => new
{
Name = p.String(maxLength: 50),
Budget = p.Decimal(precision: 19, scale: 4, storeType: "money"),
StartDate = p.DateTime(),
InstructorID = p.Int(),
},
body:
@"INSERT [dbo].[Department]([Name], [Budget], [StartDate], [InstructorID])
VALUES (@Name, @Budget, @StartDate, @InstructorID)
SELECT t0.[DepartmentID]
FROM [dbo].[Department] AS t0
WHERE @@ROWCOUNT > 0 AND t0.[DepartmentID] = scope_identity()"
);
.. y esto parece funcionar bien, pero me podría estar perdiendo algo.
He leído lo nuevo en Entity Framework 6 (¡más cómo actualizar!) Y el código Primero inserte / actualice / elimine la especificación de asignación de procedimientos almacenados . Además, revisé el historial de commit de g6 de EF6 y encontré commit 1911dc7 , que es la primera parte de habilitar andamios de procedimientos almacenados en migraciones.
Creo que lo descubrí.
El código que genera el cuerpo del procedimiento insertado se encuentra en el método DmlFunctionSqlGenerator.GenerateInsert () en src/EntityFramework.SqlServer/SqlGen/DmlFunctionSqlGenerator.cs
.
Aquí está el código relevante:
// Part 1
sql.Append(
DmlSqlGenerator.GenerateInsertSql(
firstCommandTree,
_sqlGenerator,
out _,
generateReturningSql: false,
createParameters: false));
sql.AppendLine();
var firstTable
= (EntityType)((DbScanExpression)firstCommandTree.Target.Expression).Target.ElementType;
// Part 2
sql.Append(IntroduceRequiredLocalVariables(firstTable, firstCommandTree));
// Part 3
foreach (var commandTree in commandTrees.Skip(1))
{
sql.Append(
DmlSqlGenerator.GenerateInsertSql(
commandTree,
_sqlGenerator,
out _,
generateReturningSql: false,
createParameters: false));
sql.AppendLine();
}
var returningCommandTrees
= commandTrees
.Where(ct => ct.Returning != null)
.ToList();
// Part 4
if (returningCommandTrees.Any())
{
//...
La Parte 1 genera la INSERT
. La Parte 2 genera la línea DECLARE
y la primera instrucción SELECT
. Parte 4 genera la segunda instrucción SELECT
.
En la muestra de la Universidad Contoso, la clase de entidad Departamento es una clase de modelo simple. Parece que en tales casos, la colección commandTrees
pasada a DmlFunctionSqlGenerator.GenerateInsert () contiene solo un elemento DbInsertCommandTree
. Por lo tanto, el ciclo foreach
en la Parte 3 se omite de manera efectiva.
En otros escenarios, puede haber más de un elemento DbInsertCommandTree
en la colección commandTrees
, como cuando una clase de entidad amplía otra clase de entidad y se utiliza la estrategia de mapeo de herencia Tabla por tipo . Por ejemplo:
[Table("SpecialOrder")]
public class SpecialOrder
{
public int SpecialOrderId { get; set; }
public DateTime Date { get; set; }
public int Status { get; set; }
}
[Table("ExtraSpecialOrder")]
public class ExtraSpecialOrder : SpecialOrder
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ExtraSpecialOrderId { get; set; }
public string ExtraNotes { get; set; }
}
El procedimiento almacenado de inserción andamiaje para la entidad ExtraSpecialOrder es:
CreateStoredProcedure(
"dbo.ExtraSpecialOrder_Insert",
p => new
{
Date = p.DateTime(),
Status = p.Int(),
ExtraNotes = p.String(),
},
body:
@"INSERT [dbo].[SpecialOrder]([Date], [Status])
VALUES (@Date, @Status)
DECLARE @SpecialOrderId int
SELECT @SpecialOrderId = [SpecialOrderId]
FROM [dbo].[SpecialOrder]
WHERE @@ROWCOUNT > 0 AND [SpecialOrderId] = scope_identity()
INSERT [dbo].[ExtraSpecialOrder]([SpecialOrderId], [ExtraNotes])
VALUES (@SpecialOrderId, @ExtraNotes)
SELECT t0.[SpecialOrderId], t1.[ExtraSpecialOrderId]
FROM [dbo].[SpecialOrder] AS t0
JOIN [dbo].[ExtraSpecialOrder] AS t1 ON t1.[SpecialOrderId] = t0.[SpecialOrderId]
WHERE @@ROWCOUNT > 0 AND t0.[SpecialOrderId] = @SpecialOrderId"
);
Tenga en cuenta que se requieren dos INSERT
en este caso.
Por lo tanto, el procedimiento almacenado de inserción de scaffolded para la clase de entidad de departamento contiene dos instrucciones SELECT
porque de esta manera, la generación de SQL es extensible a los casos en que se genera más de una INSERT
. Aunque el resultado no está adaptado para el caso en el que solo hay una INSERT
, es posible editar a mano el cuerpo del procedimiento almacenado generado de modo que solo haya una instrucción SELECT
.
Desafortunadamente, el marco de entidad a menudo genera código que parece innecesariamente complejo. Parece preferir dividir las consultas en sentencias más pequeñas en lugar de manejarlo todo en uno, también debe tenerse en cuenta que su código no está diseñado para ser "legible por el ser humano", mientras que t-sql a menudo lo es. Esta pregunta tiene algunas buenas respuestas sobre el tema: ¿Por qué Entity Framework genera SQL lento sobreescrito?