Issue with EF Core transaction order and foreign key constraint
Description
I'm facing an issue with Entity Framework Core while trying to add both a User
and an Order
entity in the same transaction. In my application, a User
can have multiple Order
s, and each Order
must have exactly one User
.
I expect both entities to be created in the correct order - first the User
, then the Order
, so the foreign key relationship can be resolved successfully. However, EF Core seems to attempt to create the Order
before the User
, leading to a foreign key constraint violation.
Important Information
- This issue is part of a simplified version of a domain-driven-design project. It's worth noting that my
Order
entity does not have a navigation property toUser
as they belong to different "domains." I believe that the foreign keyint UserId
should be sufficient for this relationship. - I have tried calling
SaveChanges()
after adding theUser
to theDbContext
but BEFORE adding theOrder
, and it works. However, this approach results in two separate transactions, which is not acceptable for my use-case.
Code
I have provided a runnable repository with all the relevant code in Worker.cs
, MyDbContext.cs
, User.cs
, and Order.cs
. The core of the code looks like this:
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();
}
Stack Traces
Here are the relevant parts of the stack trace:
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'.
I'm looking for advice on how to ensure that EF Core creates the User
entity before the Order
entity within the same transaction, considering my domain design and constraints.
Any help or suggestions would be greatly appreciated!
2条答案
按热度按时间goqiplq21#
Order entity does not have a navigation property to User . . . I believe that the foreign key int UserId should be sufficient for this relationship.
Nope. EF doesn't understand Foreign Key properties without Navigation Properties. Without a Navigation Property EF will not know how to order the inserts, and you would have to force that by calling SaveChanges multiple times.
5us2dqdw2#
Order entity does not have a navigation property to User . . . I believe that the foreign key int UserId should be sufficient for this relationship.
That's correct, EF Core supports that. And correctly reorders insert/update/delete operations. As soon as you provide the correct data.
Let take a look at your example:
Since you are using only Ids, ask yourself, what is the
user.Id
hereThe answer is 0. And this is what EF is trying to insert and is failing with FK constraint violation.
It can easily be seen if you insert at the end
which will show
This is a fundamental issue with new entities with auto generated keys - the actual Id is not available until you call
SaveChanges{Async}
. EF Core actually associates a temporary value to it when you callAdd
, but starting from some version does not apply it to the entity, so the entity property still has value 0, i.e. even you do thisNow, this is one of the problems of not having at least one navigation property. If you have so, you would simply set it (or add to the inverse entity collection) and EF navigation property fixup will take care of PKs/FKs and correct order.
Since you don't have any navigation, you have to use ugly workarounds. For instance, to let EF assign temporary Id value to the entity and still keep it marked as temporary, you could use the following:
Ugly right? And can break in any future EF version. The only good thing is that now the info shows
and
SaveChanges
runs successfully with the log something like this