C# 在实体框架中保存标识字段之前,是否可以获取标识字段值

C# 在实体框架中保存标识字段之前,是否可以获取标识字段值,c#,sql-server,entity-framework,azure-sql-database,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 =

我有一张客户和销售表

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在插入时由数据库生成,所有绕过该列并自行确定ID的“技巧”可能都有缺陷

简短回答:如果您希望在保存之前在生成ID时有发言权,请使用GUID(
UNIQUEIDENTIFIER
),或
序列(如果您使用的是SQL Server 2012或更新版本)

为什么不自己计算下一个免费ID: <>不要考虑运行一个查询,如“代码>上下文.Cube。马克斯(C= >C.ID)+ 1 < /代码>作为一个可行的解决方案,因为总是有可能并发数据库访问:在读取下一个“自由”ID之后,但在存储实体之前,另一个进程或线程可能会将新的实体持久化到同一个表中。计算下一个空闲ID很容易发生冲突,除非获取ID、使用ID做一些事情以及使用该ID存储实体的整个操作都是原子操作。这可能需要在数据库中使用表锁,这可能效率低下

(即使使用SQL Server 2012中引入的新功能
SEQUENCE
s,也存在同样的问题。)(请参阅答案的末尾。)

可能的解决方案:
  • 如果在保存对象之前需要确定其ID,则不要使用
    IDENTITY
    列中的ID使用GUID,因为您极不可能与这些GUID发生冲突

  • 没有必要在两者之间做出选择:你实际上可以吃蛋糕!没有什么能阻止您拥有两个ID列,一个是您在外部确定的(GUID),另一个是DB内部确定的(ID列);请参阅马克·希曼(MarkSeemann)的博客文章,更详细地了解这个想法。以下是通过示例得出的总体思路:

    创建表Foos
    (
    FooId INT IDENTITY非空主键群集,
    --^^^^^^在插入时由DBMS分配。主要用于DB内部使用。
    Id UNIQUEIDENTIFIER ROWGUIDCOL非空唯一默认值(NEWID()),
    --^^^可由数据库用户指定和查看。主要用于数据库外部使用。
    …
    );
    创建表FooBars
    (
    FooId INT NOT NULL外键引用Foos(FooId),
    --在外键约束中使用DB内部ID^^^^^
    …
    );
    创建视图PublicFoos作为
    从Foos中选择Id;
    --^^发布数据库用户的公共ID
    
    (请确保遵守一些一致命名内部和公共ID字段名称的约定。)

  • 序列
    s
    是SQL Server 2012中引入的一项功能,是拥有
    标识
    列的一种可能的替代方法。它们会自动增加,并保证您在使用获取下一个免费ID时有一个唯一的号码。其中一个用例是:

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

    一些警告:

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

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

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


    你可以通过一个小技巧得到这个值

    在SQLServer中创建一个函数,如下所示

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

    然后使用

    var nextValue = dbContext.fn_getIdentity("Employee_tbl")
    
    返回标识列的上次递增值。这并不意味着MAX+1,就好像您的上一个事务为此列生成了一个标识值,但被回滚了,那么您将看到将生成的下一个值

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

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

    我使用工作单元,因此它将在处理/保存所有数据后提交。即使数据尚未提交,我也会获取该值吗?@Reynaldi:-在调用SaveChanges()之前无法获取Id。如果您使用的是SQL Server 2012,则可以使用序列类型的功能来代替标识列,因此您无需自行实现。@Rahultripath:如果可能有多个进程/线程同时将内容存储到数据库中,则从序列中获取下一个ID将无法可靠地工作。出于同样的原因,运行像
    dbContext.Customers.Max(c=>c.Id)+1这样的查询是个坏主意。理想情况下,获取ID、处理ID并将新实体存储到DB应该是一个原子操作(在进行过程中,其他任何操作都不能干预)。@Reynaldi这里不是100%确定,但我相信数据库会在插入时锁定表。所以这个过程#2会