3

通过Tom Dykstra的Getting Started with Entity Framework 6 Code First using MVC 5教程,part 9涵盖了如何设置EF6以使用CUD的存储过程。CUD的存储过程:脚手架插入存储过程中两个SELECT语句的用途是什么?

DepartmentSP迁移经由包管理控制台添加,以下CreateStoredProcedure()调用自动生成以创建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" 
); 

为什么有在自动生成的存储在两个SELECT语句程序?

我测试了以下简化:

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()" 
); 

...这似乎很好地工作,但我可能失去了一些东西。我已阅读What's New in Entity Framework 6 (Plus How To Upgrade!)Code First Insert/Update/Delete Stored Procedure Mapping spec。另外,我查看了EF6 git commit history,发现commit 1911dc7,这是迁移中启用存储过程脚手架的第一部分。

回答

2

我想我想通了。

src/EntityFramework.SqlServer/SqlGen/DmlFunctionSqlGenerator.cs的DmlFunctionSqlGenerator.GenerateInsert()方法中找到生成Insert存储过程主体的代码。

下面是相关代码:

// 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()) 
{ 
    //... 

第1部分产生INSERT声明。第2部分生成DECLARE行和第一个SELECT声明。第4部分生成第二个SELECT声明。

在Contoso University示例中,Department实体类是一个简单的模型类。看来在这种情况下,传递给DmlFunctionSqlGenerator.GenerateInsert()的commandTrees集合仅包含一个DbInsertCommandTree元素。因此,第3部分中的foreach循环被有效地跳过。

在其他情况下,commandTrees集合中可能会有多个DbInsertCommandTree元素,例如实体类扩展另一个实体类并使用Table per Type inheritance mapping strategy时。例如:

[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; } 
} 

用于ExtraSpecialOrder实体的脚手架插入存储过程是:

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" 
); 

注意到有两个INSERT语句在这种情况下必需的。

因此,部门实体类的脚手架插入存储过程包含两个SELECT语句,因为这样SQL生成可以扩展到生成多个INSERT语句的情况。虽然输出不适用于仅有一个INSERT语句的情况,但可以手动编辑生成的存储过程主体,以便只有一个SELECT语句。

0

不幸的是,实体框架通常会生成看起来不必要的复杂代码。它似乎更倾向于将查询分解为更多,更小的语句,而不是将其全部集中在一个中,同时也考虑到它的代码并非真的被设计为“人类可读的”,而手写的t-sql往往是这样。这个问题有关于这个问题的一些很好的答案: Why does Entity Framework generate slow overengineered SQL?