是否可以在将其保存到 entity framework 之前获取身份字段值

Is it possible to get Identity Field value before saving it in entity framework

我有一个客户和销售 table

CUSTOMER
--------------
Id (int auto increment)
Name

SALES
---------------
Id (int auto increment)
CustomerId (int)
OrderTotal (decimal)

有了 Guid,我可以做到这一点。

dbTransaction = dbContext.Database.BeginTransaction(isolationLevel);

var customer = new Customer()
{
  Id = Guid.NewGuid(),
  Name = "John Doe"
};

var sales = new Sales() 
{
  Id = Guid.NewGuid(),
  CustomerId = customer.Id,
  OrderTotal = 500
};

dbContext.SaveChanges();
dbTransaction.Commit();

如果我的主键是 int(DatabaseGeneratedOption.Identity),我该怎么做?

据我所知,在将更改保存到数据库之前,您无法获取 ID。将值插入数据库后,数据库会创建 ID。

在调用 .SaveChanges() 时添加到它,然后它才会将更改写入数据库,然后才会生成标识值。

你不能。进入 IDENTITY 列的 ID 是由数据库在插入时生成的,所有 "tricks" 绕过它并自行确定 ID 的方法都可能存在缺陷。

简短回答:如果您想在保存前生成 ID,请使用 GUID (UNIQUEIDENTIFIER) 或 SEQUENCE (如果您使用的是 SQL Server 2012 或更新版本)。

为什么您不应该自己计算下一个免费 ID:

甚至不要将 运行 诸如 context.Customers.Max(c => c.Id) + 1 之类的查询视为可行的解决方案,因为您始终有可能进行并发数据库访问:另一个进程或线程可能会保留一个新实体在您阅读下一个 "free" ID 之后但在存储您的实体之前,相同的 table。计算下一个空闲 ID 很容易发生冲突,除非您获取 ID、使用它执行某些操作以及使用该 ID 存储实体的整个操作是原子的。这可能需要在数据库中进行 table 锁定,这可能效率低下。

(即使您使用 SEQUENCEs 也存在同样的问题,这是 SQL Server 2012 中引入的新功能。) (;见答案结尾。)

可能的解决方案:

  1. 如果您需要在保存之前确定对象的 ID,请不要使用 IDENTITY 列中的 ID。 使用 GUID,因为你极不可能与这些发生任何冲突。

  2. 无需在两者之间做出选择:您实际上可以吃蛋糕!没有什么能阻止您拥有 两个 ID 列 ,一个是您在外部确定的(GUID),另一个是保留在数据库内部的(IDENTITY 列);请参阅 Mark Seemann 的博客文章 "CQS vs. server generated IDs" 以更详细地了解这个想法。以下是示例的总体思路:

    CREATE TABLE Foos
    (
        FooId INT IDENTITY NOT NULL PRIMARY KEY CLUSTERED,
     -- ^^^^^ assigned by the DBMS upon insertion. Mostly for DB-internal use.
        Id UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL UNIQUE DEFAULT (NEWID()),
     -- ^^ can be dictated and seen by the users of your DB. Mostly for DB-external use.
        …
    );
    
    CREATE TABLE FooBars
    (
        FooId INT NOT NULL FOREIGN KEY REFERENCES Foos (FooId),
     --   use DB-internal ID in foreign key constraints ^^^^^
        …
    );
    
    CREATE VIEW PublicFoos AS
    SELECT Id, … FROM Foos;
    --     ^^ publish the public ID for users of your DB
    

    (确保您遵守一些一致命名内部和 public ID 字段名称的约定。)

  3. SEQUENCEs,SQL Server 2012 中引入的一项功能,可以替代 IDENTITY 柱子。它们会自动增加,并且在使用 NEXT VALUE FOR SomeSequence. One of the use cases mentioned on MSDN 获得下一个免费 ID 时保证您的唯一编号是:

    Use sequences instead of identity columns in the following scenarios: […] The application requires a number before the insert into the table is made.

    一些注意事项:

    • 获取下一个序列值将需要额外往返数据库。

    • 与标识列一样,序列可以重置/重新播种,因此存在 理论上 ID 冲突的可能性。如果可以的话,最好从不重新植入标识列和序列。

    • 如果您使用 NEXT VALUE FOR 获取下一个自由序列值,但后来决定不使用它,这将导致您的 ID 中出现 "gap"。常规(非顺序)GUID 显然不会出现间隙,因为它们没有固有的顺序。

您可以通过小技巧获得该值。

在 SQL 服务器中创建一个类似这样的函数

CREATE FUNCTION fn_getIdentity(@tbl_name varchar(30))
AS
BEGIN

IF @tbl_name = 'Employee_tbl'
   RETURN IDENT_CURRENT('Employee_tbl')
ELSE IF @tbl_name = 'Department_tbl'
   RETURN IDENT_CURRENT('Department_tbl')
ELSE
   RETURN NULL
END

在您的 Entity framework 中创建一个实体来支持此功能并在您需要的任何地方使用它。

然后使用

var nextValue = dbContext.fn_getIdentity("Employee_tbl")

IDENT_CURRENT returns 您上次增加了标识列的值。这并不意味着 MAX + 1 就好像您之前的交易为此列生成了一个标识值但被回滚然后您将看到将生成的下一个值。

请注意,我没有正确检查语法,这个语法只是为了展示一个想法。

不过,如果使用 SQL Server 2012 或更高版本,我会选择 Stakx 提供的解决方案,即 SEQUENCE 否则创建一个 table 以通过在 table.

中永久生成的 ID 保留 ID 来实现 SEQUENCE 的功能