EF Core 事务顺序和外键约束问题
描述
我在尝试添加 User
时遇到 Entity Framework Core 问题和一个Order
同一交易中的实体。在我的应用程序中,User
可以有多个Order
s,以及每个 Order
必须恰好有一个 User
.
我希望这两个实体都按照正确的顺序创建 - 首先是 User
,然后 Order
,这样外键关系就可以成功解析了。但是,EF Core 似乎尝试创建 Order
之前User
,导致违反外键约束。
重要信息
此问题是领域驱动设计项目的简化版本的一部分。值得注意的是我的
Order
实体没有到User
的导航属性因为它们属于不同的“域”。我相信外键int UserId
对于这种关系来说应该足够了。我已尝试调用
SaveChanges()
添加User
后到DbContext
但在添加Order
之前,并且它有效。但是,这种方法会导致两个单独的事务,这对于我的用例来说是 Not Acceptable 。
代码
我提供了runnable repository以及 Worker.cs
中的所有相关代码, MyDbContext.cs
, User.cs
,和Order.cs
。代码的核心如下所示:
using (var scope = _serviceScopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
var user = new User("Test user");
var order = new Order(user);
dbContext.Users.Add(user);
dbContext.Orders.Add(order);
await dbContext.SaveChangesAsync();
}
堆栈跟踪
以下是堆栈跟踪的相关部分:
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (40ms) [Parameters=[@p0='?' (DbType = Int32), @p1='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [Order] ([UserId])
OUTPUT INSERTED.[Id]
VALUES (@p0);
INSERT INTO [User] ([Name])
OUTPUT INSERTED.[Id]
VALUES (@p1);
fail: Microsoft.EntityFrameworkCore.Update[10000]
An exception occurred in the database while saving changes for context type 'Efentityorderworker.MyDbContext'.
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> Microsoft.Data.SqlClient.SqlException (0x80131904): The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Order_User_UserId". The conflict occurred in database "db-eforder-test", table "dbo.User", column 'Id'.
我正在寻求有关如何确保 EF Core 创建 User
的建议Order
之前的实体考虑到我的域设计和约束,同一交易中的实体。
任何帮助或建议将不胜感激!
最佳答案
Order entity does not have a navigation property to User . . . I believe that the foreign key int UserId should be sufficient for this relationship.
没错,EF Core 支持这一点。并正确地重新排序插入/更新/删除操作。一旦您提供正确的数据。
让我们看一下您的示例:
var user = new User("Test user");
var order = new Order(user);
dbContext.Users.Add(user);
dbContext.Orders.Add(order);
由于您只使用 Id,所以问问自己,这里的 user.Id
是什么
var order = new Order(user);
答案是 0。这就是 EF 尝试插入的内容,但因违反 FK 约束而失败。
如果在最后插入就很容易看到
var info = dbContext.ChangeTracker.ToDebugString();
将显示
Order {Id: -2147482647} Added
Id: -2147482647 PK Temporary
UserId: 0 FK
User {Id: -2147482647} Added
Id: -2147482647 PK Temporary
Name: 'Test user'
这是具有自动生成键的新实体的一个基本问题 - 在调用 SaveChanges{Async}
之前,实际 ID 不可用。当您调用 Add
时,EF Core 实际上会为其关联一个临时值,但从某些版本开始不会将其应用于实体,因此实体属性的值仍然为 0,即即使您这样做
var user = new User("Test user");
dbContext.Users.Add(user);
// user.Id is still 0
现在,这是没有至少一个导航属性的问题之一。如果有,您只需设置它(或添加到反向实体集合),EF 导航属性修复将处理 PK/FK 和正确的顺序。
由于您没有任何导航,因此您必须使用丑陋的解决方法。例如,要让 EF 为实体分配临时 Id 值并仍将其标记为临时,您可以使用以下命令:
var user = new User("Test user");
var userId = dbContext.Add(user).Property(e => e.Id);
// user.Id is 0, userId.CurrentValue is some temp value
userId.IsTemporary = userId.IsTemporary; // <--
// user.Id is now userId.CurrentValue
var order = new Order(user);
dbContext.Add(order);
var info = dbContext.ChangeTracker.ToDebugString();
丑陋吧?并且可以在任何 future 的 EF 版本中破解。唯一的好处是现在信息显示
Order {Id: -2147482647} Added
Id: -2147482647 PK Temporary
UserId: -2147482647 FK
User {Id: -2147482647} Added
Id: -2147482647 PK Temporary
Name: 'Test user'
和SaveChanges
成功运行,日志如下
Executed DbCommand (38ms) [Parameters=[@p0='Test user' (Nullable = false) (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
INSERT INTO [dbo].[User] ([Name])
OUTPUT INSERTED.[Id]
VALUES (@p0);
Executed DbCommand (5ms) [Parameters=[@p1='3'], CommandType='Text', CommandTimeout='30']
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
INSERT INTO [dbo].[Order] ([UserId])
OUTPUT INSERTED.[Id]
VALUES (@p1);
关于c# - EF Core 事务顺序和外键约束,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77265944/