stored procedures pour CUD: quel est le but des deux instructions SELECT dans une procédure stockée Insert échafaudée?

En commençant par le tutoriel de Tom Dykstra, Getting Started avec Entity Framework 6 Code First utilisant le tutoriel MVC 5 , la partie 9 explique comment configurer EF6 pour utiliser les procédures stockées pour CUD.

Lorsque la migration DepartmentSP est ajoutée via la console du gestionnaire de packages, l'appel CreateStoredProcedure () suivant est généré automatiquement pour créer la procédure stockée Department_Insert:

 CreateStoredProcedure( "dbo.Department_Insert", p => new { Name = p.Ssortingng(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" ); 

Pourquoi existe-t-il deux SELECT dans la procédure stockée générée automatiquement?

J'ai testé la simplification suivante:

 CreateStoredProcedure( "dbo.Department_Insert", p => new { Name = p.Ssortingng(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()" ); 

.. et cela semble bien fonctionner mais je pourrais manquer quelque chose.

J'ai lu ce qui est nouveau dans Entity Framework 6 (plus comment mettre à niveau!) Et la première spécification de layout de procédure stockée d'insertion / mise à jour / suppression de code . Aussi, j'ai regardé à travers l'histoire EF6 git commit et trouvé la validation 1911dc7 , qui est la première partie de l'activation de l'échafaudage de procédure stockée dans les migrations.

Je pense que je l'ai compris.

Le code qui génère le corps de procédure stockée Insert est trouvé dans la méthode DmlFunctionSqlGenerator.GenerateInsert () dans src/EntityFramework.SqlServer/SqlGen/DmlFunctionSqlGenerator.cs .

Voici le code pertinent:

 // 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 partie 1 génère l'instruction INSERT . La partie 2 génère la ligne DECLARE et la première SELECT . La partie 4 génère la deuxième SELECT .

Dans l'exemple Contoso University, la class d'entité Department est une class de model simple. Il semble que dans de tels cas, la collection commandTrees passée à DmlFunctionSqlGenerator.GenerateInsert () ne contienne qu'un seul élément DbInsertCommandTree . Par conséquent, la boucle foreach dans la partie 3 est effectivement ignorée.

Dans d'autres scénarios, il peut y avoir plus d'un élément DbInsertCommandTree dans la collection commandTrees , par exemple lorsqu'une class d'entité étend une autre class d'entité et que la stratégie de mappage d'inheritance Table par type est utilisée. Par exemple:

 [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 ssortingng ExtraNotes { get; set; } } 

La procédure stockée Insert enfoncée pour l'entité ExtraSpecialOrder est:

 CreateStoredProcedure( "dbo.ExtraSpecialOrder_Insert", p => new { Date = p.DateTime(), Status = p.Int(), ExtraNotes = p.Ssortingng(), }, 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" ); 

Notez que deux instructions INSERT sont requirejses dans ce cas.

Ainsi, la procédure stockée Insert enfoncée pour la class d'entité Department contient deux SELECT car, de cette manière, la génération SQL est extensible aux cas où plus d'une instruction INSERT est générée. Bien que la sortie ne soit pas adaptée au cas où il existe une seule instruction INSERT , il est possible de modifier manuellement le corps de la procédure stockée générée afin qu'il n'y ait qu'une seule SELECT .

Malheureusement, le framework d'entité génère souvent du code qui semble inutilement complexe. Il semble préférer briser les requêtes en plus, plus petites déclarations plutôt que de gérer tout en un, aussi à l'esprit que son code n'est pas vraiment conçu pour être lisible par l'homme alors que t-sql manuscrit serait souvent. Cette question a de bonnes réponses sur le sujet: Pourquoi Entity Framework génère-t-il un SQL trop lent?