c# - EF Core 事务顺序和外键约束

标签 c# sql .net sql-server entity-framework-core

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/

相关文章:

c# - SQLite:没有这样的表

mysql - 如何将我的查询更改为 MySql 中的单个命令?

c# - Linq 将 2 个列表合并为一个

asp.net - 尝试运行 vNext 示例应用程序时出现 kpm restore 问题

c# - process.start() 参数

c# - 如何创建类的实例并从 Bag 对象(如 session )设置属性

c# - 从 C# 过渡到 C++,好的引用资料?

c# - 如何通过Azure管理API打开/关闭Azure虚拟机(休息)

mysql - 通过固定列表对 MySQL 中的项目进行排序?

c# - 为什么 MS Access 2007 不允许行插入,但在下一次插入尝试时允许它?