diff --git a/src/EFCore.Relational/Migrations/HistoryRepository.cs b/src/EFCore.Relational/Migrations/HistoryRepository.cs
index 48934123be0..714797d9fea 100644
--- a/src/EFCore.Relational/Migrations/HistoryRepository.cs
+++ b/src/EFCore.Relational/Migrations/HistoryRepository.cs
@@ -85,7 +85,14 @@ protected virtual string MigrationIdColumnName
.FindProperty(nameof(HistoryRow.MigrationId))!
.GetColumnName();
- private IModel EnsureModel()
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ protected virtual IModel EnsureModel()
{
if (_model == null)
{
diff --git a/src/EFCore.SqlServer/Migrations/Internal/SqlServerHistoryRepository.cs b/src/EFCore.SqlServer/Migrations/Internal/SqlServerHistoryRepository.cs
index e8128cd0ef5..6189774c621 100644
--- a/src/EFCore.SqlServer/Migrations/Internal/SqlServerHistoryRepository.cs
+++ b/src/EFCore.SqlServer/Migrations/Internal/SqlServerHistoryRepository.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text;
+using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal;
namespace Microsoft.EntityFrameworkCore.SqlServer.Migrations.Internal;
@@ -24,6 +25,36 @@ public SqlServerHistoryRepository(HistoryRepositoryDependencies dependencies)
{
}
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override IReadOnlyList GetCreateCommands()
+ {
+ // TODO: This is a hack around https://github.com/dotnet/efcore/issues/34991: provider-specific conventions may add
+ // database-level annotations (e.g. full-text catalogs) to the model, and the default EF logic causes them to be created
+ // at this point, when the history table is being created. This is too early, and causes the later actual migration to fail.
+ // So we filter out full-text catalog annotations from AlterDatabaseOperation.
+ // This follows the same approach as the Npgsql provider (npgsql/efcore.pg#3713).
+#pragma warning disable EF1001 // Internal EF Core API usage.
+ var model = EnsureModel();
+#pragma warning restore EF1001 // Internal EF Core API usage.
+
+ var operations = Dependencies.ModelDiffer.GetDifferences(null, model.GetRelationalModel());
+
+ foreach (var operation in operations)
+ {
+ if (operation is AlterDatabaseOperation alterDatabaseOperation)
+ {
+ alterDatabaseOperation.RemoveAnnotation(SqlServerAnnotationNames.FullTextCatalogs);
+ }
+ }
+
+ return Dependencies.MigrationsSqlGenerator.Generate(operations, model);
+ }
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/test/EFCore.SqlServer.Tests/Migrations/SqlServerHistoryRepositoryTest.cs b/test/EFCore.SqlServer.Tests/Migrations/SqlServerHistoryRepositoryTest.cs
index a19a9c536f1..273953819b5 100644
--- a/test/EFCore.SqlServer.Tests/Migrations/SqlServerHistoryRepositoryTest.cs
+++ b/test/EFCore.SqlServer.Tests/Migrations/SqlServerHistoryRepositoryTest.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Data.SqlClient;
+using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal;
// ReSharper disable InconsistentNaming
namespace Microsoft.EntityFrameworkCore.Migrations;
@@ -21,6 +22,25 @@ [ProductVersion] nvarchar(32) NOT NULL,
CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
);
+""", sql, ignoreLineEndingDifferences: true);
+ }
+
+ [ConditionalFact]
+ public void GetCreateScript_works_with_full_text_catalog()
+ {
+ // Inject a model finalizing convention that adds a full-text catalog to the model, simulating a scenario where
+ // provider conventions add database-level annotations. Without filtering, the history table creation script would
+ // include CREATE FULLTEXT CATALOG.
+ var sql = CreateHistoryRepository(addFullTextCatalogConvention: true).GetCreateScript();
+
+ Assert.Equal(
+ """
+CREATE TABLE [__EFMigrationsHistory] (
+ [MigrationId] nvarchar(150) NOT NULL,
+ [ProductVersion] nvarchar(32) NOT NULL,
+ CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
+);
+
""", sql, ignoreLineEndingDifferences: true);
}
@@ -149,17 +169,28 @@ public void GetEndIfScript_works()
""", sql, ignoreLineEndingDifferences: true);
}
- private static IHistoryRepository CreateHistoryRepository(string schema = null)
- => new TestDbContext(
+ private static IHistoryRepository CreateHistoryRepository(
+ string schema = null,
+ Action configureModel = null,
+ bool addFullTextCatalogConvention = false)
+ {
+ var serviceProvider = addFullTextCatalogConvention
+ ? SqlServerTestHelpers.Instance.CreateServiceProvider(
+ new ServiceCollection().AddSingleton())
+ : SqlServerTestHelpers.Instance.CreateServiceProvider();
+
+ return new TestDbContext(
new DbContextOptionsBuilder()
- .UseInternalServiceProvider(SqlServerTestHelpers.Instance.CreateServiceProvider())
+ .UseInternalServiceProvider(serviceProvider)
.UseSqlServer(
new SqlConnection("Database=DummyDatabase"),
b => b.MigrationsHistoryTable(HistoryRepository.DefaultTableName, schema))
- .Options)
+ .Options,
+ configureModel)
.GetService();
+ }
- private class TestDbContext(DbContextOptions options) : DbContext(options)
+ private class TestDbContext(DbContextOptions options, Action configureModel = null) : DbContext(options)
{
public DbSet Blogs { get; set; }
@@ -169,9 +200,35 @@ public IQueryable TableFunction()
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
+ configureModel?.Invoke(modelBuilder);
+ }
+ }
+
+ ///
+ /// A convention plugin that adds a full-text catalog annotation to the model, simulating what a provider convention
+ /// might do. This allows testing that properly filters
+ /// out the full-text catalog from the history table creation script.
+ ///
+ private class FullTextCatalogConventionPlugin : IConventionSetPlugin
+ {
+ public ConventionSet ModifyConventions(ConventionSet conventionSet)
+ {
+ conventionSet.ModelFinalizingConventions.Add(new FullTextCatalogAddingConvention());
+ return conventionSet;
}
}
+ private class FullTextCatalogAddingConvention : IModelFinalizingConvention
+ {
+#pragma warning disable EF1001 // Internal EF Core API usage.
+ public void ProcessModelFinalizing(
+ IConventionModelBuilder modelBuilder,
+ IConventionContext context)
+ => SqlServerFullTextCatalog.AddFullTextCatalog(
+ (IMutableModel)modelBuilder.Metadata, "TestCatalog", ConfigurationSource.Convention);
+#pragma warning restore EF1001 // Internal EF Core API usage.
+ }
+
private class Blog
{
public int Id { get; set; }