Skip to content

Commit 9f0f118

Browse files
committed
Add flag to ignore the MigrationHistory table from the naming conventions
1 parent 493d4b1 commit 9f0f118

File tree

6 files changed

+112
-41
lines changed

6 files changed

+112
-41
lines changed

EFCore.NamingConventions.Test/NameRewritingConventionTest.cs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
using Microsoft.EntityFrameworkCore;
88
using Microsoft.EntityFrameworkCore.Metadata;
99
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
10+
using Microsoft.EntityFrameworkCore.Metadata.Internal;
11+
using Microsoft.EntityFrameworkCore.Migrations;
1012
using Microsoft.EntityFrameworkCore.TestUtilities;
1113
using Microsoft.Extensions.DependencyInjection;
1214
using Xunit;
@@ -30,6 +32,25 @@ public void Column()
3032
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
3133
}
3234

35+
[Theory]
36+
[InlineData(true, "__EFMigrationsHistory", "MigrationId", "ProductVersion")]
37+
[InlineData(false, "__EFMigrationsHistory", "migration_id", "product_version")]
38+
public void ColumnInMigrationTable(bool ignoreMigrationTable, string tableName, string migrationIdName, string productVersionName)
39+
{
40+
var entityType = BuildEntityType(b => b.Entity<HistoryRow>(e => {
41+
e.ToTable("__EFMigrationsHistory");
42+
e.HasKey(h => h.MigrationId);
43+
e.Property(h => h.MigrationId).HasMaxLength(150);
44+
e.Property(h => h.ProductVersion).HasMaxLength(32).IsRequired();
45+
}), ignoreMigrationTable: ignoreMigrationTable);
46+
47+
Assert.Equal(tableName, entityType.GetTableName());
48+
Assert.Equal(migrationIdName, entityType.FindProperty(nameof(HistoryRow.MigrationId))
49+
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
50+
Assert.Equal(productVersionName, entityType.FindProperty(nameof(HistoryRow.ProductVersion))
51+
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
52+
}
53+
3354
[Fact]
3455
public void Column_with_turkish_culture()
3556
{
@@ -572,10 +593,10 @@ public void Foreign_key_without_name_because_over_view()
572593
Assert.Null);
573594
}
574595

575-
private IEntityType BuildEntityType(Action<ModelBuilder> builderAction, CultureInfo? culture = null)
576-
=> BuildModel(builderAction, culture).GetEntityTypes().Single();
596+
private IEntityType BuildEntityType(Action<ModelBuilder> builderAction, CultureInfo? culture = null, bool ignoreMigrationTable = false)
597+
=> BuildModel(builderAction, culture, ignoreMigrationTable).GetEntityTypes().Single();
577598

578-
private IModel BuildModel(Action<ModelBuilder> builderAction, CultureInfo? culture = null)
599+
private IModel BuildModel(Action<ModelBuilder> builderAction, CultureInfo? culture = null, bool ignoreMigrationTable = false)
579600
{
580601
var conventionSet = SqliteTestHelpers
581602
.Instance
@@ -585,7 +606,7 @@ private IModel BuildModel(Action<ModelBuilder> builderAction, CultureInfo? cultu
585606

586607
var optionsBuilder = new DbContextOptionsBuilder();
587608
SqliteTestHelpers.Instance.UseProviderOptions(optionsBuilder);
588-
optionsBuilder.UseSnakeCaseNamingConvention(culture);
609+
optionsBuilder.UseSnakeCaseNamingConvention(culture, ignoreMigrationTable);
589610
var plugin = new NamingConventionSetPlugin(optionsBuilder.Options);
590611
plugin.ModifyConventions(conventionSet);
591612

EFCore.NamingConventions/Internal/NameRewritingConvention.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@ private static readonly StoreObjectType[] _storeObjectTypes
2323

2424
private readonly INameRewriter _namingNameRewriter;
2525

26-
public NameRewritingConvention(INameRewriter nameRewriter)
27-
=> _namingNameRewriter = nameRewriter;
26+
private readonly bool _ignoreMigrationTable;
27+
28+
public NameRewritingConvention(INameRewriter nameRewriter, bool ignoreMigrationTable = false)
29+
{
30+
_namingNameRewriter = nameRewriter;
31+
_ignoreMigrationTable = ignoreMigrationTable;
32+
}
2833

2934
public virtual void ProcessEntityTypeAdded(
3035
IConventionEntityTypeBuilder entityTypeBuilder,
@@ -298,6 +303,11 @@ private void RewriteColumnName(IConventionPropertyBuilder propertyBuilder)
298303
// Remove any previous setting of the column name we may have done, so we can get the default recalculated below.
299304
property.Builder.HasNoAnnotation(RelationalAnnotationNames.ColumnName);
300305

306+
if (_ignoreMigrationTable && entityType.ClrType.FullName == "Microsoft.EntityFrameworkCore.Migrations.HistoryRow")
307+
{
308+
return;
309+
}
310+
301311
// TODO: The following is a temporary hack. We should probably just always set the relational override below,
302312
// but https://github.com/dotnet/efcore/pull/23834
303313
var baseColumnName = StoreObjectIdentifier.Create(structuralType, StoreObjectType.Table) is { } tableIdentifier

EFCore.NamingConventions/Internal/NamingConventionSetPlugin.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public ConventionSet ModifyConventions(ConventionSet conventionSet)
1717
var extension = _options.FindExtension<NamingConventionsOptionsExtension>()!;
1818
var namingStyle = extension.NamingConvention;
1919
var culture = extension.Culture;
20+
var ignoreMigrationTable = extension.IgnoreMigrationTable;
2021
if (namingStyle == NamingConvention.None)
2122
{
2223
return conventionSet;
@@ -30,7 +31,7 @@ public ConventionSet ModifyConventions(ConventionSet conventionSet)
3031
NamingConvention.UpperCase => new UpperCaseNameRewriter(culture ?? CultureInfo.InvariantCulture),
3132
NamingConvention.UpperSnakeCase => new UpperSnakeCaseNameRewriter(culture ?? CultureInfo.InvariantCulture),
3233
_ => throw new ArgumentOutOfRangeException("Unhandled enum value: " + namingStyle)
33-
});
34+
}, ignoreMigrationTable);
3435

3536
conventionSet.EntityTypeAddedConventions.Add(convention);
3637
conventionSet.EntityTypeAnnotationChangedConventions.Add(convention);

EFCore.NamingConventions/Internal/NamingConventionsOptionsExtension.cs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ public class NamingConventionsOptionsExtension : IDbContextOptionsExtension
1212
private DbContextOptionsExtensionInfo? _info;
1313
private NamingConvention _namingConvention;
1414
private CultureInfo? _culture;
15+
private bool _ignoreMigrationTable;
1516

1617
public NamingConventionsOptionsExtension() {}
1718
protected NamingConventionsOptionsExtension(NamingConventionsOptionsExtension copyFrom)
1819
{
1920
_namingConvention = copyFrom._namingConvention;
2021
_culture = copyFrom._culture;
22+
_ignoreMigrationTable = copyFrom._ignoreMigrationTable;
2123
}
2224

2325
public virtual DbContextOptionsExtensionInfo Info => _info ??= new ExtensionInfo(this);
@@ -26,6 +28,7 @@ protected NamingConventionsOptionsExtension(NamingConventionsOptionsExtension co
2628

2729
internal virtual NamingConvention NamingConvention => _namingConvention;
2830
internal virtual CultureInfo? Culture => _culture;
31+
internal virtual bool IgnoreMigrationTable => _ignoreMigrationTable;
2932

3033
public virtual NamingConventionsOptionsExtension WithoutNaming()
3134
{
@@ -34,43 +37,48 @@ public virtual NamingConventionsOptionsExtension WithoutNaming()
3437
return clone;
3538
}
3639

37-
public virtual NamingConventionsOptionsExtension WithSnakeCaseNamingConvention(CultureInfo? culture = null)
40+
public virtual NamingConventionsOptionsExtension WithSnakeCaseNamingConvention(CultureInfo? culture = null, bool ignoreMigrationTable = false)
3841
{
3942
var clone = Clone();
4043
clone._namingConvention = NamingConvention.SnakeCase;
4144
clone._culture = culture;
45+
clone._ignoreMigrationTable = ignoreMigrationTable;
4246
return clone;
4347
}
4448

45-
public virtual NamingConventionsOptionsExtension WithLowerCaseNamingConvention(CultureInfo? culture = null)
49+
public virtual NamingConventionsOptionsExtension WithLowerCaseNamingConvention(CultureInfo? culture = null, bool ignoreMigrationTable = false)
4650
{
4751
var clone = Clone();
4852
clone._namingConvention = NamingConvention.LowerCase;
4953
clone._culture = culture;
54+
clone._ignoreMigrationTable = ignoreMigrationTable;
5055
return clone;
5156
}
5257

53-
public virtual NamingConventionsOptionsExtension WithUpperCaseNamingConvention(CultureInfo? culture = null)
58+
public virtual NamingConventionsOptionsExtension WithUpperCaseNamingConvention(CultureInfo? culture = null, bool ignoreMigrationTable = false)
5459
{
5560
var clone = Clone();
5661
clone._namingConvention = NamingConvention.UpperCase;
5762
clone._culture = culture;
63+
clone._ignoreMigrationTable = ignoreMigrationTable;
5864
return clone;
5965
}
6066

61-
public virtual NamingConventionsOptionsExtension WithUpperSnakeCaseNamingConvention(CultureInfo? culture = null)
67+
public virtual NamingConventionsOptionsExtension WithUpperSnakeCaseNamingConvention(CultureInfo? culture = null, bool ignoreMigrationTable = false)
6268
{
6369
var clone = Clone();
6470
clone._namingConvention = NamingConvention.UpperSnakeCase;
6571
clone._culture = culture;
72+
clone._ignoreMigrationTable = ignoreMigrationTable;
6673
return clone;
6774
}
6875

69-
public virtual NamingConventionsOptionsExtension WithCamelCaseNamingConvention(CultureInfo? culture = null)
76+
public virtual NamingConventionsOptionsExtension WithCamelCaseNamingConvention(CultureInfo? culture = null, bool ignoreMigrationTable = false)
7077
{
7178
var clone = Clone();
7279
clone._namingConvention = NamingConvention.CamelCase;
7380
clone._culture = culture;
81+
clone._ignoreMigrationTable = ignoreMigrationTable;
7482
return clone;
7583
}
7684

@@ -108,6 +116,12 @@ public override string LogFragment
108116
_ => throw new ArgumentOutOfRangeException("Unhandled enum value: " + Extension._namingConvention)
109117
});
110118

119+
if (Extension._ignoreMigrationTable)
120+
{
121+
builder
122+
.Append(" ignoring the migrations table");
123+
}
124+
111125
if (Extension._culture is null)
112126
{
113127
builder
@@ -127,6 +141,7 @@ public override int GetServiceProviderHashCode()
127141
{
128142
var hashCode = Extension._namingConvention.GetHashCode();
129143
hashCode = (hashCode * 3) ^ (Extension._culture?.GetHashCode() ?? 0);
144+
hashCode = (hashCode * 7) ^ (Extension._ignoreMigrationTable.GetHashCode());
130145
return hashCode;
131146
}
132147

@@ -137,6 +152,10 @@ public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
137152
{
138153
debugInfo["Naming:UseNamingConvention"]
139154
= Extension._namingConvention.GetHashCode().ToString(CultureInfo.InvariantCulture);
155+
156+
debugInfo["Naming:IgnoreMigrationTable"]
157+
= Extension._ignoreMigrationTable.GetHashCode().ToString(CultureInfo.InvariantCulture);
158+
140159
if (Extension._culture != null)
141160
{
142161
debugInfo["Naming:Culture"]

EFCore.NamingConventions/NamingConventionsExtensions.cs

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,106 +9,115 @@ namespace Microsoft.EntityFrameworkCore;
99
public static class NamingConventionsExtensions
1010
{
1111
public static DbContextOptionsBuilder UseSnakeCaseNamingConvention(
12-
this DbContextOptionsBuilder optionsBuilder,
13-
CultureInfo? culture = null)
12+
[NotNull] this DbContextOptionsBuilder optionsBuilder,
13+
CultureInfo? culture = null,
14+
bool ignoreMigrationTable = false)
1415
{
1516
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
1617

1718
var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>()
1819
?? new NamingConventionsOptionsExtension())
19-
.WithSnakeCaseNamingConvention(culture);
20+
.WithSnakeCaseNamingConvention(culture, ignoreMigrationTable);
2021

2122
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
2223

2324
return optionsBuilder;
2425
}
2526

2627
public static DbContextOptionsBuilder<TContext> UseSnakeCaseNamingConvention<TContext>(
27-
this DbContextOptionsBuilder<TContext> optionsBuilder , CultureInfo? culture = null)
28+
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder , CultureInfo? culture = null, bool ignoreMigrationTable = false)
2829
where TContext : DbContext
29-
=> (DbContextOptionsBuilder<TContext>)UseSnakeCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture);
30+
=> (DbContextOptionsBuilder<TContext>)UseSnakeCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture, ignoreMigrationTable);
3031

3132
public static DbContextOptionsBuilder UseLowerCaseNamingConvention(
32-
this DbContextOptionsBuilder optionsBuilder,
33-
CultureInfo? culture = null)
33+
[NotNull] this DbContextOptionsBuilder optionsBuilder,
34+
CultureInfo? culture = null,
35+
bool ignoreMigrationTable = false)
3436
{
3537
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
3638

3739
var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>()
3840
?? new NamingConventionsOptionsExtension())
39-
.WithLowerCaseNamingConvention(culture);
41+
.WithLowerCaseNamingConvention(culture, ignoreMigrationTable);
4042

4143
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
4244

4345
return optionsBuilder;
4446
}
4547

4648
public static DbContextOptionsBuilder<TContext> UseLowerCaseNamingConvention<TContext>(
47-
this DbContextOptionsBuilder<TContext> optionsBuilder,
48-
CultureInfo? culture = null)
49+
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder,
50+
CultureInfo? culture = null,
51+
bool ignoreMigrationTable = false)
4952
where TContext : DbContext
50-
=> (DbContextOptionsBuilder<TContext>)UseLowerCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder ,culture);
53+
=> (DbContextOptionsBuilder<TContext>)UseLowerCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder ,culture, ignoreMigrationTable);
5154

5255
public static DbContextOptionsBuilder UseUpperCaseNamingConvention(
53-
this DbContextOptionsBuilder optionsBuilder,
54-
CultureInfo? culture = null)
56+
[NotNull] this DbContextOptionsBuilder optionsBuilder,
57+
CultureInfo? culture = null,
58+
bool ignoreMigrationTable = false)
5559
{
5660
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
5761

5862
var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>()
5963
?? new NamingConventionsOptionsExtension())
60-
.WithUpperCaseNamingConvention(culture);
64+
.WithUpperCaseNamingConvention(culture, ignoreMigrationTable);
6165

6266
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
6367

6468
return optionsBuilder;
6569
}
6670

6771
public static DbContextOptionsBuilder<TContext> UseUpperCaseNamingConvention<TContext>(
68-
this DbContextOptionsBuilder<TContext> optionsBuilder,
69-
CultureInfo? culture = null)
72+
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder,
73+
CultureInfo? culture = null,
74+
bool ignoreMigrationTable = false)
7075
where TContext : DbContext
71-
=> (DbContextOptionsBuilder<TContext>)UseUpperCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture);
76+
=> (DbContextOptionsBuilder<TContext>)UseUpperCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture, ignoreMigrationTable);
7277

7378
public static DbContextOptionsBuilder UseUpperSnakeCaseNamingConvention(
74-
this DbContextOptionsBuilder optionsBuilder,
75-
CultureInfo? culture = null)
79+
[NotNull] this DbContextOptionsBuilder optionsBuilder,
80+
CultureInfo? culture = null,
81+
bool ignoreMigrationTable = false)
7682
{
7783
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
7884

7985
var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>()
8086
?? new NamingConventionsOptionsExtension())
81-
.WithUpperSnakeCaseNamingConvention(culture);
87+
.WithUpperSnakeCaseNamingConvention(culture, ignoreMigrationTable);
8288

8389
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
8490

8591
return optionsBuilder;
8692
}
8793

8894
public static DbContextOptionsBuilder<TContext> UseUpperSnakeCaseNamingConvention<TContext>(
89-
this DbContextOptionsBuilder<TContext> optionsBuilder,
90-
CultureInfo? culture = null)
95+
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder,
96+
CultureInfo? culture = null,
97+
bool ignoreMigrationTable = false)
9198
where TContext : DbContext
92-
=> (DbContextOptionsBuilder<TContext>)UseUpperSnakeCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture);
99+
=> (DbContextOptionsBuilder<TContext>)UseUpperSnakeCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture, ignoreMigrationTable);
93100

94101
public static DbContextOptionsBuilder UseCamelCaseNamingConvention(
95-
this DbContextOptionsBuilder optionsBuilder,
96-
CultureInfo? culture = null)
102+
[NotNull] this DbContextOptionsBuilder optionsBuilder,
103+
CultureInfo? culture = null,
104+
bool ignoreMigrationTable = false)
97105
{
98106
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
99107

100108
var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>()
101109
?? new NamingConventionsOptionsExtension())
102-
.WithCamelCaseNamingConvention(culture);
110+
.WithCamelCaseNamingConvention(culture, ignoreMigrationTable);
103111

104112
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
105113

106114
return optionsBuilder;
107115
}
108116

109117
public static DbContextOptionsBuilder<TContext> UseCamelCaseNamingConvention<TContext>(
110-
this DbContextOptionsBuilder<TContext> optionsBuilder,
111-
CultureInfo? culture = null)
118+
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder,
119+
CultureInfo? culture = null,
120+
bool ignoreMigrationTable = false)
112121
where TContext : DbContext
113-
=> (DbContextOptionsBuilder<TContext>)UseCamelCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture);
122+
=> (DbContextOptionsBuilder<TContext>)UseCamelCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture, ignoreMigrationTable);
114123
}

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ SELECT c.id, c.full_name
4141
WHERE c.full_name = 'John Doe';
4242
```
4343

44+
## Ignoring the Migration Table `__EFMigrationsHistory`
45+
46+
To make migrations of existing databases more robust one might want to leave the migrations table out of the naming conventions.
47+
48+
```c#
49+
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
50+
=> optionsBuilder
51+
.UseNpgsql(...)
52+
.UseSnakeCaseNamingConvention(ignoreMigrationTable: true);
53+
```
54+
4455
## Supported naming conventions
4556

4657
* UseSnakeCaseNamingConvention: `FullName` becomes `full_name`

0 commit comments

Comments
 (0)