Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EF Core 9 migration behaviour change causes an exception with surrounding user transaction #35127

Closed
DvdKhl opened this issue Nov 16, 2024 · 13 comments
Labels
area-migrations closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported preview-1 regression type-bug
Milestone

Comments

@DvdKhl
Copy link

DvdKhl commented Nov 16, 2024

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

@DvdKhl DvdKhl changed the title EF Core 9 migration behaviour change causes exception an exception with surrounding transaction EF Core 9 migration behaviour change causes an exception with surrounding user transaction Nov 17, 2024
@ajcvickers
Copy link
Contributor

Note for team: this is assigned to @maumar because Migrations, but it looks directly related to @AndriySvyryd's work in 9. Should we assign issues like this to @AndriySvyryd?

@cincuranet
Copy link
Contributor

We can assign both and later one will be unassigned.

BTW I think there's similar story with #35133.

@Appli4Ever
Copy link

Is this a bug or intended behavior of "Protection against concurrent migrations" or "Warn when multiple migration operations can't be run inside a transaction" in this article: https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-9.0/whatsnew#migrations?

I am getting the same exception when executing integration test. But I am not using multiple DbContext.

My exception occurs when executing test simultaneously and all of them migrate the database before executing.

@cincuranet
Copy link
Contributor

@Appli4Ever Concurrent migrations and transactions are two different, unrelated, topics.

My exception occurs when executing test simultaneously and all of them migrate the database before executing.

What exception? Do you see migration lock being taken?

@Appli4Ever
Copy link

Appli4Ever commented Nov 18, 2024

@cincuranet

Message: 
System.NotSupportedException : User transaction is not supported with a TransactionSuppressed migrations or a retrying execution strategy.

Stack Trace: 

MigrationCommandExecutor.ExecuteNonQuery(IReadOnlyList`1 migrationCommands, IRelationalConnection connection, MigrationExecutionState executionState, Boolean commitTransaction, Nullable`1 isolationLevel)
IHistoryRepository.CreateIfNotExists()
<>c.<Migrate>b__20_0(DbContext _, Migrator migrator)
SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
Migrator.Migrate(String targetMigration)
SqlServerContext`1.InitContext(String connectionString, Boolean useTransaction, String key)
SqlServerContext`1.ctor(String name, String connectionString, Boolean useTransaction, Object constructorParameter, String migrationAssembly)
<>c__DisplayClass0_0`1.<AddContext>b__0(String name, String conn, Boolean useTransaction, Object constructParams)
TestContextFactory`1.Get(TestDbType dbType, String name, String conn, Boolean useTransaction, Object constructorParaemter)
DbContextBuilder`1.Build()

I have to look into it further, maybe this is unrelated but I am getting the same exception as @DvdKhl.

What exception? Do you see migration lock being taken?

Not seeing any migration locks. These should also not occur since im executing the migrations in a System.Threading.Lock:

private static readonly Lock Sync = new();

private static IDbContextTransaction sharedTransaction;
... 

lock (Sync)
{
    ctx.Database.UseTransaction(sharedTransaction.GetDbTransaction());

    ctx.Database.Migrate();

    ctx.Database.BeginTransaction(IsolationLevel.ReadCommitted);
}

(Executing the tests one-by-one works)

@AndriySvyryd
Copy link
Member

One of the migrations contains an operation that cannot be executed in a transaction. It could be a custom operation like

migrationBuilder.Sql("CREATE DATABASE TransactionSuppressed;", suppressTransaction: true)

Or an operation that impacts a memory-optimized table.

In this case you cannot use an external transaction.

@Dreamescaper
Copy link

Dreamescaper commented Nov 20, 2024

@AndriySvyryd
I have only 3 auto-generated migrations, looking like this:

1
            migrationBuilder.CreateTable(
                name: "EntityEvents",
                columns: table => new
                {
                    EventId = table.Column<Guid>(type: "uuid", nullable: false),
                    CreatedDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
                    EntityId = table.Column<string>(type: "text", nullable: false),
                    EventData = table.Column<string>(type: "jsonb", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_EntityEvents", x => x.EventId);
                });
2
            migrationBuilder.CreateTable(
                name: "EntitySnapshots",
                columns: table => new
                {
                    EntityId = table.Column<string>(type: "text", nullable: false),
                    DeliveryDate = table.Column<DateOnly>(type: "date", nullable: false),
                    LimitDate = table.Column<DateOnly>(type: "date", nullable: false),
                    Status = table.Column<int>(type: "integer", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_EntitySnapshots", x => x.EntityId);
                });

            migrationBuilder.CreateIndex(
                name: "IX_EntitySnapshots_DeliveryDate",
                table: "EntitySnapshots",
                column: "DeliveryDate");

            migrationBuilder.CreateIndex(
                name: "IX_EntitySnapshots_LimitDate",
                table: "EntitySnapshots",
                column: "LimitDate");

            migrationBuilder.CreateIndex(
                name: "IX_EntitySnapshots_Status",
                table: "EntitySnapshots",
                column: "Status");
3
            migrationBuilder.CreateIndex(
                name: "IX_EntityEvents_EntityId",
                table: "EntityEvents",
                column: "EntityId");

Could you suggest which one of those causes this issue?

@bar10dr
Copy link

bar10dr commented Dec 1, 2024

Single DbContext, same problem.

Any way to get around this beyond doing it manually or going back to .net 8?

Using the command line tools, it works fine using migrations add and database update
; dotnet ef migrations add InitialMigration -p "context project" -s "migration project"
; dotnet ef database update -p "context project" -s "migration project" --connection "Connection string to ContainerLifetime.Persistent db, replaced Aspire's SQL port with global port I got from docker"

Error await dbContext.Database.MigrateAsync(cancellationToken);

System.NotSupportedException: 'User transaction is not supported with a TransactionSuppressed migrations or a retrying execution strategy.'

Migration Program public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args);
    builder.AddServiceDefaults();
    builder.Services.AddHostedService<MigrationService>();

    builder.Services.AddOpenTelemetry()
        .WithTracing(tracing => tracing.AddSource(MigrationService.ActivitySourceName));

    builder.AddSqlServerDbContext<MsSQLContext>("cognitionDb");

    var app = builder.Build();

    app.Run();
}
Migration Service
public class MigrationService(IServiceProvider serviceProvider, IHostEnvironment hostEnvironment, IHostApplicationLifetime hostApplicationLifetime) : BackgroundService
{
    public const string ActivitySourceName = "Migrations";
    private static readonly ActivitySource s_activitySource = new(ActivitySourceName);

    protected override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        using var activity = s_activitySource.StartActivity("Migrating database", ActivityKind.Client);

        try
        {
            using var scope = serviceProvider.CreateScope();
            var dbContext = scope.ServiceProvider.GetRequiredService<MsSQLContext>();

            await EnsureDatabaseAsync(dbContext, cancellationToken);
            await RunMigrationAsync(dbContext, cancellationToken);
        }
        catch (Exception ex)
        {
            activity?.RecordException(ex);
            throw;
        }

        hostApplicationLifetime.StopApplication();
    }

    private static async Task EnsureDatabaseAsync(MsSQLContext dbContext, CancellationToken cancellationToken)
    {
        var dbCreator = dbContext.GetService<IRelationalDatabaseCreator>();

        var strategy = dbContext.Database.CreateExecutionStrategy();
        await strategy.ExecuteAsync(async () =>
        {
            if (!await dbCreator.ExistsAsync(cancellationToken))
            {
                await dbCreator.CreateAsync(cancellationToken);
            }
        });
    }

    private static async Task RunMigrationAsync(MsSQLContext dbContext, CancellationToken cancellationToken)
    {
        var strategy = dbContext.Database.CreateExecutionStrategy();
        await strategy.ExecuteAsync(async () =>
        {
            await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
            await dbContext.Database.MigrateAsync(cancellationToken);
            await transaction.CommitAsync(cancellationToken);
        });
    }
}
Migration
using System;
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace Cognition.Database3.Migrations
{
    /// <inheritdoc />
    public partial class InitialCreate : Migration
    {
        /// <inheritdoc />
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Roles",
                columns: table => new
                {
                    Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
                    Index = table.Column<int>(type: "int", nullable: false),
                    Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
                    RowVersion = table.Column<byte[]>(type: "rowversion", rowVersion: true, nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Roles", x => x.Id);
                });

            migrationBuilder.CreateTable(
                name: "Users",
                columns: table => new
                {
                    Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
                    DiscordId = table.Column<decimal>(type: "decimal(20,0)", nullable: false),
                    Username = table.Column<string>(type: "nvarchar(max)", nullable: false),
                    Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
                    Email = table.Column<string>(type: "nvarchar(max)", nullable: false),
                    CitizenId = table.Column<string>(type: "nvarchar(max)", nullable: false),
                    Password = table.Column<string>(type: "nvarchar(max)", nullable: false),
                    CreatedDate = table.Column<DateTime>(type: "datetime2", nullable: false),
                    LastModifiedDate = table.Column<DateTime>(type: "datetime2", nullable: false),
                    IsActive = table.Column<bool>(type: "bit", nullable: false),
                    VoteDelegateDiscordId = table.Column<decimal>(type: "decimal(20,0)", nullable: true),
                    RowVersion = table.Column<byte[]>(type: "rowversion", rowVersion: true, nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Users", x => x.Id);
                });

            migrationBuilder.CreateTable(
                name: "UserRoles",
                columns: table => new
                {
                    Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
                    RoleId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
                    DiscordId = table.Column<decimal>(type: "decimal(20,0)", nullable: false),
                    RowVersion = table.Column<byte[]>(type: "rowversion", rowVersion: true, nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_UserRoles", x => x.Id);
                    table.ForeignKey(
                        name: "FK_UserRoles_Roles_RoleId",
                        column: x => x.RoleId,
                        principalTable: "Roles",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateTable(
                name: "Organizations",
                columns: table => new
                {
                    Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
                    Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
                    ShortName = table.Column<string>(type: "nvarchar(max)", nullable: false),
                    DiscordLink = table.Column<string>(type: "nvarchar(max)", nullable: false),
                    Tags = table.Column<string>(type: "nvarchar(max)", nullable: false),
                    OwnerId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
                    Registered = table.Column<bool>(type: "bit", nullable: false),
                    RowVersion = table.Column<byte[]>(type: "rowversion", rowVersion: true, nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Organizations", x => x.Id);
                    table.ForeignKey(
                        name: "FK_Organizations_Users_OwnerId",
                        column: x => x.OwnerId,
                        principalTable: "Users",
                        principalColumn: "Id");
                });

            migrationBuilder.CreateTable(
                name: "OrganizationUser",
                columns: table => new
                {
                    MembersId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
                    OrganizationsId = table.Column<Guid>(type: "uniqueidentifier", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_OrganizationUser", x => new { x.MembersId, x.OrganizationsId });
                    table.ForeignKey(
                        name: "FK_OrganizationUser_Organizations_OrganizationsId",
                        column: x => x.OrganizationsId,
                        principalTable: "Organizations",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_OrganizationUser_Users_MembersId",
                        column: x => x.MembersId,
                        principalTable: "Users",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateIndex(
                name: "IX_Organizations_OwnerId",
                table: "Organizations",
                column: "OwnerId");

            migrationBuilder.CreateIndex(
                name: "IX_OrganizationUser_OrganizationsId",
                table: "OrganizationUser",
                column: "OrganizationsId");

            migrationBuilder.CreateIndex(
                name: "IX_UserRoles_RoleId",
                table: "UserRoles",
                column: "RoleId");
        }

        /// <inheritdoc />
        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "OrganizationUser");

            migrationBuilder.DropTable(
                name: "UserRoles");

            migrationBuilder.DropTable(
                name: "Organizations");

            migrationBuilder.DropTable(
                name: "Roles");

            migrationBuilder.DropTable(
                name: "Users");
        }
    }
}
Migration Snapshot
    [DbContext(typeof(MsSQLContext))]
    partial class MsSQLContextModelSnapshot : ModelSnapshot
    {
        protected override void BuildModel(ModelBuilder modelBuilder)
        {
            modelBuilder
                .HasAnnotation("ProductVersion", "9.0.0")
                .HasAnnotation("Relational:MaxIdentifierLength", 128);

            SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);

            modelBuilder.Entity("CogLib.Entities.Organization", b =>
                {
                    b.Property<Guid>("Id")
                        .ValueGeneratedOnAdd()
                        .HasColumnType("uniqueidentifier");

                    b.Property<string>("DiscordLink")
                        .IsRequired()
                        .HasColumnType("nvarchar(max)");

                    b.Property<string>("Name")
                        .IsRequired()
                        .HasColumnType("nvarchar(max)");

                    b.Property<Guid?>("OwnerId")
                        .HasColumnType("uniqueidentifier");

                    b.Property<bool>("Registered")
                        .HasColumnType("bit");

                    b.Property<byte[]>("RowVersion")
                        .IsConcurrencyToken()
                        .IsRequired()
                        .ValueGeneratedOnAddOrUpdate()
                        .HasColumnType("rowversion");

                    b.Property<string>("ShortName")
                        .IsRequired()
                        .HasColumnType("nvarchar(max)");

                    b.PrimitiveCollection<string>("Tags")
                        .IsRequired()
                        .HasColumnType("nvarchar(max)");

                    b.HasKey("Id");

                    b.HasIndex("OwnerId");

                    b.ToTable("Organizations");
                });

            modelBuilder.Entity("CogLib.Entities.Role", b =>
                {
                    b.Property<Guid>("Id")
                        .ValueGeneratedOnAdd()
                        .HasColumnType("uniqueidentifier");

                    b.Property<int>("Index")
                        .HasColumnType("int");

                    b.Property<string>("Name")
                        .IsRequired()
                        .HasColumnType("nvarchar(max)");

                    b.Property<byte[]>("RowVersion")
                        .IsConcurrencyToken()
                        .IsRequired()
                        .ValueGeneratedOnAddOrUpdate()
                        .HasColumnType("rowversion");

                    b.HasKey("Id");

                    b.ToTable("Roles");
                });

            modelBuilder.Entity("CogLib.Entities.User", b =>
                {
                    b.Property<Guid>("Id")
                        .ValueGeneratedOnAdd()
                        .HasColumnType("uniqueidentifier");

                    b.Property<string>("CitizenId")
                        .IsRequired()
                        .HasColumnType("nvarchar(max)");

                    b.Property<DateTime>("CreatedDate")
                        .HasColumnType("datetime2");

                    b.Property<decimal>("DiscordId")
                        .HasColumnType("decimal(20,0)");

                    b.Property<string>("Email")
                        .IsRequired()
                        .HasColumnType("nvarchar(max)");

                    b.Property<bool>("IsActive")
                        .HasColumnType("bit");

                    b.Property<DateTime>("LastModifiedDate")
                        .HasColumnType("datetime2");

                    b.Property<string>("Name")
                        .IsRequired()
                        .HasColumnType("nvarchar(max)");

                    b.Property<string>("Password")
                        .IsRequired()
                        .HasColumnType("nvarchar(max)");

                    b.Property<byte[]>("RowVersion")
                        .IsConcurrencyToken()
                        .IsRequired()
                        .ValueGeneratedOnAddOrUpdate()
                        .HasColumnType("rowversion");

                    b.Property<string>("Username")
                        .IsRequired()
                        .HasColumnType("nvarchar(max)");

                    b.Property<decimal?>("VoteDelegateDiscordId")
                        .HasColumnType("decimal(20,0)");

                    b.HasKey("Id");

                    b.ToTable("Users");
                });

            modelBuilder.Entity("CogLib.Entities.UserRole", b =>
                {
                    b.Property<Guid>("Id")
                        .ValueGeneratedOnAdd()
                        .HasColumnType("uniqueidentifier");

                    b.Property<decimal>("DiscordId")
                        .HasColumnType("decimal(20,0)");

                    b.Property<Guid>("RoleId")
                        .HasColumnType("uniqueidentifier");

                    b.Property<byte[]>("RowVersion")
                        .IsConcurrencyToken()
                        .IsRequired()
                        .ValueGeneratedOnAddOrUpdate()
                        .HasColumnType("rowversion");

                    b.HasKey("Id");

                    b.HasIndex("RoleId");

                    b.ToTable("UserRoles");
                });

            modelBuilder.Entity("OrganizationUser", b =>
                {
                    b.Property<Guid>("MembersId")
                        .HasColumnType("uniqueidentifier");

                    b.Property<Guid>("OrganizationsId")
                        .HasColumnType("uniqueidentifier");

                    b.HasKey("MembersId", "OrganizationsId");

                    b.HasIndex("OrganizationsId");

                    b.ToTable("OrganizationUser");
                });

            modelBuilder.Entity("CogLib.Entities.Organization", b =>
                {
                    b.HasOne("CogLib.Entities.User", "Owner")
                        .WithMany()
                        .HasForeignKey("OwnerId");

                    b.Navigation("Owner");
                });

            modelBuilder.Entity("CogLib.Entities.UserRole", b =>
                {
                    b.HasOne("CogLib.Entities.Role", "Role")
                        .WithMany("UserRoles")
                        .HasForeignKey("RoleId")
                        .OnDelete(DeleteBehavior.Cascade)
                        .IsRequired();

                    b.Navigation("Role");
                });

            modelBuilder.Entity("OrganizationUser", b =>
                {
                    b.HasOne("CogLib.Entities.User", null)
                        .WithMany()
                        .HasForeignKey("MembersId")
                        .OnDelete(DeleteBehavior.Cascade)
                        .IsRequired();

                    b.HasOne("CogLib.Entities.Organization", null)
                        .WithMany()
                        .HasForeignKey("OrganizationsId")
                        .OnDelete(DeleteBehavior.Cascade)
                        .IsRequired();
                });

            modelBuilder.Entity("CogLib.Entities.Role", b =>
                {
                    b.Navigation("UserRoles");
                });
        }
    }

@AndriySvyryd AndriySvyryd added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Dec 3, 2024
@AndriySvyryd AndriySvyryd added this to the 9.0.x milestone Dec 3, 2024
@AndriySvyryd
Copy link
Member

The workaround to avoid creating a transaction or using an ExecutionStrategy around the .Migrate() call. With EF 9.0 that should be unnecessary anyway.

@DvdKhl
Copy link
Author

DvdKhl commented Dec 4, 2024

With EF 9.0 that should be unnecessary anyway.

@AndriySvyryd is there any other way to ensure multiple contexts are either migrated or rolled back in case of an error, other than to place a transaction around them?

I looked at test cases in the commit, I'm not sure how I would apply this to my example above. Or does the change also fix this scenario?

@ajcvickers
Copy link
Contributor

As discussed in the team meeting, I filed a breaking changes docs issue for this: dotnet/EntityFramework.Docs#4896

@AndriySvyryd
Copy link
Member

AndriySvyryd commented Dec 4, 2024

With EF 9.0 that should be unnecessary anyway.

@AndriySvyryd is there any other way to ensure multiple contexts are either migrated or rolled back in case of an error, other than to place a transaction around them?

I looked at test cases in the commit, I'm not sure how I would apply this to my example above. Or does the change also fix this scenario?

The multiple contexts case should work again in 9.0.1. I meant that it's now unnecessary to wrap just a single Migrate call in a transaction.

@DvdKhl
Copy link
Author

DvdKhl commented Dec 4, 2024

Ah, nice, thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-migrations closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported preview-1 regression type-bug
Projects
None yet
Development

No branches or pull requests

8 participants