解释Code First CRUD自动生成的SQL for Identity列

本文关键字:SQL for Identity 自动生成 Code First CRUD 解释 | 更新日期: 2023-09-27 18:21:40

Code first为以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

你能解释一下处理标识列的代码吗?此外,如果插入过程是从头开始手动编写的,会有不同的处理方式吗?

例如,如果我删除这个自动生成的代码,我会遇到以下错误之一:

程序。。。。应为参数"@ProductID",但未提供该参数

存储更新、插入或删除语句影响了意外的行数(0)。自加载实体以来,实体可能已被修改或删除。看见http://go.microsoft.com/fwlink/?LinkId=472540有关理解和处理乐观并发异常的信息。

在应用程序中,这是我调用过程的方式,该过程工作良好,直到我尝试处理第一个自动生成的SQL:代码

using (var db = new AppContext())
{
    var record = new ProductObj()
              {
                ProductName= this.ProductName,
                Date = DateTime.UtcNow
              };
    db.ProductDbSet.Add(record);
    db.SaveChanges();
}

解释Code First CRUD自动生成的SQL for Identity列

我想这里有两件事需要解释。

插入内容时为什么要使用SELECT语句

让我们首先看看实体框架的常规插入是什么样子的。通过";常规的";我指的是没有将CUD操作映射到存储过程的插入。正常模式为:

INSERT [dbo].[Product]([Name], ...)
VALUES (@0, ...)
SELECT [Id]
FROM [dbo].[Product]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()

因此,CCD_ 3之后是CCD_ 4。这是因为EF需要知道数据库分配给新Product的标识值,以便将其分配给实体对象的Product.ProductId属性并跟踪实体。如果出于某种原因,您决定在插入后立即进行更新,EF将能够生成类似UPDATE ... WHERE Id = @0的更新语句。

当存储过程处理插入时,存储过程应以类似于常规插入的方式返回新的Id值。它期望接收一个单列结果集,该结果集的列以标识列命名。它应该包含一行,即新标识值。

这就是为什么里面有一个SELECT语句,以及如果你删除它,EF会抱怨的原因。但是,你可能会问,EF真的需要7行代码才能获得指定的标识值吗?

为什么有这么多代码

老实说,我不得不在这里进行一些推测,因为据我所知,它并没有被记录下来。但让我们来看一个最低工作版本:

INSERT [dbo].[Products]([Name])
VALUES (@Name)
SELECT scope_identity() AS ProductId;

这就完成了任务。它甚至是许多教程(包括官方教程)中关于将CUD操作映射到存储过程的标准示例。

但数据库中可能会塞满触发器、约束、默认值等。在EF可能遇到的各种情况下,很难预测它们对返回的scope_identity()的影响。所以EF想要保证返回的值真的属于新插入的记录。事实上,一个记录已经被插入了第一位。这就是为什么它从Product表中添加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