提问人:S. ten Brinke 提问时间:10/10/2023 最后编辑:marc_sS. ten Brinke 更新时间:10/10/2023 访问量:131
EF Core 事务顺序和外键约束
EF Core Transaction Order and Foreign Key Constraint
问:
EF Core 事务顺序和外键约束问题
描述
我在尝试在同一事务中同时添加实体和实体时遇到了 Entity Framework Core 的问题。在我的应用程序中,a 可以有多个 s,并且每个 s 必须只有一个 .User
Order
User
Order
Order
User
我希望这两个实体都以正确的顺序创建 - 首先是 ,然后是 ,以便可以成功解析外键关系。但是,EF Core 似乎尝试在 之前创建 ,从而导致外键约束冲突。User
Order
Order
User
重要信息
此问题是域驱动设计项目的简化版本的一部分。值得注意的是,我的实体没有导航属性,因为它们属于不同的“域”。我认为外键对于这种关系来说应该足够了。
Order
User
int UserId
我尝试在添加 之后调用,但在添加 之前,它可以工作。但是,这种方法会导致两个单独的事务,这对于我的用例来说是不可接受的。
SaveChanges()
User
DbContext
Order
法典
我提供了一个可运行的存储库,其中包含 、 、 和 中的所有相关代码。代码的核心如下所示: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 实体没有 User 的导航属性。 . .我相信外键 int UserId 应该足以建立这种关系。
不。如果没有导航属性,EF 就无法理解外键属性。如果没有导航属性,EF 将不知道如何对插入进行排序,您必须通过多次调用 SaveChanges 来强制执行此操作。
评论
Order 实体没有 User 的导航属性。 . .我相信外键 int UserId 应该足以建立这种关系。
没错,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'
这是具有自动生成密钥的新实体的一个基本问题 - 实际 Id 在调用 之前不可用。EF Core 实际上会在您调用 时将临时值关联到它,但从某个版本开始不会将其应用于实体,因此实体属性仍然具有值 0,即即使您这样做也是如此SaveChanges{Async}
Add
var user = new User("Test user");
dbContext.Users.Add(user);
// user.Id is still 0
现在,这是没有至少一个导航属性的问题之一。如果有,只需设置它(或添加到反向实体集合),EF 导航属性修复将处理 PK/FK 并纠正顺序。
由于您没有任何导航,因此您必须使用丑陋的解决方法。例如,若要让 EF 为实体分配临时 Id 值,并仍将其标记为临时 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();
丑陋吧?并且可能会在任何未来的 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);
评论
Order
User
User
Orders