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

时间:2015-11-12 22:13:34

标签: c# sql entity-framework ef-code-first

Code-first auto为以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();
}

1 个答案:

答案 0 :(得分:1)

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

为什么在插入内容时SELECT语句?

让我们首先看看实体框架的常规插入是什么样的。 “常规”是指不将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应该以看起来像常规插入的方式返回新的Id值。它期望接收一列结果集,其列以标识列命名。它应该包含一行,即新的标识值。

这就是为什么那里有一个SELECT语句,以及为什么EF会在你删除它时抱怨。但是,您可能会问,EF确实需要7行代码来获取指定的标识值吗?

为什么这么多代码?

老实说,我必须在这里推测一下,因为据我所知,它没有记录。但是让我们看一下最小的工作版本:

INSERT [dbo].[Products]([Name])
VALUES (@Name)

SELECT scope_identity() AS ProductId;

这可以胜任。它甚至是许多教程的标准示例,包括官方教程,将CUD操作映射到存储过程。

但是数据库可以填充触发器,约束,默认值等。在EF可能遇到的各种情况下,很难预测它们对返回的scope_identity()的影响。因此,EF希望保证返回的值确实属于新插入的记录。并且实际上已经插入了记录。这就是它从SELECT表中添加Product的原因,包括@@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