CUD 的存储过程:脚手架插入存储过程中的两个 SELECT 语句的目的是什么?
Stored Procedures for CUD: What is the purpose of the two SELECT statements in a scaffolded Insert stored procedure?
通读 Tom Dykstra 的 Getting Started with Entity Framework 6 Code First using MVC 5 tutorial, part 9 介绍了如何设置 EF6 以使用 CUD 的存储过程。
当通过包管理器控制台添加 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!) and the Code First Insert/Update/Delete Stored Procedure Mapping spec. Also, I looked through the EF6 git commit history and found commit 1911dc7,这是在迁移中启用存储过程脚手架的第一部分。
不幸的是,entity framework 确实经常生成看起来不必要的复杂代码。它似乎更喜欢将查询分解为更多、更小的语句,而不是将其全部处理成一个,同时请记住,它的代码并不是真正设计为 'human readable' 而手写的 t-sql 通常是。这个问题在这个问题上有一些很好的答案:
Why does Entity Framework generate slow overengineered SQL?
我想我明白了。
生成 Insert 存储过程主体的代码可在 src/EntityFramework.SqlServer/SqlGen/DmlFunctionSqlGenerator.cs
.
中的 DmlFunctionSqlGenerator.GenerateInsert() 方法中找到
相关代码如下:
// 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 大学示例中,Department 实体 class 是一个简单模型 class。在这种情况下,传递给 DmlFunctionSqlGenerator.GenerateInsert() 的 commandTrees
集合似乎只包含一个 DbInsertCommandTree
元素。因此,实际上跳过了第 3 部分中的 foreach
循环。
在其他情况下,commandTrees
集合中可以有多个 DbInsertCommandTree
元素,例如当一个实体 class 扩展另一个实体 class 并且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
语句。
因此,Department 实体 class 的脚手架插入存储过程包含两个 SELECT
语句,因为这样一来,SQL 生成可扩展到多个 INSERT
语句生成。虽然输出不适用于只有一个 INSERT
语句的情况,但可以手动编辑生成的存储过程主体,以便只有一个 SELECT
语句。
通读 Tom Dykstra 的 Getting Started with Entity Framework 6 Code First using MVC 5 tutorial, part 9 介绍了如何设置 EF6 以使用 CUD 的存储过程。
当通过包管理器控制台添加 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!) and the Code First Insert/Update/Delete Stored Procedure Mapping spec. Also, I looked through the EF6 git commit history and found commit 1911dc7,这是在迁移中启用存储过程脚手架的第一部分。
不幸的是,entity framework 确实经常生成看起来不必要的复杂代码。它似乎更喜欢将查询分解为更多、更小的语句,而不是将其全部处理成一个,同时请记住,它的代码并不是真正设计为 'human readable' 而手写的 t-sql 通常是。这个问题在这个问题上有一些很好的答案: Why does Entity Framework generate slow overengineered SQL?
我想我明白了。
生成 Insert 存储过程主体的代码可在 src/EntityFramework.SqlServer/SqlGen/DmlFunctionSqlGenerator.cs
.
相关代码如下:
// 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 大学示例中,Department 实体 class 是一个简单模型 class。在这种情况下,传递给 DmlFunctionSqlGenerator.GenerateInsert() 的 commandTrees
集合似乎只包含一个 DbInsertCommandTree
元素。因此,实际上跳过了第 3 部分中的 foreach
循环。
在其他情况下,commandTrees
集合中可以有多个 DbInsertCommandTree
元素,例如当一个实体 class 扩展另一个实体 class 并且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
语句。
因此,Department 实体 class 的脚手架插入存储过程包含两个 SELECT
语句,因为这样一来,SQL 生成可扩展到多个 INSERT
语句生成。虽然输出不适用于只有一个 INSERT
语句的情况,但可以手动编辑生成的存储过程主体,以便只有一个 SELECT
语句。