在将其保存在实体框架中之前是否可以获取Identity Field值

时间:2015-04-20 06:31:55

标签: c# sql-server entity-framework azure-sql-database

我有一个客户和销售表

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),我该怎么做?

3 个答案:

答案 0 :(得分:14)

你做不到。进入IDENTITY列的ID由数据库在插入时生成,并且所有"技巧"为了规避这一点并自己确定身份证可能存在缺陷。

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

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

不要考虑运行context.Customers.Max(c => c.Id) + 1之类的查询作为可行的解决方案,因为您可能始终有并发数据库访问:另一个进程或线程可能会持久保存新实体在您阅读了下一个" free"之后到同一张桌子。 ID,但在存储实体之前。计算下一个空闲ID将容易发生冲突,除非您获取ID,对其执行某些操作以及存储具有该ID的实体的整个操作都是原子的。这可能需要DB中的表锁,这可能效率低下。

(即使您使用SQL Server 2012中引入的新功能SEQUENCE,也存在同样的问题。)I was wrong;请参阅答案结束。)< / p>

可能的解决方案:

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

  2. 没有必要在另一个之间做出选择:你实际上可以吃蛋糕并吃掉它!没有什么可以阻止您拥有两个ID列,一个是您在外部确定的(GUID),另一个是在DB内部(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
    

    (确保遵守一些约定,以便始终命名内部和公共ID字段名称。)

  3. SEQUENCE s 是SQL Server 2012中引入的一项功能,可能是IDENTITY列的替代方法。它们会自动增加,并且在使用NEXT VALUE FOR SomeSequence获取下一个免费ID时,您可以获得唯一的号码。其中一个用例mentioned on MSDN是:

      

    在以下场景中使用序列而不是标识列:[...]应用程序在插入表格之前需要一个数字。

    一些警告:

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

    • 与标识列一样,序列可以重置/重新播种,因此存在ID冲突的理论可能性。如果您可以提供帮助,最好永远重新定位标识列和序列。

    • 如果您使用NEXT VALUE FOR获取下一个免费序列值,但随后决定不使用它,则会产生&#34;间隙&#34;在你的ID中。使用常规(非顺序)GUID显然不会发生差距,因为它们没有固有的排序。

答案 1 :(得分:1)

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

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

答案 2 :(得分:1)

你可以通过小黑客获得这个价值。

在SQL Server中创建一个类似这样的函数

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框架中创建一个实体以支持此功能,并在任何您想要的地方使用它。

然后使用

var nextValue = dbContext.fn_getIdentity("Employee_tbl")

IDENT_CURRENT返回标识列的最后一个递增值。这并不意味着MAX + 1,就好像您之前的事务为该列生成了一个标识值,但是回滚后,您将看到将生成的下一个值。

请注意,我没有正确检查语法,这个语法只是提出一个想法。

但是,如果使用SQL Server 2012或更高版本,我会使用Stakx提供的解决方案,即SEQUENCE 否则创建一个表来实现SEQUENCE的功能,方法是在表中永久生成ID后保留ID。