-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Comments
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? |
We can assign both and later one will be unassigned. BTW I think there's similar story with #35133. |
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. |
@Appli4Ever Concurrent migrations and transactions are two different, unrelated, topics.
What exception? Do you see migration lock being taken? |
Message: Stack Trace:
I have to look into it further, maybe this is unrelated but I am getting the same exception as @DvdKhl.
Not seeing any migration locks. These should also not occur since im executing the migrations in a
(Executing the tests one-by-one works) |
One of the migrations contains an operation that cannot be executed in a transaction. It could be a custom operation like
Or an operation that impacts a memory-optimized table. In this case you cannot use an external transaction. |
@AndriySvyryd 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? |
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 Errorawait dbContext.Database.MigrateAsync(cancellationToken);
Migration Programpublic static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args);
Migration Service
Migrationusing 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");
});
}
} |
The workaround to avoid creating a transaction or using an ExecutionStrategy around the |
@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? |
As discussed in the team meeting, I filed a breaking changes docs issue for this: dotnet/EntityFramework.Docs#4896 |
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. |
Ah, nice, thanks. |
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.Stack traces:
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
The text was updated successfully, but these errors were encountered: