Open yxou opened 1 year ago
@yxou Distributed transactions are only supported on .NET 7 or later.
@roji There is some strange behavior here based on "Enlist=false" being included in the connection string--see my code below for playing around with SqlClient and EF Core in this space. The exception above happens when the connection string contains "Enlist=false" and TransactionManager.ImplicitDistributedTransactions = true;
has not been called.
using System.ComponentModel.DataAnnotations.Schema;
using System.Transactions;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
// TransactionManager.ImplicitDistributedTransactions = true;
using (var context = new TestDbContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
}
using (var tran = new TransactionScope())
{
using (var context1 = new TestDbContext())
{
context1.Add(new TestModel { Id = 1 });
context1.SaveChanges();
}
using (var context2 = new TestDbContext())
{
context2.Add(new TestModel { Id = 2 });
context2.SaveChanges();
}
}
// using (var tran = new TransactionScope())
// {
// using (var connection = new SqlConnection(@"Data Source=(LocalDb)\MSSQLLocalDB;Database=AllTogetherNow;Enlist=false"))
// {
// connection.Open();
// connection.EnlistTransaction(Transaction.Current);
// using var command = connection.CreateCommand();
// command.Parameters.Add(new SqlParameter("p0", 1));
// command.CommandText = @"SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; INSERT INTO [TestModels] ([Id]) VALUES (@p0);";
// command.ExecuteNonQuery();
// connection.Close();
// }
//
// using (var connection = new SqlConnection(@"Data Source=(LocalDb)\MSSQLLocalDB;Database=AllTogetherNow;Enlist=false"))
// {
// connection.Open();
// connection.EnlistTransaction(Transaction.Current);
// using var command = connection.CreateCommand();
// command.Parameters.Add(new SqlParameter("p0", 2));
// command.CommandText = @"SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; INSERT INTO [TestModels] ([Id]) VALUES (@p0);";
// command.ExecuteNonQuery();
// connection.Close();
// }
// }
using (var context3 = new TestDbContext())
{
Console.WriteLine(context3.TestModels.Count());
}
public class TestModel
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
}
public class TestDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(@"Data Source=(LocalDb)\MSSQLLocalDB;Database=AllTogetherNow;enlist=false")
//.UseSqlServer("server=localhost;database=testDb;User ID=test;Password=123456;enlist=false;TrustServerCertificate=true;");
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging();
}
public virtual DbSet<TestModel> TestModels { get; set; }
}
@ajcvickers I now is to rewrite the SqlServerConnection, let SupportsAmbientTransactions attributes according to Enlist assignment, temporarily not sure there is no other sequelae.
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
using Microsoft.EntityFrameworkCore.Storage;
internal class TestEFCoreSqlServerConnection : SqlServerConnection
{
public Seagull2EFCoreSqlServerConnection(RelationalConnectionDependencies dependencies) : base(dependencies)
{
}
protected override bool SupportsAmbientTransactions => new SqlConnectionStringBuilder(this.ConnectionString).Enlist;
}
I am not actually clear about the resolution here. I am facing the same issue upgrading from 2.14 to 6.0.9.
We initially encountered an error using System.Transactions
and being upgraded to distributed when multiple contexts are used.
After adding "Enlist=false" to the connection string, we got the same error above.
The problem here is that EF's RelationalConnection.Open() enlists the connection in the ambient Transaction if the provider has SupportsAmbientTransactions has been overridden to true - which it is in most cases. We don't check the connection string to see whether Enlist is true or not.
This is indeed problematic... We could look at the connection string in SupportsAmbientTransactions and return false if Enlist=false - we already do this to know whether MARS is enabled or not. However, this problem goes beyond just SQL Server, and this would force all providers to look at the connection string (and introduce a caching layer as for SQL Server MARS...).
In an ideal world, EF wouldn't be dealing with ambient transactions at all, that would purely be an affair of the lower-level ADO.NET provider. I took a (very) quick look at and I'm not quite sure why we track/enlist to ambient transactions (except maybe in order to throw NestedAmbientTransactionError?), I think @AndriySvyryd/@ajcvickers may know.
After migrating from ef6 to efcore, when there are multiple DbContexts in the TransactionScope (even if they share the same connection), it returns an error the second time SaveChanges, even if enlist is false.
Refer to the document after set up the Database. AutoTransactionBehavior = AutoTransactionBehavior. Never. It didn't work either.
ef6 does not have this problem.
Specific stack:
Code : Model
DbContext
Program
Example: EfcoreDemo.zip
EF Core version:7.0.5 Database provider: (e.g. Microsoft.EntityFrameworkCore.SqlServer:7.0.5) Target framework: (e.g. .NET 6.0) Operating system: IDE: (e.g. Visual Studio 2022 17.5)