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

Add flag to ignore the MigrationHistory table from the naming conventions #66

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions EFCore.NamingConventions.Test/NameRewritingConventionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.SqlServer.Diagnostics.Internal;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -32,6 +34,25 @@ public void Column()
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
}

[Theory]
[InlineData(true, "__EFMigrationsHistory", "MigrationId", "ProductVersion")]
[InlineData(false, "__EFMigrationsHistory", "migration_id", "product_version")]
public void ColumnInMigrationTable(bool ignoreMigrationTable, string tableName, string migrationIdName, string productVersionName)
{
var entityType = BuildEntityType(b => b.Entity<HistoryRow>(e => {
e.ToTable("__EFMigrationsHistory");
e.HasKey(h => h.MigrationId);
e.Property(h => h.MigrationId).HasMaxLength(150);
e.Property(h => h.ProductVersion).HasMaxLength(32).IsRequired();
}), ignoreMigrationTable: ignoreMigrationTable);

Assert.Equal(tableName, entityType.GetTableName());
Assert.Equal(migrationIdName, entityType.FindProperty(nameof(HistoryRow.MigrationId))
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
Assert.Equal(productVersionName, entityType.FindProperty(nameof(HistoryRow.ProductVersion))
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
}

[Fact]
public void Column_with_turkish_culture()
{
Expand Down Expand Up @@ -724,10 +745,10 @@ public void Foreign_key_on_key_without_setter()
Assert.Equal("fk_card_board_board_id", entityType.GetForeignKeys().Single().GetConstraintName());
}

private IEntityType BuildEntityType(Action<ModelBuilder> builderAction, CultureInfo? culture = null)
=> BuildModel(builderAction, culture).GetEntityTypes().Single();
private IEntityType BuildEntityType(Action<ModelBuilder> builderAction, CultureInfo? culture = null, bool ignoreMigrationTable = false)
=> BuildModel(builderAction, culture, ignoreMigrationTable).GetEntityTypes().Single();

private IModel BuildModel(Action<ModelBuilder> builderAction, CultureInfo? culture = null)
private IModel BuildModel(Action<ModelBuilder> builderAction, CultureInfo? culture = null, bool ignoreMigrationTable = false)
{
var services = SqlServerTestHelpers
.Instance
Expand All @@ -741,7 +762,7 @@ private IModel BuildModel(Action<ModelBuilder> builderAction, CultureInfo? cultu

var optionsBuilder = new DbContextOptionsBuilder();
SqlServerTestHelpers.Instance.UseProviderOptions(optionsBuilder);
optionsBuilder.UseSnakeCaseNamingConvention(culture);
optionsBuilder.UseSnakeCaseNamingConvention(culture, ignoreMigrationTable);
var plugin = new NamingConventionSetPlugin(dependencies, optionsBuilder.Options);
plugin.ModifyConventions(conventionSet);

Expand Down
10 changes: 9 additions & 1 deletion EFCore.NamingConventions/Internal/NameRewritingConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ private static readonly StoreObjectType[] _storeObjectTypes

private readonly IDictionary<Type, string> _sets;
private readonly INameRewriter _namingNameRewriter;
private readonly bool _ignoreMigrationTable;

public NameRewritingConvention(ProviderConventionSetBuilderDependencies dependencies, INameRewriter nameRewriter)
public NameRewritingConvention(ProviderConventionSetBuilderDependencies dependencies, INameRewriter nameRewriter, bool ignoreMigrationTable = false)
{
_namingNameRewriter = nameRewriter;
_ignoreMigrationTable = ignoreMigrationTable;

// Copied from TableNameFromDbSetConvention
_sets = new Dictionary<Type, string>();
Expand All @@ -56,6 +58,7 @@ public NameRewritingConvention(ProviderConventionSetBuilderDependencies dependen
_sets.Remove(type);
}
}

}

public virtual void ProcessEntityTypeAdded(
Expand Down Expand Up @@ -474,6 +477,11 @@ private void RewriteColumnName(IConventionPropertyBuilder propertyBuilder)
// Remove any previous setting of the column name we may have done, so we can get the default recalculated below.
property.Builder.HasNoAnnotation(RelationalAnnotationNames.ColumnName);

if (_ignoreMigrationTable && structuralType.ClrType.FullName == "Microsoft.EntityFrameworkCore.Migrations.HistoryRow")
{
return;
}

// TODO: The following is a temporary hack. We should probably just always set the relational override below,
// but https://github.com/dotnet/efcore/pull/23834
var baseColumnName = StoreObjectIdentifier.Create(structuralType, StoreObjectType.Table) is { } tableIdentifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public ConventionSet ModifyConventions(ConventionSet conventionSet)
new NamingConventionsOptionsExtension().WithoutNaming();
var namingStyle = extension.NamingConvention;
var culture = extension.Culture;
var ignoreMigrationTable = extension.IgnoreMigrationTable;
if (namingStyle == NamingConvention.None)
{
return conventionSet;
Expand All @@ -39,7 +40,7 @@ public ConventionSet ModifyConventions(ConventionSet conventionSet)
NamingConvention.UpperCase => new UpperCaseNameRewriter(culture ?? CultureInfo.InvariantCulture),
NamingConvention.UpperSnakeCase => new UpperSnakeCaseNameRewriter(culture ?? CultureInfo.InvariantCulture),
_ => throw new ArgumentOutOfRangeException("Unhandled enum value: " + namingStyle)
});
}, ignoreMigrationTable);

conventionSet.EntityTypeAddedConventions.Add(convention);
conventionSet.EntityTypeAnnotationChangedConventions.Add(convention);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ public class NamingConventionsOptionsExtension : IDbContextOptionsExtension
private DbContextOptionsExtensionInfo? _info;
private NamingConvention _namingConvention;
private CultureInfo? _culture;
private bool _ignoreMigrationTable;

public NamingConventionsOptionsExtension() {}
protected NamingConventionsOptionsExtension(NamingConventionsOptionsExtension copyFrom)
{
_namingConvention = copyFrom._namingConvention;
_culture = copyFrom._culture;
_ignoreMigrationTable = copyFrom._ignoreMigrationTable;
}

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

internal virtual NamingConvention NamingConvention => _namingConvention;
internal virtual CultureInfo? Culture => _culture;
internal virtual bool IgnoreMigrationTable => _ignoreMigrationTable;

public virtual NamingConventionsOptionsExtension WithoutNaming()
{
Expand All @@ -34,43 +37,48 @@ public virtual NamingConventionsOptionsExtension WithoutNaming()
return clone;
}

public virtual NamingConventionsOptionsExtension WithSnakeCaseNamingConvention(CultureInfo? culture = null)
public virtual NamingConventionsOptionsExtension WithSnakeCaseNamingConvention(CultureInfo? culture = null, bool ignoreMigrationTable = false)
{
var clone = Clone();
clone._namingConvention = NamingConvention.SnakeCase;
clone._culture = culture;
clone._ignoreMigrationTable = ignoreMigrationTable;
return clone;
}

public virtual NamingConventionsOptionsExtension WithLowerCaseNamingConvention(CultureInfo? culture = null)
public virtual NamingConventionsOptionsExtension WithLowerCaseNamingConvention(CultureInfo? culture = null, bool ignoreMigrationTable = false)
{
var clone = Clone();
clone._namingConvention = NamingConvention.LowerCase;
clone._culture = culture;
clone._ignoreMigrationTable = ignoreMigrationTable;
return clone;
}

public virtual NamingConventionsOptionsExtension WithUpperCaseNamingConvention(CultureInfo? culture = null)
public virtual NamingConventionsOptionsExtension WithUpperCaseNamingConvention(CultureInfo? culture = null, bool ignoreMigrationTable = false)
{
var clone = Clone();
clone._namingConvention = NamingConvention.UpperCase;
clone._culture = culture;
clone._ignoreMigrationTable = ignoreMigrationTable;
return clone;
}

public virtual NamingConventionsOptionsExtension WithUpperSnakeCaseNamingConvention(CultureInfo? culture = null)
public virtual NamingConventionsOptionsExtension WithUpperSnakeCaseNamingConvention(CultureInfo? culture = null, bool ignoreMigrationTable = false)
{
var clone = Clone();
clone._namingConvention = NamingConvention.UpperSnakeCase;
clone._culture = culture;
clone._ignoreMigrationTable = ignoreMigrationTable;
return clone;
}

public virtual NamingConventionsOptionsExtension WithCamelCaseNamingConvention(CultureInfo? culture = null)
public virtual NamingConventionsOptionsExtension WithCamelCaseNamingConvention(CultureInfo? culture = null, bool ignoreMigrationTable = false)
{
var clone = Clone();
clone._namingConvention = NamingConvention.CamelCase;
clone._culture = culture;
clone._ignoreMigrationTable = ignoreMigrationTable;
return clone;
}

Expand Down Expand Up @@ -109,6 +117,12 @@ public override string LogFragment
_ => throw new ArgumentOutOfRangeException("Unhandled enum value: " + Extension._namingConvention)
});

if (Extension._ignoreMigrationTable)
{
builder
.Append(" ignoring the migrations table");
}

if (Extension._culture is null)
{
builder
Expand All @@ -128,6 +142,7 @@ public override int GetServiceProviderHashCode()
{
var hashCode = Extension._namingConvention.GetHashCode();
hashCode = (hashCode * 3) ^ (Extension._culture?.GetHashCode() ?? 0);
hashCode = (hashCode * 7) ^ (Extension._ignoreMigrationTable.GetHashCode());
return hashCode;
}

Expand All @@ -138,6 +153,10 @@ public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
{
debugInfo["Naming:UseNamingConvention"]
= Extension._namingConvention.GetHashCode().ToString(CultureInfo.InvariantCulture);

debugInfo["Naming:IgnoreMigrationTable"]
= Extension._ignoreMigrationTable.GetHashCode().ToString(CultureInfo.InvariantCulture);

if (Extension._culture != null)
{
debugInfo["Naming:Culture"]
Expand Down
67 changes: 38 additions & 29 deletions EFCore.NamingConventions/NamingConventionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,106 +9,115 @@ namespace Microsoft.EntityFrameworkCore;
public static class NamingConventionsExtensions
{
public static DbContextOptionsBuilder UseSnakeCaseNamingConvention(
this DbContextOptionsBuilder optionsBuilder,
CultureInfo? culture = null)
[NotNull] this DbContextOptionsBuilder optionsBuilder,
CultureInfo? culture = null,
bool ignoreMigrationTable = false)
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));

var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>()
?? new NamingConventionsOptionsExtension())
.WithSnakeCaseNamingConvention(culture);
.WithSnakeCaseNamingConvention(culture, ignoreMigrationTable);

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

return optionsBuilder;
}

public static DbContextOptionsBuilder<TContext> UseSnakeCaseNamingConvention<TContext>(
this DbContextOptionsBuilder<TContext> optionsBuilder , CultureInfo? culture = null)
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder , CultureInfo? culture = null, bool ignoreMigrationTable = false)
where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>)UseSnakeCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture);
=> (DbContextOptionsBuilder<TContext>)UseSnakeCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture, ignoreMigrationTable);

public static DbContextOptionsBuilder UseLowerCaseNamingConvention(
this DbContextOptionsBuilder optionsBuilder,
CultureInfo? culture = null)
[NotNull] this DbContextOptionsBuilder optionsBuilder,
CultureInfo? culture = null,
bool ignoreMigrationTable = false)
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));

var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>()
?? new NamingConventionsOptionsExtension())
.WithLowerCaseNamingConvention(culture);
.WithLowerCaseNamingConvention(culture, ignoreMigrationTable);

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

return optionsBuilder;
}

public static DbContextOptionsBuilder<TContext> UseLowerCaseNamingConvention<TContext>(
this DbContextOptionsBuilder<TContext> optionsBuilder,
CultureInfo? culture = null)
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder,
CultureInfo? culture = null,
bool ignoreMigrationTable = false)
where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>)UseLowerCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder ,culture);
=> (DbContextOptionsBuilder<TContext>)UseLowerCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder ,culture, ignoreMigrationTable);

public static DbContextOptionsBuilder UseUpperCaseNamingConvention(
this DbContextOptionsBuilder optionsBuilder,
CultureInfo? culture = null)
[NotNull] this DbContextOptionsBuilder optionsBuilder,
CultureInfo? culture = null,
bool ignoreMigrationTable = false)
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));

var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>()
?? new NamingConventionsOptionsExtension())
.WithUpperCaseNamingConvention(culture);
.WithUpperCaseNamingConvention(culture, ignoreMigrationTable);

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

return optionsBuilder;
}

public static DbContextOptionsBuilder<TContext> UseUpperCaseNamingConvention<TContext>(
this DbContextOptionsBuilder<TContext> optionsBuilder,
CultureInfo? culture = null)
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder,
CultureInfo? culture = null,
bool ignoreMigrationTable = false)
where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>)UseUpperCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture);
=> (DbContextOptionsBuilder<TContext>)UseUpperCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture, ignoreMigrationTable);

public static DbContextOptionsBuilder UseUpperSnakeCaseNamingConvention(
this DbContextOptionsBuilder optionsBuilder,
CultureInfo? culture = null)
[NotNull] this DbContextOptionsBuilder optionsBuilder,
CultureInfo? culture = null,
bool ignoreMigrationTable = false)
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));

var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>()
?? new NamingConventionsOptionsExtension())
.WithUpperSnakeCaseNamingConvention(culture);
.WithUpperSnakeCaseNamingConvention(culture, ignoreMigrationTable);

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

return optionsBuilder;
}

public static DbContextOptionsBuilder<TContext> UseUpperSnakeCaseNamingConvention<TContext>(
this DbContextOptionsBuilder<TContext> optionsBuilder,
CultureInfo? culture = null)
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder,
CultureInfo? culture = null,
bool ignoreMigrationTable = false)
where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>)UseUpperSnakeCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture);
=> (DbContextOptionsBuilder<TContext>)UseUpperSnakeCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture, ignoreMigrationTable);

public static DbContextOptionsBuilder UseCamelCaseNamingConvention(
this DbContextOptionsBuilder optionsBuilder,
CultureInfo? culture = null)
[NotNull] this DbContextOptionsBuilder optionsBuilder,
CultureInfo? culture = null,
bool ignoreMigrationTable = false)
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));

var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>()
?? new NamingConventionsOptionsExtension())
.WithCamelCaseNamingConvention(culture);
.WithCamelCaseNamingConvention(culture, ignoreMigrationTable);

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

return optionsBuilder;
}

public static DbContextOptionsBuilder<TContext> UseCamelCaseNamingConvention<TContext>(
this DbContextOptionsBuilder<TContext> optionsBuilder,
CultureInfo? culture = null)
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder,
CultureInfo? culture = null,
bool ignoreMigrationTable = false)
where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>)UseCamelCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture);
=> (DbContextOptionsBuilder<TContext>)UseCamelCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture, ignoreMigrationTable);
}
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ SELECT c.id, c.full_name
WHERE c.full_name = 'John Doe';
```

## Ignoring the Migration Table `__EFMigrationsHistory`

To make migrations of existing databases more robust one might want to leave the migrations table out of the naming conventions.

```c#
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseNpgsql(...)
.UseSnakeCaseNamingConvention(ignoreMigrationTable: true);
```

## Supported naming conventions

* UseSnakeCaseNamingConvention: `FullName` becomes `full_name`
Expand Down