Clear transaction before TransactionCommittedAsync call
- Dominant language
- C#
- Stars
- 14.8k
- Forks
- 3.4k
- PR merge metrics
- PR metrics pending
Description
### Bug description
Executing code which triggers a database read/write inside a DbTransactionInterceptor.TransactionCommitted(Async)
causes a InvalidOperationException with the message "The transaction object is not associated with the same connection object as this command".
This seems to be due to the fact that inside [RelationalTransaction.Commit(Async)](https://github.com/dotnet/efcore/blob/fc65c2d452ff7812bc3845a7d0facfd7062553f1/src/EFCore.Relational/Storage/RelationalTransaction.cs#L180) the transaction is only cleared after the interceptors have been called.
When executing a dbcommand within this function efcore seems to still try and use the transaction for the command which in turn fails for the database because from its view the transaction is already committed.
I can see that this is probably intended behavior, because calling this interceptor before the transaction is disposed would be beneficial for usecases in need of inspecting it.
Is there any workaround or other option to execute my logic after a transaction, or am i "missusing a feature" that
was only intended for small logentries etc
### Your code
```csharp
using System.Data.Common;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
namespace EfPlayground;
public class Parent
{
public int Id { get; set; }
public virtual ICollection Children { get; set; } = new List();
}
public class Child
{
public int Id { get; set; }
public int ParentId { get; set; }
}
public class TestContext : DbContext
{
public DbSet Parents => Set();
public DbSet Children => Set();
public TestContext(DbContextOptions options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity()
.HasMany(p => p.Children)
.WithOne()
.HasForeignKey(c => c.ParentId);
}
}
public sealed class QueryOnCommitInterceptor : DbTransactionInterceptor
{
public TestContext Context { get; set; } = null!;
private Parent? _trackedParent;
public void Capture(Parent parent)
=> _trackedParent = parent;
public override void TransactionCommitted(DbTransaction transaction, TransactionEndEventData eventData)
{
// This triggers a query via lazy loading
// -> throws InvalidOperationException (SqliteCommand.ExecuteReader)
var count = _trackedParent?.Children.Count;
Console.WriteLine($"Children count: {count}");
}
}
public class Bugreport
{
public static void main()
{
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
var queryOnCommitInterceptor = new QueryOnCommitInterceptor();
var optionsBuilder = new DbContextOptionsBuilder()
.UseLazyLoadingProxies()
.AddInterceptors(queryOnCommitInterceptor)
.UseSqlite(connection);
using var context = new TestContext(optionsBuilder.Options);
context.Database.EnsureCreated();
queryOnCommitInterceptor.Context = context;
context.Parents.Add(new Parent { Id = 1 });
context.Children.Add(new Child { Id = 1, ParentId = 1 });
context.SaveChanges();
context.ChangeTracker.Clear();
using var tx = context.Database.BeginTransaction();
var parent = context.Parents.First();
queryOnCommitInterceptor.Capture(parent);
context.SaveChanges();
// Exception is thrown during Commit()
tx.Commit();
}
}
```
### Stack traces
```text
The transaction object is not associated with the same connection object as this command.
--- EXCEPTION #1/1 [InvalidOperationException]
Message = “The transaction object is not associated with the same connection object as this command.”
ExceptionPath = Root
ClassName = System.InvalidOperationException
HResult = 80131509
Source = Microsoft.Data.Sqlite
StackTraceString = “
at Microsoft.Data.Sqlite.SqliteCommand.ExecuteReader(CommandBehavior behavior)
at Microsoft.Data.Sqlite.SqliteCommand.ExecuteDbDataReader(CommandBehavior behavior)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReader(RelationalCommandParameterObject parameterObject)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.InitializeReader(Enumerator enumerator)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.<>c.b__21_0(DbContext _, Enumerator enumerator)
at Microsoft.EntityFrameworkCore.Storage.NonRetryingExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.Load[TSource](IQueryable`1 source)
at Microsoft.EntityFrameworkCore.Internal.EntityFinder`1.Load(INavigation navigation, InternalEntityEntry entry, LoadOptions options)
at Microsoft.EntityFrameworkCore.Internal.EntityFinderCollectionLoaderAdapter.Load(InternalEntityEntry entry, LoadOptions options)
at Microsoft.EntityFrameworkCore.ChangeTracking.CollectionEntry.Load(LoadOptions options)
at Microsoft.EntityFrameworkCore.Infrastructure.Internal.LazyLoader.Load(Object entity, String navigationName)
at Microsoft.EntityFrameworkCore.Proxies.Internal.LazyLoadingInterceptor.Intercept(IInvocation invocation)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.ParentProxy.get_Children()
at EfPlayground.QueryOnCommitInterceptor.TransactionCommitted(DbTransaction transaction, TransactionEndEventData eventData) in C:\Users\samuel.reithmeir\RiderProjects\EfPlayground\EfPlayground\Bugreport.cs:line 52
at Microsoft.EntityFrameworkCore.Diagnostics.RelationalLoggerExtensions.TransactionCommitted(IDiagnosticsLogger`1 diagnostics, IRelationalConnection connection, DbTransaction transaction, Guid transactionId, DateTimeOffset startTime, TimeSpan duration)
at Microsoft.EntityFrameworkCore.Storage.RelationalTransaction.Commit()
at EfPlayground.Bugreport.main() in C:\Users\samuel.reithmeir\RiderProjects\EfPlayground\EfPlayground\Bugreport.cs:line 87
at InvokeStub_Bugreport.main(Object, Object, IntPtr*)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
”
Error: JetBrains Launcher could not run. The transaction object is not associated with the same connection object as this command.
```
### Verbose output
```text
```
### EF Core version
8.0.10
### Database provider
Microsoft.EntityFrameworkCore.Sqlite
### Target framework
.NET 8.0
### Operating system
Windows 11
### IDE
Rider
Contributor guide
Assessment
This issue has not been assessed yet.