dotnet / dotnet/EntityFramework.Docs

Document that the DbContext is not rolled back when SaveChanges fails

Open
#3,243 2 comments 0 reactions 0 assignees View on GitHub
area-save-changes
Dominant language
Mermaid
Stars
1.7k
Forks
2k
Avg merge
7d 23h
Merged PRs (30d)
16

Description

EF Core version: **5.0.5**
Database provider: **Microsoft.EntityFrameworkCore.SqlServer**
Target framework: **.NET 5**
Operating system: **Windows 10**
IDE: **Microsoft Visual Studio Enterprise 2019 Preview, Version 16.10.0 Preview 2.1**

Consider the below database context.

```
public class MyDbContext : DbContext
{
public DbSet Categories { get; set; }
public DbSet Products { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(@"Data Source=RAVANA-TPP15\MSSQLSERVER2019;Initial Catalog=EfCore5;" +
"Integrated Security=True;Persist Security Info=False;Pooling=False;Encrypt=False;TrustServerCertificate=False")
.EnableSensitiveDataLogging()
.LogTo(Console.WriteLine, LogLevel.Information);
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity(builder =>
{
builder
.HasMany(x => x.Products)
.WithOne()
.HasForeignKey(x => x.CategoryId);
});
}
}

public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection Products { get; set; }
}

public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int CategoryId { get; set; }
}
```

Now I am trying this.

```
class Program
{
static async Task Main(string[] args)
{
using var context = new MyDbContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();

Category category = null;

using IDbContextTransaction transaction = context.Database.BeginTransaction();
try
{
category = new Category() { Name = "Some Category" };
context.Categories.Add(category);
await context.SaveChangesAsync();

await transaction.CreateSavepointAsync("CategoryCreated");

var product1 = new Product { Name = "Some Product 1", CategoryId = category.Id };
context.Products.Add(product1);
await context.SaveChangesAsync();

await transaction.CreateSavepointAsync("Product1Created");

// This will throw an error, FOREIGN KEY constraint error because it's invalid CategoryId
var product2 = new Product { Name = "Some Product 2", CategoryId = 10 };
context.Products.Add(product2);
await context.SaveChangesAsync();

await transaction.CommitAsync();

}
catch (Exception)
{
await transaction.RollbackToSavepointAsync("Product1Created");

// Setting the correct CategoryId FK
var product2 = new Product { Name = "Some Product 2 with Correct Category Id", CategoryId = category.Id };
context.Products.Add(product2);
await context.SaveChangesAsync();

await transaction.CommitAsync();
}
}
}
```
In the catch here, I am rolling back to the **SavePoint** `Product1Created` and restarting adding the `product2` with Correct FK for CategoryId (**Note**: different product name as well).

And inside the catch, again this is getting thrown,

`The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Products_Categories_CategoryId". The conflict occurred in database "EfCore5", table "dbo.Categories", column 'Id'.`

Full stacktrace,
```
warn: 9/05/2021 15:46:29.247 CoreEventId.SensitiveDataLoggingEnabledWarning[10400] (Microsoft.EntityFrameworkCore.Infrastructure)
Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data; this mode should only be enabled during development.
info: 9/05/2021 15:46:29.338 CoreEventId.ContextInitialized[10403] (Microsoft.EntityFrameworkCore.Infrastructure)
Entity Framework Core 5.0.5 initialized 'MyDbContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: SensitiveDataLoggingEnabled
info: 9/05/2021 15:46:29.623 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (18ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT 1
info: 9/05/2021 15:46:29.672 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (2ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
IF SERVERPROPERTY('EngineEdition') <> 5
BEGIN
ALTER DATABASE [EfCore5] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
END;
info: 9/05/2021 15:46:29.687 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (15ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
DROP DATABASE [EfCore5];
info: 9/05/2021 15:46:30.069 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (213ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
CREATE DATABASE [EfCore5];
info: 9/05/2021 15:46:30.117 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (44ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
IF SERVERPROPERTY('EngineEdition') <> 5
BEGIN
ALTER DATABASE [EfCore5] SET READ_COMMITTED_SNAPSHOT ON;
END;
info: 9/05/2021 15:46:30.127 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT 1
info: 9/05/2021 15:46:30.205 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (7ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE [Categories] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NULL,
CONSTRAINT [PK_Categories] PRIMARY KEY ([Id])
);
info: 9/05/2021 15:46:30.209 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE [Products] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NULL,
[CategoryId] int NOT NULL,
CONSTRAINT [PK_Products] PRIMARY KEY ([Id]),
CONSTRAINT [FK_Products_Categories_CategoryId] FOREIGN KEY ([CategoryId]) REFERENCES [Categories] ([Id]) ON DELETE CASCADE
);
info: 9/05/2021 15:46:30.215 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE INDEX [IX_Products_CategoryId] ON [Products] ([CategoryId]);
info: 9/05/2021 15:46:30.340 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (17ms) [Parameters=[@p0='Some Category' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [Categories] ([Name])
VALUES (@p0);
SELECT [Id]
FROM [Categories]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
info: 9/05/2021 15:46:30.389 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (2ms) [Parameters=[@p0='1', @p1='Some Product 1' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [Products] ([CategoryId], [Name])
VALUES (@p0, @p1);
SELECT [Id]
FROM [Products]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
fail: 9/05/2021 15:47:15.426 RelationalEventId.CommandError[20102] (Microsoft.EntityFrameworkCore.Database.Command)
Failed executing DbCommand (91ms) [Parameters=[@p0='10', @p1='Some Product 2' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [Products] ([CategoryId], [Name])
VALUES (@p0, @p1);
SELECT [Id]
FROM [Products]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
fail: 9/05/2021 15:47:15.697 CoreEventId.SaveChangesFailed[10000] (Microsoft.EntityFrameworkCore.Update)
An exception occurred in the database while saving changes for context type 'EfCore5.MyDbContext'.
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
---> Microsoft.Data.SqlClient.SqlException (0x80131904): The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Products_Categories_CategoryId". The conflict occurred in database "EfCore5", table "dbo.Categories", column 'Id'.
The statement has been terminated.
at Microsoft.Data.SqlClient.SqlCommand.<>c.b__169_0(Task`1 result)
at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
at System.Threading.Tasks.Task.<>c.<.cctor>b__277_0(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location ---
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
ClientConnectionId:4f84cb19-7582-4a86-96b5-d83e4f82b020
Error Number:547,State:0,Class:16
--- End of inner exception stack trace ---
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(DbContext _, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
fail: 9/05/2021 15:47:34.861 RelationalEventId.CommandError[20102] (Microsoft.EntityFrameworkCore.Database.Command)
Failed executing DbCommand (86ms) [Parameters=[@p0='10', @p1='Some Product 2' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [Products] ([CategoryId], [Name])
VALUES (@p0, @p1);
SELECT [Id]
FROM [Products]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
fail: 9/05/2021 15:47:35.095 CoreEventId.SaveChangesFailed[10000] (Microsoft.EntityFrameworkCore.Update)
An exception occurred in the database while saving changes for context type 'EfCore5.MyDbContext'.
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
---> Microsoft.Data.SqlClient.SqlException (0x80131904): The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Products_Categories_CategoryId". The conflict occurred in database "EfCore5", table "dbo.Categories", column 'Id'.
The statement has been terminated.
at Microsoft.Data.SqlClient.SqlCommand.<>c.b__169_0(Task`1 result)
at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
at System.Threading.Tasks.Task.<>c.<.cctor>b__277_0(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location ---
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
ClientConnectionId:4f84cb19-7582-4a86-96b5-d83e4f82b020
Error Number:547,State:0,Class:16
--- End of inner exception stack trace ---
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(DbContext _, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
```

**When I check the log, found an interesting thing. On the catch, when I am inserting the correct `product2` it's still using the previous `product2` values. (I changed the `product2` name on the insert inside catch to a different one to make it clear)**
![image](https://user-images.githubusercontent.com/5653381/117560021-3d251200-b0de-11eb-9563-127b54666bfb.png)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.