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?