mirror of
https://github.com/fergalmoran/EFCore.NamingConventions.git
synced 2025-12-22 09:38:21 +00:00
@@ -9,6 +9,10 @@
|
||||
<PackageReference Update="xunit.runner.visualstudio" Version="2.4.3" />
|
||||
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.1" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.1" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Specification.Tests" Version="5.0.1" />
|
||||
<PackageReference Update="Microsoft.Extensions.Configuration" Version="5.0.0" />
|
||||
<PackageReference Update="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
|
||||
<PackageReference Update="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -9,7 +9,12 @@
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Specification.Tests" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Specification.Tests" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
103
EFCore.NamingConventions.Test/EndToEndTest.cs
Normal file
103
EFCore.NamingConventions.Test/EndToEndTest.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Xunit;
|
||||
|
||||
namespace EFCore.NamingConventions.Test
|
||||
{
|
||||
public class EndToEndTest : IClassFixture<EndToEndTest.EndToEndTestFixture>
|
||||
{
|
||||
public EndToEndTest(EndToEndTestFixture fixture)
|
||||
=> Fixture = fixture;
|
||||
|
||||
[Fact]
|
||||
public void Table_splitting()
|
||||
{
|
||||
using var context = CreateContext();
|
||||
|
||||
var split1EntityType = context.Model.FindEntityType(typeof(Split1));
|
||||
var split2EntityType = context.Model.FindEntityType(typeof(Split2));
|
||||
|
||||
var table = StoreObjectIdentifier.Create(split1EntityType, StoreObjectType.Table)!.Value;
|
||||
Assert.Equal(table, StoreObjectIdentifier.Create(split2EntityType, StoreObjectType.Table));
|
||||
|
||||
Assert.Equal("common", split1EntityType.FindProperty("Common").GetColumnName(table));
|
||||
Assert.Equal("split2_common", split2EntityType.FindProperty("Common").GetColumnName(table));
|
||||
|
||||
var split1 = context.Set<Split1>().Include(s1 => s1.Split2).Single();
|
||||
Assert.Equal(100, split1.Common);
|
||||
Assert.Equal(101, split1.Split2.Common);
|
||||
}
|
||||
|
||||
TestContext CreateContext() => Fixture.CreateContext();
|
||||
|
||||
readonly EndToEndTestFixture Fixture;
|
||||
|
||||
public class TestContext : DbContext
|
||||
{
|
||||
public TestContext(SqliteConnection connection)
|
||||
: base(new DbContextOptionsBuilder<TestContext>()
|
||||
.UseSqlite(connection)
|
||||
.UseSnakeCaseNamingConvention()
|
||||
.Options)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Split1>(e =>
|
||||
{
|
||||
e.ToTable("split");
|
||||
e.HasOne(s1 => s1.Split2).WithOne(s2 => s2.Split1).HasForeignKey<Split2>(s2 => s2.Id);
|
||||
e.HasData(new Split1 { Id = 1, OneProp = 1, Common = 100 });
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Split2>(e =>
|
||||
{
|
||||
e.ToTable("split");
|
||||
e.HasData(new Split2 { Id = 1, TwoProp = 2, Common = 101 });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class Split1
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int OneProp { get; set; }
|
||||
public int Common { get; set; }
|
||||
|
||||
public Split2 Split2 { get; set; }
|
||||
}
|
||||
|
||||
public class Split2
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int TwoProp { get; set; }
|
||||
public int Common { get; set; }
|
||||
|
||||
public Split1 Split1 { get; set; }
|
||||
}
|
||||
|
||||
public class EndToEndTestFixture : IDisposable
|
||||
{
|
||||
private readonly SqliteConnection _connection;
|
||||
|
||||
public TestContext CreateContext() => new(_connection);
|
||||
|
||||
public EndToEndTestFixture()
|
||||
{
|
||||
_connection = new SqliteConnection("Filename=:memory:");
|
||||
_connection.Open();
|
||||
using var context = new TestContext(_connection);
|
||||
context.Database.EnsureCreated();
|
||||
}
|
||||
|
||||
public void Dispose() => _connection.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,6 +133,54 @@ namespace EFCore.NamingConventions.Test
|
||||
Assert.Equal("ix_simple_blog_indexed_property", entityType.GetIndexes().Single().GetDatabaseName());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Table_splitting()
|
||||
{
|
||||
var model = BuildModel(b =>
|
||||
{
|
||||
b.Entity("One", e =>
|
||||
{
|
||||
e.ToTable("table");
|
||||
e.Property<int>("Id");
|
||||
e.Property<int>("OneProp");
|
||||
e.Property<int>("Common");
|
||||
|
||||
e.HasOne("Two").WithOne().HasForeignKey("Two", "Id");
|
||||
});
|
||||
|
||||
b.Entity("Two", e =>
|
||||
{
|
||||
e.ToTable("table");
|
||||
e.Property<int>("Id");
|
||||
e.Property<int>("TwoProp");
|
||||
e.Property<int>("Common");
|
||||
});
|
||||
});
|
||||
|
||||
var oneEntityType = model.FindEntityType("One");
|
||||
var twoEntityType = model.FindEntityType("Two");
|
||||
|
||||
var table = StoreObjectIdentifier.Create(oneEntityType, StoreObjectType.Table)!.Value;
|
||||
Assert.Equal(table, StoreObjectIdentifier.Create(twoEntityType, StoreObjectType.Table));
|
||||
|
||||
Assert.Equal("table", oneEntityType.GetTableName());
|
||||
Assert.Equal("one_prop", oneEntityType.FindProperty("OneProp").GetColumnName(table));
|
||||
|
||||
Assert.Equal("table", twoEntityType.GetTableName());
|
||||
Assert.Equal("two_prop", twoEntityType.FindProperty("TwoProp").GetColumnName(table));
|
||||
|
||||
var foreignKey = twoEntityType.GetForeignKeys().Single();
|
||||
Assert.Same(oneEntityType.FindPrimaryKey(), foreignKey.PrincipalKey);
|
||||
Assert.Same(twoEntityType.FindPrimaryKey().Properties.Single(), foreignKey.Properties.Single());
|
||||
Assert.Equal(oneEntityType.FindPrimaryKey().GetName(), twoEntityType.FindPrimaryKey().GetName());
|
||||
|
||||
Assert.Equal(
|
||||
foreignKey.PrincipalKey.Properties.Single().GetColumnName(table),
|
||||
foreignKey.Properties.Single().GetColumnName(table));
|
||||
|
||||
Assert.Empty(oneEntityType.GetForeignKeys());
|
||||
}
|
||||
|
||||
#region Owned entities
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace EFCore.NamingConventions.Internal
|
||||
public class NameRewritingConvention :
|
||||
IEntityTypeAddedConvention, IEntityTypeAnnotationChangedConvention, IPropertyAddedConvention,
|
||||
IForeignKeyOwnershipChangedConvention, IKeyAddedConvention, IForeignKeyAddedConvention,
|
||||
IIndexAddedConvention, IEntityTypeBaseTypeChangedConvention
|
||||
IIndexAddedConvention, IEntityTypeBaseTypeChangedConvention, IModelFinalizingConvention
|
||||
{
|
||||
private static readonly StoreObjectType[] _storeObjectTypes
|
||||
= { StoreObjectType.Table, StoreObjectType.View, StoreObjectType.Function, StoreObjectType.SqlQuery};
|
||||
@@ -70,7 +70,7 @@ namespace EFCore.NamingConventions.Internal
|
||||
var entityType = propertyBuilder.Metadata.DeclaringEntityType;
|
||||
var property = propertyBuilder.Metadata;
|
||||
|
||||
property.SetColumnName(_namingNameRewriter.RewriteName(property.GetColumnBaseName()));
|
||||
propertyBuilder.HasColumnName(_namingNameRewriter.RewriteName(property.GetColumnBaseName()));
|
||||
|
||||
foreach (var storeObjectType in _storeObjectTypes)
|
||||
{
|
||||
@@ -80,7 +80,7 @@ namespace EFCore.NamingConventions.Internal
|
||||
|
||||
if (property.GetColumnNameConfigurationSource(identifier.Value) == ConfigurationSource.Convention)
|
||||
{
|
||||
property.SetColumnName(
|
||||
propertyBuilder.HasColumnName(
|
||||
_namingNameRewriter.RewriteName(property.GetColumnName(identifier.Value)), identifier.Value);
|
||||
}
|
||||
}
|
||||
@@ -124,41 +124,34 @@ namespace EFCore.NamingConventions.Internal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, we need to apply the entity type prefix to all the owned properties, since
|
||||
// the convention that normally does this (SharedTableConvention) runs at model finalization
|
||||
// time, and will not overwrite our own rewritten column names.
|
||||
foreach (var property in ownedEntityType.GetProperties()
|
||||
.Except(ownedEntityType.FindPrimaryKey().Properties)
|
||||
.Where(p => p.Builder.CanSetColumnName(null)))
|
||||
{
|
||||
var columnName = property.GetColumnBaseName();
|
||||
var prefix = ownedEntityType.ShortName() + '_';
|
||||
|
||||
if (columnName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
columnName = columnName[prefix.Length..];
|
||||
}
|
||||
|
||||
var rewrittenPrefix = _namingNameRewriter.RewriteName(prefix);
|
||||
if (!columnName.StartsWith(rewrittenPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
columnName = rewrittenPrefix + columnName;
|
||||
}
|
||||
|
||||
// TODO: We should uniquify, but we don't know about all the entity types mapped
|
||||
// to this table. SharedTableConvention does its thing during model finalization,
|
||||
// so it has the full list of entities and can uniquify.
|
||||
property.Builder.HasColumnName(columnName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessEntityTypeAnnotationChanged(IConventionEntityTypeBuilder entityTypeBuilder, string name,
|
||||
IConventionAnnotation annotation, IConventionAnnotation oldAnnotation, IConventionContext<IConventionAnnotation> context)
|
||||
{
|
||||
if (name == RelationalAnnotationNames.TableName &&
|
||||
oldAnnotation?.Value is null &&
|
||||
if (name != RelationalAnnotationNames.TableName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The table's name is changing - rewrite keys, index names
|
||||
if (entityTypeBuilder.Metadata.FindPrimaryKey() is IConventionKey primaryKey)
|
||||
{
|
||||
primaryKey.Builder.HasName(_namingNameRewriter.RewriteName(primaryKey.GetDefaultName()));
|
||||
}
|
||||
|
||||
foreach (var foreignKey in entityTypeBuilder.Metadata.GetForeignKeys())
|
||||
{
|
||||
foreignKey.Builder.HasConstraintName(_namingNameRewriter.RewriteName(foreignKey.GetDefaultName()));
|
||||
}
|
||||
|
||||
foreach (var index in entityTypeBuilder.Metadata.GetIndexes())
|
||||
{
|
||||
index.Builder.HasDatabaseName(_namingNameRewriter.RewriteName(index.GetDefaultDatabaseName()));
|
||||
}
|
||||
|
||||
if (oldAnnotation?.Value is null &&
|
||||
annotation?.Value is not null &&
|
||||
entityTypeBuilder.Metadata.FindOwnership() is IConventionForeignKey ownership &&
|
||||
(string)annotation.Value != ownership.PrincipalEntityType.GetTableName())
|
||||
@@ -183,14 +176,53 @@ namespace EFCore.NamingConventions.Internal
|
||||
}
|
||||
|
||||
public void ProcessForeignKeyAdded(IConventionForeignKeyBuilder relationshipBuilder, IConventionContext<IConventionForeignKeyBuilder> context)
|
||||
=> relationshipBuilder.HasConstraintName(_namingNameRewriter.RewriteName(relationshipBuilder.Metadata.GetConstraintName()));
|
||||
=> relationshipBuilder.HasConstraintName(_namingNameRewriter.RewriteName(relationshipBuilder.Metadata.GetDefaultName()));
|
||||
|
||||
public void ProcessKeyAdded(IConventionKeyBuilder keyBuilder, IConventionContext<IConventionKeyBuilder> context)
|
||||
=> keyBuilder.HasName(_namingNameRewriter.RewriteName(keyBuilder.Metadata.GetName()));
|
||||
=> keyBuilder.HasName(_namingNameRewriter.RewriteName(keyBuilder.Metadata.GetDefaultName()));
|
||||
|
||||
public void ProcessIndexAdded(
|
||||
IConventionIndexBuilder indexBuilder,
|
||||
IConventionContext<IConventionIndexBuilder> context)
|
||||
=> indexBuilder.HasDatabaseName(_namingNameRewriter.RewriteName(indexBuilder.Metadata.GetDatabaseName()));
|
||||
=> indexBuilder.HasDatabaseName(_namingNameRewriter.RewriteName(indexBuilder.Metadata.GetDefaultDatabaseName()));
|
||||
|
||||
/// <summary>
|
||||
/// EF Core's <see cref="SharedTableConvention" /> runs at model finalization time, and adds entity type prefixes to
|
||||
/// clashing columns. These prefixes also needs to be rewritten by us, so we run after that convention to do that.
|
||||
/// </summary>
|
||||
public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
|
||||
{
|
||||
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
|
||||
{
|
||||
foreach (var property in entityType.GetProperties())
|
||||
{
|
||||
var columnName = property.GetColumnBaseName();
|
||||
if (columnName.StartsWith(entityType.ShortName() + '_', StringComparison.Ordinal))
|
||||
{
|
||||
property.Builder.HasColumnName(
|
||||
_namingNameRewriter.RewriteName(entityType.ShortName()) +
|
||||
columnName.Substring(entityType.ShortName().Length));
|
||||
}
|
||||
|
||||
foreach (var storeObjectType in _storeObjectTypes)
|
||||
{
|
||||
var identifier = StoreObjectIdentifier.Create(entityType, storeObjectType);
|
||||
if (identifier is null)
|
||||
continue;
|
||||
|
||||
if (property.GetColumnNameConfigurationSource(identifier.Value) == ConfigurationSource.Convention)
|
||||
{
|
||||
columnName = property.GetColumnName(identifier.Value);
|
||||
if (columnName.StartsWith(entityType.ShortName() + '_', StringComparison.Ordinal))
|
||||
{
|
||||
property.Builder.HasColumnName(
|
||||
_namingNameRewriter.RewriteName(entityType.ShortName()) +
|
||||
columnName.Substring(entityType.ShortName().Length));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace EFCore.NamingConventions.Internal
|
||||
conventionSet.ForeignKeyAddedConventions.Add(convention);
|
||||
conventionSet.IndexAddedConventions.Add(convention);
|
||||
conventionSet.EntityTypeBaseTypeChangedConventions.Add(convention);
|
||||
conventionSet.ModelFinalizingConventions.Add(convention);
|
||||
|
||||
return conventionSet;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user