Skip to content

Commit 0de56fb

Browse files
authored
Filter out full-text catalog operations from history table creation (#38050)
This is a workaround for #34991. Part of #11488
1 parent f7d7301 commit 0de56fb

3 files changed

Lines changed: 101 additions & 6 deletions

File tree

src/EFCore.Relational/Migrations/HistoryRepository.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,14 @@ protected virtual string MigrationIdColumnName
8585
.FindProperty(nameof(HistoryRow.MigrationId))!
8686
.GetColumnName();
8787

88-
private IModel EnsureModel()
88+
/// <summary>
89+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
90+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
91+
/// any release. You should only use it directly in your code with extreme caution and knowing that
92+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
93+
/// </summary>
94+
[EntityFrameworkInternal]
95+
protected virtual IModel EnsureModel()
8996
{
9097
if (_model == null)
9198
{

src/EFCore.SqlServer/Migrations/Internal/SqlServerHistoryRepository.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Text;
5+
using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal;
56

67
namespace Microsoft.EntityFrameworkCore.SqlServer.Migrations.Internal;
78

@@ -24,6 +25,36 @@ public SqlServerHistoryRepository(HistoryRepositoryDependencies dependencies)
2425
{
2526
}
2627

28+
/// <summary>
29+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
30+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
31+
/// any release. You should only use it directly in your code with extreme caution and knowing that
32+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
33+
/// </summary>
34+
protected override IReadOnlyList<MigrationCommand> GetCreateCommands()
35+
{
36+
// TODO: This is a hack around https://github.com/dotnet/efcore/issues/34991: provider-specific conventions may add
37+
// database-level annotations (e.g. full-text catalogs) to the model, and the default EF logic causes them to be created
38+
// at this point, when the history table is being created. This is too early, and causes the later actual migration to fail.
39+
// So we filter out full-text catalog annotations from AlterDatabaseOperation.
40+
// This follows the same approach as the Npgsql provider (npgsql/efcore.pg#3713).
41+
#pragma warning disable EF1001 // Internal EF Core API usage.
42+
var model = EnsureModel();
43+
#pragma warning restore EF1001 // Internal EF Core API usage.
44+
45+
var operations = Dependencies.ModelDiffer.GetDifferences(null, model.GetRelationalModel());
46+
47+
foreach (var operation in operations)
48+
{
49+
if (operation is AlterDatabaseOperation alterDatabaseOperation)
50+
{
51+
alterDatabaseOperation.RemoveAnnotation(SqlServerAnnotationNames.FullTextCatalogs);
52+
}
53+
}
54+
55+
return Dependencies.MigrationsSqlGenerator.Generate(operations, model);
56+
}
57+
2758
/// <summary>
2859
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
2960
/// the same compatibility standards as public APIs. It may be changed or removed without notice in

test/EFCore.SqlServer.Tests/Migrations/SqlServerHistoryRepositoryTest.cs

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using Microsoft.Data.SqlClient;
5+
using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal;
56

67
// ReSharper disable InconsistentNaming
78
namespace Microsoft.EntityFrameworkCore.Migrations;
@@ -21,6 +22,25 @@ [ProductVersion] nvarchar(32) NOT NULL,
2122
CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
2223
);
2324
25+
""", sql, ignoreLineEndingDifferences: true);
26+
}
27+
28+
[ConditionalFact]
29+
public void GetCreateScript_works_with_full_text_catalog()
30+
{
31+
// Inject a model finalizing convention that adds a full-text catalog to the model, simulating a scenario where
32+
// provider conventions add database-level annotations. Without filtering, the history table creation script would
33+
// include CREATE FULLTEXT CATALOG.
34+
var sql = CreateHistoryRepository(addFullTextCatalogConvention: true).GetCreateScript();
35+
36+
Assert.Equal(
37+
"""
38+
CREATE TABLE [__EFMigrationsHistory] (
39+
[MigrationId] nvarchar(150) NOT NULL,
40+
[ProductVersion] nvarchar(32) NOT NULL,
41+
CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
42+
);
43+
2444
""", sql, ignoreLineEndingDifferences: true);
2545
}
2646

@@ -149,17 +169,28 @@ public void GetEndIfScript_works()
149169
""", sql, ignoreLineEndingDifferences: true);
150170
}
151171

152-
private static IHistoryRepository CreateHistoryRepository(string schema = null)
153-
=> new TestDbContext(
172+
private static IHistoryRepository CreateHistoryRepository(
173+
string schema = null,
174+
Action<ModelBuilder> configureModel = null,
175+
bool addFullTextCatalogConvention = false)
176+
{
177+
var serviceProvider = addFullTextCatalogConvention
178+
? SqlServerTestHelpers.Instance.CreateServiceProvider(
179+
new ServiceCollection().AddSingleton<IConventionSetPlugin, FullTextCatalogConventionPlugin>())
180+
: SqlServerTestHelpers.Instance.CreateServiceProvider();
181+
182+
return new TestDbContext(
154183
new DbContextOptionsBuilder()
155-
.UseInternalServiceProvider(SqlServerTestHelpers.Instance.CreateServiceProvider())
184+
.UseInternalServiceProvider(serviceProvider)
156185
.UseSqlServer(
157186
new SqlConnection("Database=DummyDatabase"),
158187
b => b.MigrationsHistoryTable(HistoryRepository.DefaultTableName, schema))
159-
.Options)
188+
.Options,
189+
configureModel)
160190
.GetService<IHistoryRepository>();
191+
}
161192

162-
private class TestDbContext(DbContextOptions options) : DbContext(options)
193+
private class TestDbContext(DbContextOptions options, Action<ModelBuilder> configureModel = null) : DbContext(options)
163194
{
164195
public DbSet<Blog> Blogs { get; set; }
165196

@@ -169,9 +200,35 @@ public IQueryable<TableFunction> TableFunction()
169200

170201
protected override void OnModelCreating(ModelBuilder modelBuilder)
171202
{
203+
configureModel?.Invoke(modelBuilder);
204+
}
205+
}
206+
207+
/// <summary>
208+
/// A convention plugin that adds a full-text catalog annotation to the model, simulating what a provider convention
209+
/// might do. This allows testing that <see cref="SqlServerHistoryRepository.GetCreateCommands" /> properly filters
210+
/// out the full-text catalog from the history table creation script.
211+
/// </summary>
212+
private class FullTextCatalogConventionPlugin : IConventionSetPlugin
213+
{
214+
public ConventionSet ModifyConventions(ConventionSet conventionSet)
215+
{
216+
conventionSet.ModelFinalizingConventions.Add(new FullTextCatalogAddingConvention());
217+
return conventionSet;
172218
}
173219
}
174220

221+
private class FullTextCatalogAddingConvention : IModelFinalizingConvention
222+
{
223+
#pragma warning disable EF1001 // Internal EF Core API usage.
224+
public void ProcessModelFinalizing(
225+
IConventionModelBuilder modelBuilder,
226+
IConventionContext<IConventionModelBuilder> context)
227+
=> SqlServerFullTextCatalog.AddFullTextCatalog(
228+
(IMutableModel)modelBuilder.Metadata, "TestCatalog", ConfigurationSource.Convention);
229+
#pragma warning restore EF1001 // Internal EF Core API usage.
230+
}
231+
175232
private class Blog
176233
{
177234
public int Id { get; set; }

0 commit comments

Comments
 (0)