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

EF9 Migration Performance Regression #35716

Open
cmatheso opened this issue Mar 3, 2025 · 0 comments
Open

EF9 Migration Performance Regression #35716

cmatheso opened this issue Mar 3, 2025 · 0 comments

Comments

@cmatheso
Copy link

cmatheso commented Mar 3, 2025

I've recently started looking at upgrading our .NET 8 apps from EF8 to EF9 and unfortunately think we are blocked until the regression issues are resolved.

As part of our testing infrastructure we spin up a fresh containerized mssql database and run migrations before proceeding with integration testing. Using EF8, we were able to run through >320 migrations in ~15 seconds. Bumping up to EF9 I've manually aborted the process past 10 minutes - its not finishing in a reasonable amount of time. I've noticed 2 main changes pre/post upgrade, 1st shows via the generated SQL is the lack of GO commands between statements and the single global transaction change vs. per migration transaction changes. The other is parameterization of seeded record inserts, but not confident that's causing the overall degradation.

Trying to debug some recorded dbcommands pre/post upgrade, here's 1 example of the performance degradation I'm seeing:

EF9 generated SQL to insert a single seeded row object during a larger migration:

IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N?, N?, N?, N?, N?, N?, N?, N?) AND [object_id] = OBJECT_ID(N?))
    SET IDENTITY_INSERT [MY_DB].[MY_TABLE] ON;
INSERT INTO [MY_DB].[MY_TABLE] ([Id], [CreatedAt], [CreatedBy], [FkColumn1], [FkColumn2], [IsDisplayable], [UpdatedAt], [UpdatedBy])
VALUES (?, ?, N?, ?, ?, CAST(? AS bit), ?, N?),
(?, ?, N?, ?, ?, CAST(? AS bit), ?, N?);
IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N?, N?, N?, N?, N?, N?, N?, N?) AND [object_id] = OBJECT_ID(N?))
    SET IDENTITY_INSERT [MY_DB].[MY_TABLE] OFF;

Execution time: 3.47 seconds

EF8 generated SQL to insert a single seeded row object during a larger migration:

IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'CreatedAt', N'CreatedBy', N'FkColumn1', N'FkColumn2', N'UpdatedAt', N'UpdatedBy') AND [object_id] = OBJECT_ID(N'[MY_DB].[MY_TABLE]'))
    SET IDENTITY_INSERT [MY_DB].[MY_TABLE] ON;
INSERT INTO [MY_DB].[MY_TABLE] ([Id], [CreatedAt], [CreatedBy], [FkColumn1], [FkColumn2], [UpdatedAt], [UpdatedBy])
VALUES ('1940df2a-0341-4950-950f-a95449844678', '2025-02-26T21:55:16.0890000Z', N'00000000-0000-0000-0000-000000000000', 'c464c814-cff4-4c44-85fe-48aee620147e', '0870c5d5-04f8-4d73-8cea-c447c8548eaa', '2025-02-26T21:55:16.0890000Z', N'00000000-0000-0000-0000-000000000000');
IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'CreatedAt', N'CreatedBy', N'FkColumn1', N'FkColumn2', N'UpdatedAt', N'UpdatedBy') AND [object_id] = OBJECT_ID(N'[MY_DB].[MY_TABLE]'))
    SET IDENTITY_INSERT [MY_DB].[MY_TABLE] OFF;

Execution time: 6.9 msec.

Table definition if curious:

CREATE TABLE [MY_DB].[MY_TABLE]
(
[Id] [uniqueidentifier] NOT NULL CONSTRAINT [DF_MY_DB_MY_TABLE_Id] DEFAULT (newsequentialid()),
[Adm_TenantId] [uniqueidentifier] NOT NULL CONSTRAINT [DF_MY_DB_MY_TABLE_Adm_TenantId] DEFAULT ('00000000-0000-0000-0000-000000000000'),
[FkColumn1] [uniqueidentifier] NOT NULL,
[FkColumn2] [uniqueidentifier] NOT NULL,
[Adm_UpdatedBy] [nvarchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL CONSTRAINT [DF_MY_DB_MY_TABLE_Adm_UpdatedBy] DEFAULT (suser_sname()),
[Adm_UpdatedAt] [datetime2] NOT NULL CONSTRAINT [DF_MY_DB_MY_TABLE_Adm_UpdatedAt] DEFAULT (getutcdate()),
[CreatedBy] [nvarchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL CONSTRAINT [DF_MY_DB_MY_TABLE_CreatedBy] DEFAULT ('00000000-0000-0000-0000-000000000000'),
[CreatedAt] [datetime2] NOT NULL CONSTRAINT [DF_MY_DB_MY_TABLE_CreatedAt] DEFAULT (getutcdate()),
[UpdatedBy] [nvarchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL CONSTRAINT [DF_MY_DB_MY_TABLE_UpdatedBy] DEFAULT ('00000000-0000-0000-0000-000000000000'),
[UpdatedAt] [datetime2] NOT NULL CONSTRAINT [DF_MY_DB_MY_TABLE_UpdatedAt] DEFAULT (getutcdate()),
[IsDisplayable] [bit] NOT NULL CONSTRAINT [DF_MY_DB_MY_TABLE_IsDisplayable] DEFAULT (CONVERT([bit],(1)))
);
GO
ALTER TABLE [MY_DB].[MY_TABLE] ADD CONSTRAINT [PK_MY_DB_MY_TABLE_Id] PRIMARY KEY CLUSTERED ([Id])
GO

I'm not able to post internal app code but may be able to spin up a test repo if needed. Couple key info points:

  • Using .NET 8.
  • Attempting to use EF 9.0.2 (from 8.0.10).
  • Windows11, MSSQL via WSL2, local environment.
  • 1 Database, 1 DbContext, 11 Tables, >320 EF migrations.
  • Most migrations are pure EF generated (ie, creates/alter tables, update indexes, etc). We have ~500 rows of seeded data spread throughout. Very small set of migrations are custom/raw sql.
  • Running start -> finish the migration SQL when dumped to a script is around 12000 lines. The lack of GO commands post EF9 unfortunately also makes the generated script unusable out of box.
  • Other libs: Microsoft.Data.SqlClient=6.0.1, OpenTelemetry.Instrumentation.SqlClient=1.11.0-beta.1.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants