通过Tom Dykstra的Getting Started with Entity Framework 6 Code First using MVC 5教程,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!)和Code First Insert/Update/Delete Stored Procedure Mapping spec。另外,我查看了EF6 git的提交历史,发现了commit 1911dc7,这是在迁移中启用存储过程支架的第一部分。
最佳答案
我想我知道了。
在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大学示例中,部门实体类是简单的模型类。看来在这种情况下,传递给DmlFunctionSqlGenerator.GenerateInsert()的
commandTrees
集合仅包含一个DbInsertCommandTree
元素。因此,有效地跳过了第3部分中的foreach
循环。在其他情况下,
DbInsertCommandTree
集合中可以有多个commandTrees
元素,例如,当一个实体类扩展了另一个实体类并且使用了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
语句。