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会