-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Description
We use multiple DbContexts within our application, which need to be either all fully migrated or none of them in case of an exception. To achieve this we have surrounded everything in a transaction during the migration.
With EF8 this worked well, but with EF9 we're getting the exception System.NotSupportedException: 'User transaction is not supported with a TransactionSuppressed migrations or a retrying execution strategy.
'
We found the related(?) issue #35096 but I can't apply the solution in it since it doesn't fail at a .Sql()
call.
And we're at least not changing any retrying strategy and I'm not sure how to disable it if it is enabled.
Minimal repo:
Add the Microsoft.EntityFrameworkCore.SqlServer
package and notice that this code works in EF8 but throws the exception above in EF9.
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using System.ComponentModel.DataAnnotations;
using var conn = new SqlConnection();
conn.ConnectionString = $"Data Source=localhost;Initial Catalog=Main;User ID=username;Password=userpassword;Trust Server Certificate=True";
await conn.OpenAsync();
using (var tran = conn.BeginTransaction()) {
using (var aContext = new AContext()) {
aContext.Database.SetDbConnection(conn);
using var aCtxTran = await aContext.Database.UseTransactionAsync(tran);
await aContext.Database.MigrateAsync();
}
using (var bContext = new BContext()) {
bContext.Database.SetDbConnection(conn);
using var bCtxTran = await bContext.Database.UseTransactionAsync(tran);
await bContext.Database.MigrateAsync();
}
await tran.CommitAsync();
}
public class AContext : DbContext { public DbSet<A> AEntites { get; set; } = null!; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseSqlServer(); }
public class A { [Key] public int Id { get; set; } }
public class BContext : DbContext { public DbSet<B> BEntites { get; set; } = null!; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseSqlServer(); }
public class B { [Key] public int Id { get; set; } }
#region Migrations
#nullable disable
namespace MultiEFContextMigration.Migrations {
/// <inheritdoc />
public partial class AMigration : Migration {
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) {
migrationBuilder.CreateTable(
name: "AEntites",
columns: table => new {
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1")
},
constraints: table => {
table.PrimaryKey("PK_AEntites", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder) {
migrationBuilder.DropTable(
name: "AEntites");
}
}
}
#nullable disable
namespace MultiEFContextMigration.Migrations {
[DbContext(typeof(AContext))]
[Migration("20241116225124_AMigration")]
partial class AMigration {
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder) {
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("A", b => {
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.HasKey("Id");
b.ToTable("AEntites");
});
#pragma warning restore 612, 618
}
}
}
#nullable disable
namespace MultiEFContextMigration.Migrations {
[DbContext(typeof(AContext))]
partial class AContextModelSnapshot : ModelSnapshot {
protected override void BuildModel(ModelBuilder modelBuilder) {
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("A", b => {
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.HasKey("Id");
b.ToTable("AEntites");
});
#pragma warning restore 612, 618
}
}
}
#nullable disable
namespace MultiEFContextMigration.Migrations.B {
/// <inheritdoc />
public partial class BMigration : Migration {
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) {
migrationBuilder.CreateTable(
name: "BEntites",
columns: table => new {
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1")
},
constraints: table => {
table.PrimaryKey("PK_BEntites", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder) {
migrationBuilder.DropTable(
name: "BEntites");
}
}
}
#nullable disable
namespace MultiEFContextMigration.Migrations.B {
[DbContext(typeof(BContext))]
[Migration("20241116225139_BMigration")]
partial class BMigration {
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder) {
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("B", b => {
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.HasKey("Id");
b.ToTable("BEntites");
});
#pragma warning restore 612, 618
}
}
}
#nullable disable
namespace MultiEFContextMigration.Migrations.B {
[DbContext(typeof(BContext))]
partial class BContextModelSnapshot : ModelSnapshot {
protected override void BuildModel(ModelBuilder modelBuilder) {
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("B", b => {
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.HasKey("Id");
b.ToTable("BEntites");
});
#pragma warning restore 612, 618
}
}
}
#endregion
Stack traces:
Unhandled exception. System.NotSupportedException: User transaction is not supported with a TransactionSuppressed migrations or a retrying execution strategy.
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationCommandExecutor.ExecuteNonQueryAsync(IReadOnlyList`1 migrationCommands, IRelationalConnection connection, MigrationExecutionState executionState, Boolean commitTransaction, Nullable`1 isolationLevel, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Migrations.HistoryRepository.Microsoft.EntityFrameworkCore.Migrations.IHistoryRepository.CreateIfNotExistsAsync(CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.<>c.<<MigrateAsync>b__22_0>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.<>c.<<MigrateAsync>b__22_0>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.MigrateAsync(String targetMigration, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.MigrateAsync(String targetMigration, CancellationToken cancellationToken)
at Program.<Main>$(String[] args) in E:\source\MultiEFContextMigration\MultiEFContextMigration\Program.cs:line 16
at Program.<Main>(String[] args)
Include provider and version information
EF Core version: 9.0.0
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 9.0
Operating system: Win11 23H2 (OS Build 22631.4317)
IDE: Visual Studio 2022 17.12.0