Fix owned entity support (#58)

* Also avoid rewriting primary key name with TPT
* Also redo tests again to be properly end-to-end

Fixes #50
This commit is contained in:
Shay Rojansky
2021-01-09 15:35:44 +01:00
committed by GitHub
parent 4cb06461dd
commit ea9e59ca80
5 changed files with 305 additions and 427 deletions

View File

@@ -11,7 +11,6 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" /> <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Specification.Tests" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Specification.Tests" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Specification.Tests" />
<PackageReference Include="Microsoft.Extensions.Configuration" /> <PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />

View File

@@ -1,103 +0,0 @@
// 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();
}
}
}

View File

@@ -1,283 +1,131 @@
using System; // 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.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using EFCore.NamingConventions.Internal; using EFCore.NamingConventions.Internal;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit; using Xunit;
// ReSharper disable UnusedMember.Global
namespace EFCore.NamingConventions.Test namespace EFCore.NamingConventions.Test
{ {
public class NameRewritingConventionTest public class NameRewritingConventionTest
{ {
[Fact] [Fact]
public void Table_name() public void Table()
{ {
var entityType = BuildEntityType("SimpleBlog", _ => {}); var entityType = BuildEntityType(b => b.Entity<SampleEntity>());
Assert.Equal("simple_blog", entityType.GetTableName()); Assert.Equal("sample_entity", entityType.GetTableName());
} }
[Fact] [Fact]
public void Column_name() public void Column()
{ {
var entityType = BuildEntityType("SimpleBlog", e => e.Property<int>("SimpleBlogId")); var entityType = BuildEntityType(b => b.Entity<SampleEntity>());
Assert.Equal("sample_entity_id", entityType.FindProperty(nameof(SampleEntity.SampleEntityId))
Assert.Equal("simple_blog_id", entityType.FindProperty("SimpleBlogId")
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value)); .GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
} }
[Fact] [Fact]
public void Column_name_on_view() public void Column_with_turkish_culture()
{ {
var entityType = BuildEntityType("SimpleBlog", e => var entityType = BuildEntityType(
{ b => b.Entity<SampleEntity>(),
e.ToTable("SimpleBlogTable"); CultureInfo.CreateSpecificCulture("tr-TR"));
e.ToView("SimpleBlogView"); Assert.Equal("sample_entity_ıd", entityType.FindProperty(nameof(SampleEntity.SampleEntityId))
e.ToFunction("SimpleBlogFunction"); .GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
e.Property<int>("SimpleBlogId"); }
});
[Fact]
public void Column_with_invariant_culture()
{
var entityType = BuildEntityType(
b => b.Entity<SampleEntity>(),
CultureInfo.InvariantCulture);
Assert.Equal("sample_entity_id", entityType.FindProperty(nameof(SampleEntity.SampleEntityId))
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
}
[Fact]
public void Column_on_view()
{
var entityType = BuildEntityType(b => b.Entity<SampleEntity>(
e =>
{
e.ToTable("SimpleBlogTable");
e.ToView("SimpleBlogView");
e.ToFunction("SimpleBlogFunction");
}));
foreach (var type in new[] { StoreObjectType.Table, StoreObjectType.View, StoreObjectType.Function }) foreach (var type in new[] { StoreObjectType.Table, StoreObjectType.View, StoreObjectType.Function })
{ {
Assert.Equal("simple_blog_id", entityType.FindProperty("SimpleBlogId") Assert.Equal("sample_entity_id", entityType.FindProperty(nameof(SampleEntity.SampleEntityId))
.GetColumnName(StoreObjectIdentifier.Create(entityType, type)!.Value)); .GetColumnName(StoreObjectIdentifier.Create(entityType, type)!.Value));
} }
} }
[Fact] [Fact]
public void Column_name_turkish_culture() public void Primary_key()
{
var entityType = BuildEntityType(b => b.Entity<SampleEntity>());
Assert.Equal("pk_sample_entity", entityType.GetKeys().Single(k => k.IsPrimaryKey()).GetName());
}
[Fact]
public void Alternative_key()
{ {
var entityType = BuildEntityType( var entityType = BuildEntityType(
"SimpleBlog", b => b.Entity<SampleEntity>(
e => e.Property<int>("SimpleBlogId"), e =>
CultureInfo.CreateSpecificCulture("tr-TR")); {
e.Property<int>("SomeAlternateKey");
Assert.Equal("simple_blog_ıd", entityType.FindProperty("SimpleBlogId") e.HasAlternateKey("SomeAlternateKey");
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value)); }));
Assert.Equal("ak_sample_entity_some_alternate_key", entityType.GetKeys().Single(k => !k.IsPrimaryKey()).GetName());
} }
[Fact] [Fact]
public void Column_name_invariant_culture() public void Foreign_key()
{ {
var entityType = BuildEntityType( var model = BuildModel(b => b.Entity<Blog>());
"SimpleBlog", var entityType = model.FindEntityType(typeof(Post));
e => e.Property<int>("SimpleBlogId"),
CultureInfo.InvariantCulture);
Assert.Equal("simple_blog_id", entityType.FindProperty("SimpleBlogId")
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
}
[Fact]
public void Primary_key_name()
{
var entityType = BuildEntityType("SimpleBlog", e =>
{
e.Property<int>("SimpleBlogId");
e.HasKey("SimpleBlogId");
});
Assert.Equal("pk_simple_blog", entityType.GetKeys().Single(k => k.IsPrimaryKey()).GetName());
}
[Fact]
public void Alternative_key_name()
{
var entityType = BuildEntityType("SimpleBlog", e =>
{
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());
}
[Fact]
public void Foreign_key_name()
{
var model = BuildModel(b =>
{
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()); Assert.Equal("fk_post_blog_blog_id", entityType.GetForeignKeys().Single().GetConstraintName());
} }
[Fact] [Fact]
public void Index_name() public void Index()
{ {
var entityType = BuildEntityType("SimpleBlog", e => var entityType = BuildEntityType(b => b.Entity<SampleEntity>().HasIndex(s => s.SomeProperty));
{ Assert.Equal("ix_sample_entity_some_property", entityType.GetIndexes().Single().GetDatabaseName());
e.Property<int>("IndexedProperty");
e.HasIndex("IndexedProperty");
});
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]
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] [Fact]
public void TPH() public void TPH()
{ {
var model = BuildModel(b => var model = BuildModel(b =>
{ {
b.Entity("SimpleBlog", e => b.Entity<Parent>();
{ b.Entity<Child>();
e.Property<int>("SimpleBlogId");
e.HasKey("SimpleBlogId");
});
b.Entity("FancyBlog", e =>
{
e.HasBaseType("SimpleBlog");
e.Property<int>("FancyProperty");
});
}); });
var simpleBlogEntityType = model.FindEntityType("SimpleBlog"); var parentEntityType = model.FindEntityType(typeof(Parent));
Assert.Equal("simple_blog", simpleBlogEntityType.GetTableName()); var childEntityType = model.FindEntityType(typeof(Child));
Assert.Equal("simple_blog_id", simpleBlogEntityType.FindProperty("SimpleBlogId")
.GetColumnName(StoreObjectIdentifier.Create(simpleBlogEntityType, StoreObjectType.Table)!.Value));
var fancyBlogEntityType = model.FindEntityType("FancyBlog"); Assert.Equal("parent", parentEntityType.GetTableName());
Assert.Equal("simple_blog", fancyBlogEntityType.GetTableName()); Assert.Equal("id", parentEntityType.FindProperty(nameof(Parent.Id))
Assert.Equal("fancy_property", fancyBlogEntityType.FindProperty("FancyProperty") .GetColumnName(StoreObjectIdentifier.Create(parentEntityType, StoreObjectType.Table)!.Value));
.GetColumnName(StoreObjectIdentifier.Create(fancyBlogEntityType, StoreObjectType.Table)!.Value)); Assert.Equal("parent_property", parentEntityType.FindProperty(nameof(Parent.ParentProperty))
.GetColumnName(StoreObjectIdentifier.Create(childEntityType, StoreObjectType.Table)!.Value));
Assert.Equal("parent", childEntityType.GetTableName());
Assert.Equal("child_property", childEntityType.FindProperty(nameof(Child.ChildProperty))
.GetColumnName(StoreObjectIdentifier.Create(childEntityType, StoreObjectType.Table)!.Value));
Assert.Same(parentEntityType.FindPrimaryKey(), childEntityType.FindPrimaryKey());
} }
[Fact] [Fact]
@@ -285,51 +133,182 @@ namespace EFCore.NamingConventions.Test
{ {
var model = BuildModel(b => var model = BuildModel(b =>
{ {
b.Entity("SimpleBlog", e => b.Entity<Parent>().ToTable("parent");
{ b.Entity<Child>().ToTable("child");
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"); var parentEntityType = model.FindEntityType(typeof(Parent));
Assert.Equal("simple_blog", simpleBlogEntityType.GetTableName()); var childEntityType = model.FindEntityType(typeof(Child));
Assert.Equal("simple_blog_id", simpleBlogEntityType.FindProperty("SimpleBlogId")
.GetColumnName(StoreObjectIdentifier.Create(simpleBlogEntityType, StoreObjectType.Table)!.Value));
var fancyBlogEntityType = model.FindEntityType("FancyBlog"); Assert.Equal("parent", parentEntityType.GetTableName());
Assert.Equal("fancy_blog", fancyBlogEntityType.GetTableName()); Assert.Equal("id", parentEntityType.FindProperty(nameof(Parent.Id))
Assert.Equal("fancy_property", fancyBlogEntityType.FindProperty("FancyProperty") .GetColumnName(StoreObjectIdentifier.Create(parentEntityType, StoreObjectType.Table)!.Value));
.GetColumnName(StoreObjectIdentifier.Create(fancyBlogEntityType, StoreObjectType.Table)!.Value)); Assert.Equal("parent_property", parentEntityType.FindProperty(nameof(Parent.ParentProperty))
.GetColumnName(StoreObjectIdentifier.Create(parentEntityType, StoreObjectType.Table)!.Value));
Assert.Equal("child", childEntityType.GetTableName());
Assert.Equal("child_property", childEntityType.FindProperty(nameof(Child.ChildProperty))
.GetColumnName(StoreObjectIdentifier.Create(childEntityType, StoreObjectType.Table)!.Value));
var parentKey = parentEntityType.FindPrimaryKey();
var childKey = childEntityType.FindPrimaryKey();
Assert.Equal("PK_parent", parentKey.GetName());
Assert.Equal("PK_parent", childKey.GetName());
} }
#endregion Inheritance [Fact]
public void Table_splitting()
#region Support
private IModel BuildModel(Action<ModelBuilder> buildAction, CultureInfo cultureInfo = null)
{ {
var conventionSet = InMemoryTestHelpers.Instance.CreateConventionSetBuilder().CreateConventionSet(); var model = BuildModel(b =>
ConventionSet.Remove(conventionSet.ModelFinalizedConventions, typeof(ValidatingConvention)); {
b.Entity<Split1>(
e =>
{
e.ToTable("split_table");
e.HasOne(s1 => s1.S2).WithOne(s2 => s2.S1).HasForeignKey<Split2>(s2 => s2.Id);
});
b.Entity<Split2>(e => e.ToTable("split_table"));
});
var split1EntityType = model.FindEntityType(typeof(Split1));
var split2EntityType = model.FindEntityType(typeof(Split2));
var table = StoreObjectIdentifier.Create(split1EntityType, StoreObjectType.Table)!.Value;
Assert.Equal(table, StoreObjectIdentifier.Create(split2EntityType, StoreObjectType.Table));
Assert.Equal("split_table", split1EntityType.GetTableName());
Assert.Equal("one_prop", split1EntityType.FindProperty(nameof(Split1.OneProp)).GetColumnName(table));
Assert.Equal("split_table", split2EntityType.GetTableName());
Assert.Equal("two_prop", split2EntityType.FindProperty(nameof(Split2.TwoProp)).GetColumnName(table));
Assert.Equal("common", split1EntityType.FindProperty(nameof(Split1.Common)).GetColumnName(table));
Assert.Equal("split2_common", split2EntityType.FindProperty(nameof(Split2.Common)).GetColumnName(table));
var foreignKey = split2EntityType.GetForeignKeys().Single();
Assert.Same(split1EntityType.FindPrimaryKey(), foreignKey.PrincipalKey);
Assert.Same(split2EntityType.FindPrimaryKey().Properties.Single(), foreignKey.Properties.Single());
Assert.Equal(split1EntityType.FindPrimaryKey().GetName(), split2EntityType.FindPrimaryKey().GetName());
Assert.Equal(
foreignKey.PrincipalKey.Properties.Single().GetColumnName(table),
foreignKey.Properties.Single().GetColumnName(table));
Assert.Empty(split1EntityType.GetForeignKeys());
}
[Fact]
public void Owned_entity_with_table_splitting()
{
var model = BuildModel(b => b.Entity<Owner>().OwnsOne(o => o.Owned));
var ownerEntityType = model.FindEntityType(typeof(Owner));
var ownedEntityType = model.FindEntityType(typeof(Owned));
Assert.Equal("owner", ownerEntityType.GetTableName());
Assert.Equal("owner", ownedEntityType.GetTableName());
var table = StoreObjectIdentifier.Create(ownerEntityType, StoreObjectType.Table)!.Value;
Assert.Equal(table, StoreObjectIdentifier.Create(ownedEntityType, StoreObjectType.Table)!.Value);
Assert.Equal("owned_owned_property", ownedEntityType.FindProperty(nameof(Owned.OwnedProperty)).GetColumnName(table));
var (ownerKey, ownedKey) = (ownerEntityType.FindPrimaryKey(), ownedEntityType.FindPrimaryKey());
Assert.Equal("pk_owner", ownerKey.GetName());
Assert.Equal("pk_owner", ownedKey.GetName());
Assert.Equal("id", ownerKey.Properties.Single().GetColumnName(table));
Assert.Equal("id", ownedKey.Properties.Single().GetColumnName(table));
}
[Fact]
public void Owned_entity_without_table_splitting()
{
var model = BuildModel(b =>
b.Entity<Owner>().OwnsOne(o => o.Owned).ToTable("another_table"));
var ownedEntityType = model.FindEntityType(typeof(Owned));
Assert.Equal("pk_another_table", ownedEntityType.FindPrimaryKey().GetName());
Assert.Equal("another_table", ownedEntityType.GetTableName());
Assert.Equal("owned_property", ownedEntityType.FindProperty("OwnedProperty")
.GetColumnName(StoreObjectIdentifier.Create(ownedEntityType, StoreObjectType.Table)!.Value));
}
private IEntityType BuildEntityType(Action<ModelBuilder> builderAction, CultureInfo culture = null)
=> BuildModel(builderAction, culture).GetEntityTypes().Single();
private IModel BuildModel(Action<ModelBuilder> builderAction, CultureInfo culture = null)
{
var conventionSet = SqliteTestHelpers.Instance.CreateConventionSetBuilder().CreateConventionSet();
var optionsBuilder = new DbContextOptionsBuilder(); var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseSnakeCaseNamingConvention(cultureInfo); SqliteTestHelpers.Instance.UseProviderOptions(optionsBuilder);
new NamingConventionSetPlugin(optionsBuilder.Options).ModifyConventions(conventionSet); optionsBuilder.UseSnakeCaseNamingConvention(culture);
var plugin = new NamingConventionSetPlugin(optionsBuilder.Options);
plugin.ModifyConventions(conventionSet);
var builder = new ModelBuilder(conventionSet); var modelBuilder = new ModelBuilder(conventionSet);
buildAction(builder); builderAction(modelBuilder);
return builder.FinalizeModel(); return modelBuilder.FinalizeModel();
} }
private IEntityType BuildEntityType(string entityTypeName, Action<EntityTypeBuilder> buildAction, CultureInfo cultureInfo = null) public class SampleEntity
=> BuildModel(b => buildAction(b.Entity(entityTypeName)), cultureInfo).GetEntityTypes().Single(); {
public int SampleEntityId { get; set; }
public int SomeProperty { get; set; }
}
#endregion public class Blog
{
public int BlogId { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public Blog Blog { get; set; }
public int BlogId { get; set; }
}
public class Parent
{
public int Id { get; set; }
public int ParentProperty { get; set; }
}
public class Child : Parent
{
public int ChildProperty { get; set; }
}
public class Split1
{
public int Id { get; set; }
public int OneProp { get; set; }
public int Common { get; set; }
public Split2 S2 { get; set; }
}
public class Split2
{
public int Id { get; set; }
public int TwoProp { get; set; }
public int Common { get; set; }
public Split1 S1 { get; set; }
}
public class Owner
{
public int Id { get; set; }
public int OwnerProperty { get; set; }
public Owned Owned { get; set; }
}
public class Owned
{
public int OwnedProperty { get; set; }
}
} }
} }

View File

@@ -1,29 +1,30 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.InMemory.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Sqlite.Diagnostics.Internal;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
#pragma warning disable EF1001
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore.TestUtilities namespace Microsoft.EntityFrameworkCore.TestUtilities
{ {
public class InMemoryTestHelpers : TestHelpers public class SqliteTestHelpers : TestHelpers
{ {
protected InMemoryTestHelpers() protected SqliteTestHelpers()
{ {
} }
public static InMemoryTestHelpers Instance { get; } = new(); public static SqliteTestHelpers Instance { get; } = new();
public override IServiceCollection AddProviderServices(IServiceCollection services) public override IServiceCollection AddProviderServices(IServiceCollection services)
=> services.AddEntityFrameworkInMemoryDatabase(); => services.AddEntityFrameworkSqlite();
public override void UseProviderOptions(DbContextOptionsBuilder optionsBuilder) public override void UseProviderOptions(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseInMemoryDatabase(nameof(InMemoryTestHelpers)); => optionsBuilder.UseSqlite(new SqliteConnection("Data Source=:memory:"));
public override LoggingDefinitions LoggingDefinitions { get; } = new InMemoryLoggingDefinitions(); #pragma warning disable EF1001
public override LoggingDefinitions LoggingDefinitions { get; } = new SqliteLoggingDefinitions();
#pragma warning enable EF1001
} }
} }

View File

@@ -1,11 +1,9 @@
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
{ {
@@ -66,25 +64,7 @@ namespace EFCore.NamingConventions.Internal
public virtual void ProcessPropertyAdded( public virtual void ProcessPropertyAdded(
IConventionPropertyBuilder propertyBuilder, IConventionPropertyBuilder propertyBuilder,
IConventionContext<IConventionPropertyBuilder> context) IConventionContext<IConventionPropertyBuilder> context)
{ => RewriteColumnName(propertyBuilder);
var entityType = propertyBuilder.Metadata.DeclaringEntityType;
var property = propertyBuilder.Metadata;
propertyBuilder.HasColumnName(_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)
{
propertyBuilder.HasColumnName(
_namingNameRewriter.RewriteName(property.GetColumnName(identifier.Value)), identifier.Value);
}
}
}
public void ProcessForeignKeyOwnershipChanged(IConventionForeignKeyBuilder relationshipBuilder, IConventionContext<bool?> context) public void ProcessForeignKeyOwnershipChanged(IConventionForeignKeyBuilder relationshipBuilder, IConventionContext<bool?> context)
{ {
@@ -102,27 +82,11 @@ namespace EFCore.NamingConventions.Internal
ownedEntityType.Builder.HasNoAnnotation(RelationalAnnotationNames.TableName); ownedEntityType.Builder.HasNoAnnotation(RelationalAnnotationNames.TableName);
ownedEntityType.Builder.HasNoAnnotation(RelationalAnnotationNames.Schema); ownedEntityType.Builder.HasNoAnnotation(RelationalAnnotationNames.Schema);
// Also need to reset all primary key properties // We've previously set rewritten column names when the entity was originally added (before becoming owned).
foreach (var keyProperty in ownedEntityType.FindPrimaryKey().Properties) // These need to be rewritten again to include the owner prefix.
foreach (var property in ownedEntityType.GetProperties())
{ {
keyProperty.Builder.HasNoAnnotation(RelationalAnnotationNames.ColumnName); RewriteColumnName(property.Builder);
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
}
}
} }
} }
} }
@@ -135,40 +99,50 @@ namespace EFCore.NamingConventions.Internal
return; return;
} }
var entityType = entityTypeBuilder.Metadata;
// The table's name is changing - rewrite keys, index names // The table's name is changing - rewrite keys, index names
if (entityTypeBuilder.Metadata.FindPrimaryKey() is IConventionKey primaryKey) if (entityType.FindPrimaryKey() is IConventionKey primaryKey)
{ {
primaryKey.Builder.HasName(_namingNameRewriter.RewriteName(primaryKey.GetDefaultName())); if (entityType.BaseType is not null
&& entityType.GetTableName() != entityType.BaseType.GetTableName())
{
// It's not yet possible to set the PK name with TPT, see https://github.com/dotnet/efcore/issues/23444.
primaryKey.Builder.HasNoAnnotation(RelationalAnnotationNames.Name);
}
else
{
primaryKey.Builder.HasName(_namingNameRewriter.RewriteName(primaryKey.GetDefaultName()));
}
} }
foreach (var foreignKey in entityTypeBuilder.Metadata.GetForeignKeys()) foreach (var foreignKey in entityType.GetForeignKeys())
{ {
foreignKey.Builder.HasConstraintName(_namingNameRewriter.RewriteName(foreignKey.GetDefaultName())); foreignKey.Builder.HasConstraintName(_namingNameRewriter.RewriteName(foreignKey.GetDefaultName()));
} }
foreach (var index in entityTypeBuilder.Metadata.GetIndexes()) foreach (var index in entityType.GetIndexes())
{ {
index.Builder.HasDatabaseName(_namingNameRewriter.RewriteName(index.GetDefaultDatabaseName())); index.Builder.HasDatabaseName(_namingNameRewriter.RewriteName(index.GetDefaultDatabaseName()));
} }
if (oldAnnotation?.Value is null && if (annotation?.Value is not null &&
annotation?.Value is not null && entityType.FindOwnership() is IConventionForeignKey ownership &&
entityTypeBuilder.Metadata.FindOwnership() is IConventionForeignKey ownership &&
(string)annotation.Value != ownership.PrincipalEntityType.GetTableName()) (string)annotation.Value != ownership.PrincipalEntityType.GetTableName())
{ {
// An owned entity's table is being set explicitly - this is the trigger to undo table splitting (which is the default). // An owned entity's table is being set explicitly - this is the trigger to undo table splitting (which is the default).
// 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 entityType.GetProperties()
.Except(entityTypeBuilder.Metadata.FindPrimaryKey().Properties) .Except(entityType.FindPrimaryKey().Properties)
.Where(p => p.Builder.CanSetColumnName(null))) .Where(p => p.Builder.CanSetColumnName(null)))
{ {
property.Builder.HasColumnName(_namingNameRewriter.RewriteName(property.GetDefaultColumnBaseName())); RewriteColumnName(property.Builder);
} }
// We previously rewrote the owned entity's primary key name, when the owned entity was still in table splitting. // 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. // Now that its getting its own table, rewrite the primary key constraint name again.
if (entityTypeBuilder.Metadata.FindPrimaryKey() is IConventionKey key) if (entityType.FindPrimaryKey() is IConventionKey key)
{ {
key.Builder.HasName(_namingNameRewriter.RewriteName(key.GetDefaultName())); key.Builder.HasName(_namingNameRewriter.RewriteName(key.GetDefaultName()));
} }
@@ -224,5 +198,33 @@ namespace EFCore.NamingConventions.Internal
} }
} }
} }
private void RewriteColumnName(IConventionPropertyBuilder propertyBuilder)
{
var property = propertyBuilder.Metadata;
var entityType = property.DeclaringEntityType;
// 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);
// 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
#pragma warning disable 618
propertyBuilder.HasColumnName(_namingNameRewriter.RewriteName(property.GetColumnName()));
#pragma warning restore 618
foreach (var storeObjectType in _storeObjectTypes)
{
var identifier = StoreObjectIdentifier.Create(entityType, storeObjectType);
if (identifier is null)
continue;
if (property.GetColumnNameConfigurationSource(identifier.Value) == ConfigurationSource.Convention)
{
propertyBuilder.HasColumnName(
_namingNameRewriter.RewriteName(property.GetColumnName(identifier.Value)), identifier.Value);
}
}
}
} }
} }