Big overhaul for 5.0 (#47)

* Use EF Core 5.0
* Fixes around TPH, owned entity management
* Redid tests

Fixes #45
Closes #46
Fixes #41
This commit is contained in:
Shay Rojansky
2020-12-10 14:52:26 +02:00
committed by GitHub
parent 36a41daa87
commit 3f62586c3f
12 changed files with 388 additions and 132 deletions

View File

@@ -13,7 +13,7 @@ jobs:
- name: Setup .NET Core SDK - name: Setup .NET Core SDK
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: '3.1.x' dotnet-version: '5.0.x'
- name: Test - name: Test
run: dotnet test run: dotnet test

View File

@@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<IncludeSymbols>true</IncludeSymbols> <IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
<LangVersion>9.0</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,13 +1,14 @@
<Project> <Project>
<ItemGroup> <ItemGroup>
<PackageReference Update="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0-rc.1.20451.14" /> <PackageReference Update="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore" Version="5.0.0-rc.1.20451.13" /> <PackageReference Update="Microsoft.EntityFrameworkCore" Version="5.0.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Relational" Version="5.0.0-rc.1.20451.13" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Relational" Version="5.0.1" />
<!-- Test --> <!-- Test -->
<PackageReference Update="xunit" Version="2.4.1" /> <PackageReference Update="xunit" Version="2.4.1" />
<PackageReference Update="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Update="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="16.7.1" /> <PackageReference Update="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.0-rc.1.20451.13" /> <PackageReference Update="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Specification.Tests" Version="5.0.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net5.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -9,6 +9,7 @@
<PackageReference Include="xunit.runner.visualstudio" /> <PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" /> <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Specification.Tests" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,9 +1,12 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using EFCore.NamingConventions.Internal;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit; using Xunit;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
@@ -12,146 +15,272 @@ namespace EFCore.NamingConventions.Test
public class NameRewritingConventionTest public class NameRewritingConventionTest
{ {
[Fact] [Fact]
public void Table_name_is_rewritten() public void Table_name()
{ {
using var context = CreateContext(); var entityType = BuildEntityType("SimpleBlog", _ => {});
var entityType = context.Model.FindEntityType(typeof(SimpleBlog));
Assert.Equal("simple_blog", entityType.GetTableName()); Assert.Equal("simple_blog", entityType.GetTableName());
} }
[Fact] [Fact]
public void Column_name_is_rewritten() public void Column_name()
{ {
using var context = CreateContext(); var entityType = BuildEntityType("SimpleBlog", e => e.Property<int>("SimpleBlogId"));
var entityType = context.Model.FindEntityType(typeof(SimpleBlog));
Assert.Equal("id_with_special_name", entityType.FindProperty("IdWithSpecialName").GetColumnName()); Assert.Equal("simple_blog_id", entityType.FindProperty("SimpleBlogId")
Assert.Equal("full_name", entityType.FindProperty("FullName").GetColumnName()); .GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
} }
[Fact] [Fact]
public void Column_name_is_rewritten_in_turkish() public void Column_name_on_view()
{ {
using var context = CreateContext(CultureInfo.CreateSpecificCulture("tr-TR")); var entityType = BuildEntityType("SimpleBlog", e =>
var entityType = context.Model.FindEntityType(typeof(SimpleBlog)); {
Assert.Equal("ıd_with_special_name", entityType.FindProperty("IdWithSpecialName").GetColumnName()); e.ToTable("SimpleBlogTable");
Assert.Equal("full_name", entityType.FindProperty("FullName").GetColumnName()); e.ToView("SimpleBlogView");
e.ToFunction("SimpleBlogFunction");
e.Property<int>("SimpleBlogId");
});
foreach (var type in new[] { StoreObjectType.Table, StoreObjectType.View, StoreObjectType.Function })
{
Assert.Equal("simple_blog_id", entityType.FindProperty("SimpleBlogId")
.GetColumnName(StoreObjectIdentifier.Create(entityType, type)!.Value));
}
} }
[Fact] [Fact]
public void Column_name_is_rewritten_in_invariant() public void Column_name_turkish_culture()
{ {
using var context = CreateContext(CultureInfo.InvariantCulture); var entityType = BuildEntityType(
var entityType = context.Model.FindEntityType(typeof(SimpleBlog)); "SimpleBlog",
Assert.Equal("id_with_special_name", entityType.FindProperty("IdWithSpecialName").GetColumnName()); e => e.Property<int>("SimpleBlogId"),
Assert.Equal("full_name", entityType.FindProperty("FullName").GetColumnName()); CultureInfo.CreateSpecificCulture("tr-TR"));
Assert.Equal("simple_blog_ıd", entityType.FindProperty("SimpleBlogId")
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
} }
[Fact] [Fact]
public void Owned_entity_is_rewritten() public void Column_name_invariant_culture()
{ {
using var context = CreateContext(); var entityType = BuildEntityType(
var entityType = context.Model.FindEntityType(typeof(OwnedStatistics1)); "SimpleBlog",
Assert.Equal("simple_blog", entityType.GetTableName()); e => e.Property<int>("SimpleBlogId"),
var property = entityType.GetProperty(nameof(OwnedStatistics1.SomeStatistic)); CultureInfo.InvariantCulture);
Assert.Equal("owned_statistics1_some_statistic", property.GetColumnName());
Assert.Equal("simple_blog_id", entityType.FindProperty("SimpleBlogId")
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
} }
[Fact] [Fact]
public void Owned_entity_split_is_rewritten() public void Primary_key_name()
{ {
using var context = CreateContext(); var entityType = BuildEntityType("SimpleBlog", e =>
var entityType = context.Model.FindEntityType(typeof(OwnedStatistics2)); {
Assert.Equal("OwnedStatisticsSplit", entityType.GetTableName()); e.Property<int>("SimpleBlogId");
var property = entityType.GetProperty(nameof(OwnedStatistics2.SomeStatistic)); e.HasKey("SimpleBlogId");
Assert.Equal("some_statistic", property.GetColumnName()); });
}
[Fact]
public void Primary_key_name_is_rewritten()
{
using var context = CreateContext();
var entityType = context.Model.FindEntityType(typeof(SimpleBlog));
Assert.Equal("pk_simple_blog", entityType.GetKeys().Single(k => k.IsPrimaryKey()).GetName()); Assert.Equal("pk_simple_blog", entityType.GetKeys().Single(k => k.IsPrimaryKey()).GetName());
} }
[Fact] [Fact]
public void Alternative_key_name_is_rewritten() public void Alternative_key_name()
{ {
using var context = CreateContext(); var entityType = BuildEntityType("SimpleBlog", e =>
var entityType = context.Model.FindEntityType(typeof(SimpleBlog)); {
e.Property<int>("SimpleBlogId");
e.Property<int>("SomeAlternativeKey");
e.HasKey("SimpleBlogId");
e.HasAlternateKey("SomeAlternativeKey");
});
Assert.Equal("ak_simple_blog_some_alternative_key", entityType.GetKeys().Single(k => !k.IsPrimaryKey()).GetName()); Assert.Equal("ak_simple_blog_some_alternative_key", entityType.GetKeys().Single(k => !k.IsPrimaryKey()).GetName());
} }
[Fact] [Fact]
public void Foreign_key_name_is_rewritten() public void Foreign_key_name()
{ {
using var context = CreateContext(); var model = BuildModel(b =>
var entityType = context.Model.FindEntityType(typeof(Post)); {
Assert.Equal("fk_post_simple_blog_blog_id", entityType.GetForeignKeys().Single().GetConstraintName()); b.Entity("Blog", e =>
{
e.Property<int>("BlogId");
e.HasKey("BlogId");
e.HasMany("Post").WithOne("Blog");
});
b.Entity("Post", e =>
{
e.Property<int>("PostId");
e.Property<int>("BlogId");
e.HasKey("PostId");
});
});
var entityType = model.FindEntityType("Post");
Assert.Equal("fk_post_blog_blog_id", entityType.GetForeignKeys().Single().GetConstraintName());
} }
[Fact] [Fact]
public void Index_name_is_rewritten() public void Index_name()
{ {
using var context = CreateContext(); var entityType = BuildEntityType("SimpleBlog", e =>
var entityType = context.Model.FindEntityType(typeof(SimpleBlog)); {
Assert.Equal("ix_simple_blog_full_name", entityType.GetIndexes().Single().GetDatabaseName()); e.Property<int>("IndexedProperty");
e.HasIndex("IndexedProperty");
});
Assert.Equal("ix_simple_blog_indexed_property", entityType.GetIndexes().Single().GetDatabaseName());
} }
#region Owned entities
[Fact]
public void Owned_entity_with_table_splitting()
{
var model = BuildModel(b =>
{
b.Entity("SimpleBlog", e =>
{
e.OwnsOne("OwnedEntity", "Nav", o => o.Property<int>("OwnedProperty"));
});
});
var entityType = model.FindEntityType("OwnedEntity");
Assert.Equal("pk_simple_blog", entityType.FindPrimaryKey().GetName());
Assert.Equal("simple_blog", entityType.GetTableName());
Assert.Equal("owned_property", entityType.FindProperty("OwnedProperty")
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
}
[Fact]
public void Owned_entity_without_table_splitting()
{
var model = BuildModel(b =>
{
b.Entity("SimpleBlog", e =>
{
e.Property<int>("SimpleBlogId");
e.HasKey("SimpleBlogId");
e.OwnsOne("OwnedEntity", "Nav", o =>
{
o.ToTable("another_table");
o.Property<int>("OwnedProperty");
});
});
});
var entityType = model.FindEntityType("OwnedEntity");
Assert.Equal("pk_another_table", entityType.FindPrimaryKey().GetName());
Assert.Equal("another_table", entityType.GetTableName());
Assert.Equal("owned_property", entityType.FindProperty("OwnedProperty")
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
}
[Fact]
public void Owned_entity_with_view_without_table_splitting()
{
var model = BuildModel(b =>
{
b.Entity("OwnedEntity", e =>
{
e.ToTable("OwnedEntityTable");
e.ToView("OwnedEntityView");
e.Property<int>("OwnedProperty");
});
b.Entity("SimpleBlog", e => e.OwnsOne("OwnedEntity", "Nav"));
});
var entityType = model.FindEntityType("OwnedEntity");
Assert.Equal("OwnedEntityTable", entityType.GetTableName());
Assert.Equal("OwnedEntityView", entityType.GetViewName());
Assert.Equal("owned_property", entityType.FindProperty("OwnedProperty")
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
Assert.Equal("owned_property", entityType.FindProperty("OwnedProperty")
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.View)!.Value));
}
#endregion Owned entities
#region Inheritance
[Fact]
public void TPH()
{
var model = BuildModel(b =>
{
b.Entity("SimpleBlog", e =>
{
e.Property<int>("SimpleBlogId");
e.HasKey("SimpleBlogId");
});
b.Entity("FancyBlog", e =>
{
e.HasBaseType("SimpleBlog");
e.Property<int>("FancyProperty");
});
});
var simpleBlogEntityType = model.FindEntityType("SimpleBlog");
Assert.Equal("simple_blog", simpleBlogEntityType.GetTableName());
Assert.Equal("simple_blog_id", simpleBlogEntityType.FindProperty("SimpleBlogId")
.GetColumnName(StoreObjectIdentifier.Create(simpleBlogEntityType, StoreObjectType.Table)!.Value));
var fancyBlogEntityType = model.FindEntityType("FancyBlog");
Assert.Equal("simple_blog", fancyBlogEntityType.GetTableName());
Assert.Equal("fancy_property", fancyBlogEntityType.FindProperty("FancyProperty")
.GetColumnName(StoreObjectIdentifier.Create(fancyBlogEntityType, StoreObjectType.Table)!.Value));
}
[Fact]
public void TPT()
{
var model = BuildModel(b =>
{
b.Entity("SimpleBlog", e =>
{
e.Property<int>("SimpleBlogId");
e.HasKey("SimpleBlogId");
});
b.Entity("FancyBlog", e =>
{
e.HasBaseType("SimpleBlog");
e.ToTable("fancy_blog");
e.Property<int>("FancyProperty");
});
});
var simpleBlogEntityType = model.FindEntityType("SimpleBlog");
Assert.Equal("simple_blog", simpleBlogEntityType.GetTableName());
Assert.Equal("simple_blog_id", simpleBlogEntityType.FindProperty("SimpleBlogId")
.GetColumnName(StoreObjectIdentifier.Create(simpleBlogEntityType, StoreObjectType.Table)!.Value));
var fancyBlogEntityType = model.FindEntityType("FancyBlog");
Assert.Equal("fancy_blog", fancyBlogEntityType.GetTableName());
Assert.Equal("fancy_property", fancyBlogEntityType.FindProperty("FancyProperty")
.GetColumnName(StoreObjectIdentifier.Create(fancyBlogEntityType, StoreObjectType.Table)!.Value));
}
#endregion Inheritance
#region Support #region Support
TestContext CreateContext(CultureInfo culture = null) => new TestContext(builder => builder.UseSnakeCaseNamingConvention(culture)); private IModel BuildModel(Action<ModelBuilder> buildAction, CultureInfo cultureInfo = null)
public class TestContext : DbContext
{ {
private readonly Func<DbContextOptionsBuilder, DbContextOptionsBuilder> _useNamingConvention; var conventionSet = InMemoryTestHelpers.Instance.CreateConventionSetBuilder().CreateConventionSet();
public TestContext(Func<DbContextOptionsBuilder, DbContextOptionsBuilder> useNamingConvention) ConventionSet.Remove(conventionSet.ModelFinalizedConventions, typeof(ValidatingConvention));
=> _useNamingConvention = useNamingConvention;
protected override void OnModelCreating(ModelBuilder modelBuilder) var optionsBuilder = new DbContextOptionsBuilder();
=> modelBuilder.Entity<SimpleBlog>(e => optionsBuilder.UseSnakeCaseNamingConvention(cultureInfo);
{ new NamingConventionSetPlugin(optionsBuilder.Options).ModifyConventions(conventionSet);
e.HasIndex(b => b.FullName);
e.OwnsOne(b => b.OwnedStatistics1);
e.OwnsOne(b => b.OwnedStatistics2, s => s.ToTable("OwnedStatisticsSplit"));
e.HasAlternateKey(b => b.SomeAlternativeKey);
});
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) var builder = new ModelBuilder(conventionSet);
=> _useNamingConvention(optionsBuilder.UseInMemoryDatabase("test")); buildAction(builder);
return builder.FinalizeModel();
} }
public class SimpleBlog private IEntityType BuildEntityType(string entityTypeName, Action<EntityTypeBuilder> buildAction, CultureInfo cultureInfo = null)
{ => BuildModel(b => buildAction(b.Entity(entityTypeName)), cultureInfo).GetEntityTypes().Single();
[Key]
public int IdWithSpecialName { get; set; }
public string FullName { get; set; }
public int SomeAlternativeKey { get; set; }
public List<Post> Posts { get; set; }
public OwnedStatistics1 OwnedStatistics1 { get; set; }
public OwnedStatistics2 OwnedStatistics2 { get; set; }
}
public class Post
{
public int Id { get; set; }
public string FullName { get; set; }
public int BlogId { get; set; }
public SimpleBlog Blog { get; set; }
}
public class OwnedStatistics1
{
public int SomeStatistic { get; set; }
}
public class OwnedStatistics2
{
public int SomeStatistic { get; set; }
}
#endregion #endregion
} }

View File

@@ -0,0 +1,29 @@
// 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 Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.InMemory.Diagnostics.Internal;
using Microsoft.Extensions.DependencyInjection;
#pragma warning disable EF1001
// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore.TestUtilities
{
public class InMemoryTestHelpers : TestHelpers
{
protected InMemoryTestHelpers()
{
}
public static InMemoryTestHelpers Instance { get; } = new();
public override IServiceCollection AddProviderServices(IServiceCollection services)
=> services.AddEntityFrameworkInMemoryDatabase();
public override void UseProviderOptions(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseInMemoryDatabase(nameof(InMemoryTestHelpers));
public override LoggingDefinitions LoggingDefinitions { get; } = new InMemoryLoggingDefinitions();
}
}

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<VersionPrefix>5.0.0-rc1</VersionPrefix> <VersionPrefix>5.0.0</VersionPrefix>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../EFCore.NamingConventions.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>../EFCore.NamingConventions.snk</AssemblyOriginatorKeyFile>

View File

@@ -1,17 +1,22 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace EFCore.NamingConventions.Internal namespace EFCore.NamingConventions.Internal
{ {
public class NameRewritingConvention : public class NameRewritingConvention :
IEntityTypeAddedConvention, IEntityTypeAnnotationChangedConvention, IPropertyAddedConvention, IEntityTypeAddedConvention, IEntityTypeAnnotationChangedConvention, IPropertyAddedConvention,
IForeignKeyOwnershipChangedConvention, IKeyAddedConvention, IForeignKeyAddedConvention, IForeignKeyOwnershipChangedConvention, IKeyAddedConvention, IForeignKeyAddedConvention,
IIndexAddedConvention IIndexAddedConvention, IEntityTypeBaseTypeChangedConvention
{ {
private static readonly StoreObjectType[] _storeObjectTypes
= { StoreObjectType.Table, StoreObjectType.View, StoreObjectType.Function, StoreObjectType.SqlQuery};
private readonly INameRewriter _namingNameRewriter; private readonly INameRewriter _namingNameRewriter;
public NameRewritingConvention(INameRewriter nameRewriter) => _namingNameRewriter = nameRewriter; public NameRewritingConvention(INameRewriter nameRewriter) => _namingNameRewriter = nameRewriter;
@@ -21,17 +26,60 @@ namespace EFCore.NamingConventions.Internal
{ {
var entityType = entityTypeBuilder.Metadata; var entityType = entityTypeBuilder.Metadata;
// Only touch root entities for now (TPH). Revisit for TPT/TPC. // Note that the base type is null when the entity type is first added - a base type only gets added later
if (entityType.BaseType == null) // (see ProcessEntityTypeBaseTypeChanged). But we still have this check for safety.
if (entityType.BaseType is null)
{ {
entityTypeBuilder.ToTable(_namingNameRewriter.RewriteName(entityType.GetTableName()), entityType.GetSchema()); entityTypeBuilder.ToTable(_namingNameRewriter.RewriteName(entityType.GetTableName()), entityType.GetSchema());
} }
} }
public virtual void ProcessPropertyAdded( public void ProcessEntityTypeBaseTypeChanged(
IConventionPropertyBuilder propertyBuilder, IConventionContext<IConventionPropertyBuilder> context) IConventionEntityTypeBuilder entityTypeBuilder,
=> propertyBuilder.HasColumnName(_namingNameRewriter.RewriteName(propertyBuilder.Metadata.GetColumnName())); IConventionEntityType newBaseType,
IConventionEntityType oldBaseType,
IConventionContext<IConventionEntityType> context)
{
var entityType = entityTypeBuilder.Metadata;
if (newBaseType is null)
{
// The entity is getting removed from a hierarchy. Set the (rewritten) TableName.
entityTypeBuilder.ToTable(_namingNameRewriter.RewriteName(entityType.GetTableName()), entityType.GetSchema());
}
else
{
// The entity is getting a new base type (e.g. joining a hierarchy).
// If this is TPH, we remove the previously rewritten TableName (and non-rewritten Schema) which we set when the
// entity type was first added to the model (see ProcessEntityTypeAdded).
// If this is TPT, TableName and Schema are set explicitly, so the following will be ignored.
entityTypeBuilder.HasNoAnnotation(RelationalAnnotationNames.TableName);
entityTypeBuilder.HasNoAnnotation(RelationalAnnotationNames.Schema);
}
}
public virtual void ProcessPropertyAdded(
IConventionPropertyBuilder propertyBuilder,
IConventionContext<IConventionPropertyBuilder> context)
{
var entityType = propertyBuilder.Metadata.DeclaringEntityType;
var property = propertyBuilder.Metadata;
property.SetColumnName(_namingNameRewriter.RewriteName(property.GetColumnBaseName()));
foreach (var storeObjectType in _storeObjectTypes)
{
var identifier = StoreObjectIdentifier.Create(entityType, storeObjectType);
if (identifier is null)
continue;
if (property.GetColumnNameConfigurationSource(identifier.Value) == ConfigurationSource.Convention)
{
property.SetColumnName(
_namingNameRewriter.RewriteName(property.GetColumnName(identifier.Value)), identifier.Value);
}
}
}
public void ProcessForeignKeyOwnershipChanged(IConventionForeignKeyBuilder relationshipBuilder, IConventionContext<bool?> context) public void ProcessForeignKeyOwnershipChanged(IConventionForeignKeyBuilder relationshipBuilder, IConventionContext<bool?> context)
{ {
@@ -46,12 +94,30 @@ namespace EFCore.NamingConventions.Internal
// Reset the table name which we've set when the entity type was added // Reset the table name which we've set when the entity type was added
// If table splitting was configured by explicitly setting the table name, the following // If table splitting was configured by explicitly setting the table name, the following
// does nothing. // does nothing.
ownedEntityType.SetTableName(ownedEntityType.GetDefaultTableName()); ownedEntityType.Builder.HasNoAnnotation(RelationalAnnotationNames.TableName);
ownedEntityType.Builder.HasNoAnnotation(RelationalAnnotationNames.Schema);
// Also need to reset all primary key properties // Also need to reset all primary key properties
foreach (var keyProperty in ownedEntityType.FindPrimaryKey().Properties) foreach (var keyProperty in ownedEntityType.FindPrimaryKey().Properties)
{ {
keyProperty.SetColumnName(keyProperty.GetDefaultColumnName()); keyProperty.Builder.HasNoAnnotation(RelationalAnnotationNames.ColumnName);
foreach (var storeObjectType in _storeObjectTypes)
{
var identifier = StoreObjectIdentifier.Create(ownedEntityType, storeObjectType);
if (identifier is null)
continue;
if (keyProperty.GetColumnNameConfigurationSource(identifier.Value) == ConfigurationSource.Convention)
{
#pragma warning disable EF1001
// TODO: Using internal APIs to remove the override
var tableOverrides = (IDictionary<StoreObjectIdentifier, RelationalPropertyOverrides>)
keyProperty[RelationalAnnotationNames.RelationalOverrides];
tableOverrides.Remove(identifier.Value);
#pragma warning restore EF1001
}
}
} }
// Finally, we need to apply the entity type prefix to all the owned properties, since // Finally, we need to apply the entity type prefix to all the owned properties, since
@@ -61,7 +127,7 @@ namespace EFCore.NamingConventions.Internal
.Except(ownedEntityType.FindPrimaryKey().Properties) .Except(ownedEntityType.FindPrimaryKey().Properties)
.Where(p => p.Builder.CanSetColumnName(null))) .Where(p => p.Builder.CanSetColumnName(null)))
{ {
var columnName = property.GetColumnName(); var columnName = property.GetColumnBaseName();
var prefix = _namingNameRewriter.RewriteName(ownedEntityType.ShortName()); var prefix = _namingNameRewriter.RewriteName(ownedEntityType.ShortName());
if (!columnName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) if (!columnName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{ {
@@ -81,17 +147,26 @@ namespace EFCore.NamingConventions.Internal
IConventionAnnotation annotation, IConventionAnnotation oldAnnotation, IConventionContext<IConventionAnnotation> context) IConventionAnnotation annotation, IConventionAnnotation oldAnnotation, IConventionContext<IConventionAnnotation> context)
{ {
if (name == RelationalAnnotationNames.TableName && if (name == RelationalAnnotationNames.TableName &&
annotation?.GetConfigurationSource() == ConfigurationSource.Explicit && oldAnnotation?.Value is null &&
entityTypeBuilder.Metadata.FindOwnership() != null) annotation?.Value is not null &&
entityTypeBuilder.Metadata.FindOwnership() is IConventionForeignKey ownership &&
(string)annotation.Value != ownership.PrincipalEntityType.GetTableName())
{ {
// An owned entity's table is being set explicitly - this is the trigger to do table // An owned entity's table is being set explicitly - this is the trigger to undo table splitting (which is the default).
// splitting. When the entity became owned, we prefixed all of its properties - we
// must now undo that. // When the entity became owned, we prefixed all of its properties - we must now undo that.
foreach (var property in entityTypeBuilder.Metadata.GetProperties() foreach (var property in entityTypeBuilder.Metadata.GetProperties()
.Except(entityTypeBuilder.Metadata.FindPrimaryKey().Properties) .Except(entityTypeBuilder.Metadata.FindPrimaryKey().Properties)
.Where(p => p.Builder.CanSetColumnName(null))) .Where(p => p.Builder.CanSetColumnName(null)))
{ {
property.Builder.HasColumnName(_namingNameRewriter.RewriteName(property.GetDefaultColumnName())); property.Builder.HasColumnName(_namingNameRewriter.RewriteName(property.GetDefaultColumnBaseName()));
}
// We previously rewrote the owned entity's primary key name, when the owned entity was still in table splitting.
// Now that its getting its own table, rewrite the primary key constraint name again.
if (entityTypeBuilder.Metadata.FindPrimaryKey() is IConventionKey key)
{
key.Builder.HasName(_namingNameRewriter.RewriteName(key.GetDefaultName()));
} }
} }
} }

View File

@@ -38,6 +38,7 @@ namespace EFCore.NamingConventions.Internal
conventionSet.KeyAddedConventions.Add(convention); conventionSet.KeyAddedConventions.Add(convention);
conventionSet.ForeignKeyAddedConventions.Add(convention); conventionSet.ForeignKeyAddedConventions.Add(convention);
conventionSet.IndexAddedConventions.Add(convention); conventionSet.IndexAddedConventions.Add(convention);
conventionSet.EntityTypeBaseTypeChangedConventions.Add(convention);
return conventionSet; return conventionSet;
} }

View File

@@ -23,7 +23,7 @@ namespace EFCore.NamingConventions.Internal
public virtual DbContextOptionsExtensionInfo Info => _info ??= new ExtensionInfo(this); public virtual DbContextOptionsExtensionInfo Info => _info ??= new ExtensionInfo(this);
protected virtual NamingConventionsOptionsExtension Clone() => new NamingConventionsOptionsExtension(this); protected virtual NamingConventionsOptionsExtension Clone() => new(this);
internal virtual NamingConvention NamingConvention => _namingConvention; internal virtual NamingConvention NamingConvention => _namingConvention;
internal virtual CultureInfo Culture => _culture; internal virtual CultureInfo Culture => _culture;

View File

@@ -8,11 +8,13 @@ namespace Microsoft.EntityFrameworkCore
{ {
public static class NamingConventionsExtensions public static class NamingConventionsExtensions
{ {
public static DbContextOptionsBuilder UseSnakeCaseNamingConvention([NotNull] this DbContextOptionsBuilder optionsBuilder , CultureInfo culture = null) public static DbContextOptionsBuilder UseSnakeCaseNamingConvention(
[NotNull] this DbContextOptionsBuilder optionsBuilder , CultureInfo culture = null)
{ {
Check.NotNull(optionsBuilder, nameof(optionsBuilder)); Check.NotNull(optionsBuilder, nameof(optionsBuilder));
var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>() ?? new NamingConventionsOptionsExtension()) var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>()
?? new NamingConventionsOptionsExtension())
.WithSnakeCaseNamingConvention(culture); .WithSnakeCaseNamingConvention(culture);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
@@ -20,15 +22,18 @@ namespace Microsoft.EntityFrameworkCore
return optionsBuilder; return optionsBuilder;
} }
public static DbContextOptionsBuilder<TContext> UseSnakeCaseNamingConvention<TContext>([NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder , CultureInfo culture = null) public static DbContextOptionsBuilder<TContext> UseSnakeCaseNamingConvention<TContext>(
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder , CultureInfo culture = null)
where TContext : DbContext where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>)UseSnakeCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture); => (DbContextOptionsBuilder<TContext>)UseSnakeCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture);
public static DbContextOptionsBuilder UseLowerCaseNamingConvention([NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) public static DbContextOptionsBuilder UseLowerCaseNamingConvention(
[NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null)
{ {
Check.NotNull(optionsBuilder, nameof(optionsBuilder)); Check.NotNull(optionsBuilder, nameof(optionsBuilder));
var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>() ?? new NamingConventionsOptionsExtension()) var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>()
?? new NamingConventionsOptionsExtension())
.WithLowerCaseNamingConvention(culture); .WithLowerCaseNamingConvention(culture);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
@@ -36,15 +41,18 @@ namespace Microsoft.EntityFrameworkCore
return optionsBuilder; return optionsBuilder;
} }
public static DbContextOptionsBuilder<TContext> UseLowerCaseNamingConvention<TContext>([NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder, CultureInfo culture = null) public static DbContextOptionsBuilder<TContext> UseLowerCaseNamingConvention<TContext>(
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder, CultureInfo culture = null)
where TContext : DbContext where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>)UseLowerCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder ,culture); => (DbContextOptionsBuilder<TContext>)UseLowerCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder ,culture);
public static DbContextOptionsBuilder UseUpperCaseNamingConvention([NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) public static DbContextOptionsBuilder UseUpperCaseNamingConvention(
[NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null)
{ {
Check.NotNull(optionsBuilder, nameof(optionsBuilder)); Check.NotNull(optionsBuilder, nameof(optionsBuilder));
var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>() ?? new NamingConventionsOptionsExtension()) var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>()
?? new NamingConventionsOptionsExtension())
.WithUpperCaseNamingConvention(culture); .WithUpperCaseNamingConvention(culture);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
@@ -52,15 +60,18 @@ namespace Microsoft.EntityFrameworkCore
return optionsBuilder; return optionsBuilder;
} }
public static DbContextOptionsBuilder<TContext> UseUpperCaseNamingConvention<TContext>([NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder, CultureInfo culture = null) public static DbContextOptionsBuilder<TContext> UseUpperCaseNamingConvention<TContext>(
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder, CultureInfo culture = null)
where TContext : DbContext where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>)UseUpperCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture); => (DbContextOptionsBuilder<TContext>)UseUpperCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture);
public static DbContextOptionsBuilder UseUpperSnakeCaseNamingConvention([NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) public static DbContextOptionsBuilder UseUpperSnakeCaseNamingConvention(
[NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null)
{ {
Check.NotNull(optionsBuilder, nameof(optionsBuilder)); Check.NotNull(optionsBuilder, nameof(optionsBuilder));
var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>() ?? new NamingConventionsOptionsExtension()) var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>()
?? new NamingConventionsOptionsExtension())
.WithUpperSnakeCaseNamingConvention(culture); .WithUpperSnakeCaseNamingConvention(culture);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
@@ -68,7 +79,8 @@ namespace Microsoft.EntityFrameworkCore
return optionsBuilder; return optionsBuilder;
} }
public static DbContextOptionsBuilder<TContext> UseUpperSnakeCaseNamingConvention<TContext>([NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder, CultureInfo culture = null) public static DbContextOptionsBuilder<TContext> UseUpperSnakeCaseNamingConvention<TContext>(
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder, CultureInfo culture = null)
where TContext : DbContext where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>)UseUpperSnakeCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture); => (DbContextOptionsBuilder<TContext>)UseUpperSnakeCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture);
} }

7
global.json Normal file
View File

@@ -0,0 +1,7 @@
{
"sdk": {
"version": "5.0.100",
"rollForward": "latestMajor",
"allowPrerelease": "false"
}
}