EF Core 事务顺序和外键约束

EF Core Transaction Order and Foreign Key Constraint

提问人:S. ten Brinke 提问时间:10/10/2023 最后编辑:marc_sS. ten Brinke 更新时间:10/10/2023 访问量:131

问:

EF Core 事务顺序和外键约束问题

描述

我在尝试在同一事务中同时添加实体和实体时遇到了 Entity Framework Core 的问题。在我的应用程序中,a 可以有多个 s,并且每个 s 必须只有一个 .UserOrderUserOrderOrderUser

我希望这两个实体都以正确的顺序创建 - 首先是 ,然后是 ,以便可以成功解析外键关系。但是,EF Core 似乎尝试在 之前创建 ,从而导致外键约束冲突。UserOrderOrderUser

重要信息

  • 此问题是域驱动设计项目的简化版本的一部分。值得注意的是,我的实体没有导航属性,因为它们属于不同的“域”。我认为外键对于这种关系来说应该足够了。OrderUserint UserId

  • 我尝试在添加 之后调用,但在添加 之前,它可以工作。但是,这种方法会导致两个单独的事务,这对于我的用例来说是不可接受的。SaveChanges()UserDbContextOrder

法典

我提供了一个可运行的存储库,其中包含 、 、 和 中的所有相关代码。代码的核心如下所示:Worker.csMyDbContext.csUser.csOrder.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 在同一事务中的实体之前创建实体的建议。UserOrder

任何帮助或建议将不胜感激!

C# .NET SQL 服务器 实体框架核心

评论

2赞 Panagiotis Kanavos 10/10/2023
EF 将按正确的顺序插入项,前提是实体和配置正确。但是,您既没有发布类,也没有发布 DbContext 配置。SQL 代码表明 User 和 Order 之间没有关系。在问题本身中发布相关代码
3赞 Panagiotis Kanavos 10/10/2023
如果有一个属性和/或一个,事情就会起作用。在尝试设置迁移和多文件类型配置之前,请尝试创建一个有效的简单示例。OrderUserUserOrders
2赞 Panagiotis Kanavos 10/10/2023
查看 EF Core 入门教程。它比您的示例更复杂,但不需要显式配置
0赞 S. ten Brinke 10/10/2023
@PanagiotisKanavos我确实在可运行的存储库链接中发布了所有类和配置:)github.com/sander1095/Efentityorderworker
1赞 Panagiotis Kanavos 10/10/2023
将它们发布这个问题中,而不是指向可能在 10 分钟内消失的内容的链接。没有实际的代码,这个问题就毫无意义。顺便说一句,我已经解释了缺少代码的问题。将配置拆分为多个文件也会使修复此类问题变得更加困难。

答:

2赞 David Browne - Microsoft 10/10/2023 #1

Order 实体没有 User 的导航属性。 . .我相信外键 int UserId 应该足以建立这种关系。

不。如果没有导航属性,EF 就无法理解外键属性。如果没有导航属性,EF 将不知道如何对插入进行排序,您必须通过多次调用 SaveChanges 来强制执行此操作。

评论

0赞 S. ten Brinke 10/10/2023
嘿大卫!感谢您的回复。根据我的经验,没有导航属性的外键属性运行良好,并且 Fluent API 支持很长时间,没有问题。但是,这是它第一次引起问题。您对支持您的主张的一些官方文档/资源一无所知吗?这样我就可以更深入地挖掘。
0赞 Panagiotis Kanavos 10/10/2023
@S.tenBrinke,你误解了当时旧项目中发生的事情。在(仍然缺少)代码中,DbContext 中没有定义任何关系。类中有一半的关系可能会被加载,也可能不会被加载。EF 不会在运行时检查数据库,它只使用代码本身中配置的约定和关系。
2赞 Ivan Stoev 10/10/2023 #2

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);