为身份列解释代码优先 CRUD 自动生成 SQL
Explain Code First CRUD auto-generated SQL for Identity column
代码优先自动为 table 生成如下插入过程代码,其中 ProductID
作为主键(标识列)。
CREATE PROCEDURE [dbo].[InsertProducts]
@ProductName [nvarchar](max),
@Date [datetime],
AS
BEGIN
INSERT dbo.ProductsTable([ProductName], [Date])
VALUES (@ProductName, @Date)
-- identity stuff starts here
DECLARE @ProductID int
SELECT @ProductID = [ProductID]
FROM dbo.FIT_StorageLocations
WHERE @@ROWCOUNT > 0 AND [ProductID] = scope_identity()
SELECT t0.[ProductID]
FROM dbo.ProductsTable AS t0
WHERE @@ROWCOUNT > 0 AND t0.[ProductID] = @ProductID
END
GO
能否解释一下处理标识列的代码?另外,如果要从头开始手动编写插入过程,是否会有不同的处理方式?
例如,如果我要删除这个自动生成的代码,我会遇到以下错误之一:
Procedure ....expects parameter '@ProductID', which was not supplied
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.
在应用程序中,这就是我调用程序的方式,在我尝试弄乱首先自动生成的代码之前它运行良好 SQL:
using (var db = new AppContext())
{
var record = new ProductObj()
{
ProductName= this.ProductName,
Date = DateTime.UtcNow
};
db.ProductDbSet.Add(record);
db.SaveChanges();
}
我想这里有两件事需要解释。
为什么我插入内容时出现 SELECT
语句?
让我们先看看 Entity Framework 的常规插入是什么样子的。 “常规”是指不将 CUD 操作映射到存储过程的插入。正常模式是:
INSERT [dbo].[Product]([Name], ...)
VALUES (@0, ...)
SELECT [Id]
FROM [dbo].[Product]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()
因此 INSERT
后跟 SELECT
。这是因为 EF 需要知道数据库分配给新的 Product
的身份值,以便将其分配给实体对象的 Product.ProductId
属性 并跟踪实体。如果出于某种原因您决定在插入后立即进行更新,EF 将能够生成类似 UPDATE ... WHERE Id = @0
.
的更新语句
当存储过程处理插入时,sproc 应该return 新的 Id 值以类似于常规插入的方式。它期望接收一个单列结果集,其中该列以标识列命名。它应该包含一行,即新的标识值。
所以这就是为什么其中有一个 SELECT
语句,以及如果您删除它,EF 会抱怨的原因。但是,您可能会问,EF 真的需要 7 行代码来获取分配的标识值吗?
为什么这么多代码?
老实说,我不得不在这里推测一下,因为据我所知,它没有记录在案。但是让我们看一个最小的工作版本:
INSERT [dbo].[Products]([Name])
VALUES (@Name)
SELECT scope_identity() AS ProductId;
这样就可以了。它甚至是许多关于将 CUD 操作映射到存储过程的教程(包括官方教程)的标准示例。
但是数据库可能充满了触发器、约束、默认值等。在 EF 可能遇到的各种情况下,很难预测它们对 returned scope_identity()
的影响。所以EF要保证returned这个值真的属于新插入的记录。并且实际上首先插入了一条记录。这就是为什么它从 Product
table 添加 SELECT
,包括 @@ROWCOUNT
.
要实施这些保护措施,最小版本为:
INSERT [dbo].[Products]([Name])
VALUES (@Name)
SELECT t0.[ProductId]
FROM [dbo].[Products] AS t0
WHERE @@ROWCOUNT > 0 AND t0.[ProductId] = scope_identity()
与常规插入相同。
这是我对 EF 的了解。令我有点困惑的是,这个 SELECT
显然足以用于常规 INSERT
但不适用于存储过程。我无法解释为什么生成的代码中有两个 SELECT
。
代码优先自动为 table 生成如下插入过程代码,其中 ProductID
作为主键(标识列)。
CREATE PROCEDURE [dbo].[InsertProducts]
@ProductName [nvarchar](max),
@Date [datetime],
AS
BEGIN
INSERT dbo.ProductsTable([ProductName], [Date])
VALUES (@ProductName, @Date)
-- identity stuff starts here
DECLARE @ProductID int
SELECT @ProductID = [ProductID]
FROM dbo.FIT_StorageLocations
WHERE @@ROWCOUNT > 0 AND [ProductID] = scope_identity()
SELECT t0.[ProductID]
FROM dbo.ProductsTable AS t0
WHERE @@ROWCOUNT > 0 AND t0.[ProductID] = @ProductID
END
GO
能否解释一下处理标识列的代码?另外,如果要从头开始手动编写插入过程,是否会有不同的处理方式?
例如,如果我要删除这个自动生成的代码,我会遇到以下错误之一:
Procedure ....expects parameter '@ProductID', which was not supplied
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.
在应用程序中,这就是我调用程序的方式,在我尝试弄乱首先自动生成的代码之前它运行良好 SQL:
using (var db = new AppContext())
{
var record = new ProductObj()
{
ProductName= this.ProductName,
Date = DateTime.UtcNow
};
db.ProductDbSet.Add(record);
db.SaveChanges();
}
我想这里有两件事需要解释。
为什么我插入内容时出现 SELECT
语句?
让我们先看看 Entity Framework 的常规插入是什么样子的。 “常规”是指不将 CUD 操作映射到存储过程的插入。正常模式是:
INSERT [dbo].[Product]([Name], ...)
VALUES (@0, ...)
SELECT [Id]
FROM [dbo].[Product]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()
因此 INSERT
后跟 SELECT
。这是因为 EF 需要知道数据库分配给新的 Product
的身份值,以便将其分配给实体对象的 Product.ProductId
属性 并跟踪实体。如果出于某种原因您决定在插入后立即进行更新,EF 将能够生成类似 UPDATE ... WHERE Id = @0
.
当存储过程处理插入时,sproc 应该return 新的 Id 值以类似于常规插入的方式。它期望接收一个单列结果集,其中该列以标识列命名。它应该包含一行,即新的标识值。
所以这就是为什么其中有一个 SELECT
语句,以及如果您删除它,EF 会抱怨的原因。但是,您可能会问,EF 真的需要 7 行代码来获取分配的标识值吗?
为什么这么多代码?
老实说,我不得不在这里推测一下,因为据我所知,它没有记录在案。但是让我们看一个最小的工作版本:
INSERT [dbo].[Products]([Name])
VALUES (@Name)
SELECT scope_identity() AS ProductId;
这样就可以了。它甚至是许多关于将 CUD 操作映射到存储过程的教程(包括官方教程)的标准示例。
但是数据库可能充满了触发器、约束、默认值等。在 EF 可能遇到的各种情况下,很难预测它们对 returned scope_identity()
的影响。所以EF要保证returned这个值真的属于新插入的记录。并且实际上首先插入了一条记录。这就是为什么它从 Product
table 添加 SELECT
,包括 @@ROWCOUNT
.
要实施这些保护措施,最小版本为:
INSERT [dbo].[Products]([Name])
VALUES (@Name)
SELECT t0.[ProductId]
FROM [dbo].[Products] AS t0
WHERE @@ROWCOUNT > 0 AND t0.[ProductId] = scope_identity()
与常规插入相同。
这是我对 EF 的了解。令我有点困惑的是,这个 SELECT
显然足以用于常规 INSERT
但不适用于存储过程。我无法解释为什么生成的代码中有两个 SELECT
。