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.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" />

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.Linq;
using EFCore.NamingConventions.Internal;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;
// ReSharper disable UnusedMember.Global
namespace EFCore.NamingConventions.Test
{
public class NameRewritingConventionTest
{
[Fact]
public void Table_name()
public void Table()
{
var entityType = BuildEntityType("SimpleBlog", _ => {});
Assert.Equal("simple_blog", entityType.GetTableName());
var entityType = BuildEntityType(b => b.Entity<SampleEntity>());
Assert.Equal("sample_entity", entityType.GetTableName());
}
[Fact]
public void Column_name()
public void Column()
{
var entityType = BuildEntityType("SimpleBlog", e => e.Property<int>("SimpleBlogId"));
Assert.Equal("simple_blog_id", entityType.FindProperty("SimpleBlogId")
var entityType = BuildEntityType(b => b.Entity<SampleEntity>());
Assert.Equal("sample_entity_id", entityType.FindProperty(nameof(SampleEntity.SampleEntityId))
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
}
[Fact]
public void Column_name_on_view()
public void Column_with_turkish_culture()
{
var entityType = BuildEntityType("SimpleBlog", e =>
{
e.ToTable("SimpleBlogTable");
e.ToView("SimpleBlogView");
e.ToFunction("SimpleBlogFunction");
e.Property<int>("SimpleBlogId");
});
var entityType = BuildEntityType(
b => b.Entity<SampleEntity>(),
CultureInfo.CreateSpecificCulture("tr-TR"));
Assert.Equal("sample_entity_ıd", entityType.FindProperty(nameof(SampleEntity.SampleEntityId))
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
}
[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 })
{
Assert.Equal("simple_blog_id", entityType.FindProperty("SimpleBlogId")
Assert.Equal("sample_entity_id", entityType.FindProperty(nameof(SampleEntity.SampleEntityId))
.GetColumnName(StoreObjectIdentifier.Create(entityType, type)!.Value));
}
}
[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(
"SimpleBlog",
e => e.Property<int>("SimpleBlogId"),
CultureInfo.CreateSpecificCulture("tr-TR"));
Assert.Equal("simple_blog_ıd", entityType.FindProperty("SimpleBlogId")
.GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value));
b => b.Entity<SampleEntity>(
e =>
{
e.Property<int>("SomeAlternateKey");
e.HasAlternateKey("SomeAlternateKey");
}));
Assert.Equal("ak_sample_entity_some_alternate_key", entityType.GetKeys().Single(k => !k.IsPrimaryKey()).GetName());
}
[Fact]
public void Column_name_invariant_culture()
public void Foreign_key()
{
var entityType = BuildEntityType(
"SimpleBlog",
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");
var model = BuildModel(b => b.Entity<Blog>());
var entityType = model.FindEntityType(typeof(Post));
Assert.Equal("fk_post_blog_blog_id", entityType.GetForeignKeys().Single().GetConstraintName());
}
[Fact]
public void Index_name()
public void Index()
{
var entityType = BuildEntityType("SimpleBlog", e =>
{
e.Property<int>("IndexedProperty");
e.HasIndex("IndexedProperty");
});
Assert.Equal("ix_simple_blog_indexed_property", entityType.GetIndexes().Single().GetDatabaseName());
var entityType = BuildEntityType(b => b.Entity<SampleEntity>().HasIndex(s => s.SomeProperty));
Assert.Equal("ix_sample_entity_some_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]
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");
});
b.Entity<Parent>();
b.Entity<Child>();
});
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 parentEntityType = model.FindEntityType(typeof(Parent));
var childEntityType = model.FindEntityType(typeof(Child));
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));
Assert.Equal("parent", parentEntityType.GetTableName());
Assert.Equal("id", parentEntityType.FindProperty(nameof(Parent.Id))
.GetColumnName(StoreObjectIdentifier.Create(parentEntityType, 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]
@@ -285,51 +133,182 @@ namespace EFCore.NamingConventions.Test
{
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");
});
b.Entity<Parent>().ToTable("parent");
b.Entity<Child>().ToTable("child");
});
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 parentEntityType = model.FindEntityType(typeof(Parent));
var childEntityType = model.FindEntityType(typeof(Child));
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));
Assert.Equal("parent", parentEntityType.GetTableName());
Assert.Equal("id", parentEntityType.FindProperty(nameof(Parent.Id))
.GetColumnName(StoreObjectIdentifier.Create(parentEntityType, 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
#region Support
private IModel BuildModel(Action<ModelBuilder> buildAction, CultureInfo cultureInfo = null)
[Fact]
public void Table_splitting()
{
var conventionSet = InMemoryTestHelpers.Instance.CreateConventionSetBuilder().CreateConventionSet();
ConventionSet.Remove(conventionSet.ModelFinalizedConventions, typeof(ValidatingConvention));
var model = BuildModel(b =>
{
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();
optionsBuilder.UseSnakeCaseNamingConvention(cultureInfo);
new NamingConventionSetPlugin(optionsBuilder.Options).ModifyConventions(conventionSet);
SqliteTestHelpers.Instance.UseProviderOptions(optionsBuilder);
optionsBuilder.UseSnakeCaseNamingConvention(culture);
var plugin = new NamingConventionSetPlugin(optionsBuilder.Options);
plugin.ModifyConventions(conventionSet);
var builder = new ModelBuilder(conventionSet);
buildAction(builder);
return builder.FinalizeModel();
var modelBuilder = new ModelBuilder(conventionSet);
builderAction(modelBuilder);
return modelBuilder.FinalizeModel();
}
private IEntityType BuildEntityType(string entityTypeName, Action<EntityTypeBuilder> buildAction, CultureInfo cultureInfo = null)
=> BuildModel(b => buildAction(b.Entity(entityTypeName)), cultureInfo).GetEntityTypes().Single();
public class SampleEntity
{
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.
// 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.InMemory.Diagnostics.Internal;
using Microsoft.EntityFrameworkCore.Sqlite.Diagnostics.Internal;
using Microsoft.Extensions.DependencyInjection;
#pragma warning disable EF1001
// ReSharper disable once CheckNamespace
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)
=> services.AddEntityFrameworkInMemoryDatabase();
=> services.AddEntityFrameworkSqlite();
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.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace EFCore.NamingConventions.Internal
{
@@ -66,25 +64,7 @@ namespace EFCore.NamingConventions.Internal
public virtual void ProcessPropertyAdded(
IConventionPropertyBuilder propertyBuilder,
IConventionContext<IConventionPropertyBuilder> context)
{
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);
}
}
}
=> RewriteColumnName(propertyBuilder);
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.Schema);
// Also need to reset all primary key properties
foreach (var keyProperty in ownedEntityType.FindPrimaryKey().Properties)
// We've previously set rewritten column names when the entity was originally added (before becoming owned).
// These need to be rewritten again to include the owner prefix.
foreach (var property in ownedEntityType.GetProperties())
{
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
}
}
RewriteColumnName(property.Builder);
}
}
}
@@ -135,40 +99,50 @@ namespace EFCore.NamingConventions.Internal
return;
}
var entityType = entityTypeBuilder.Metadata;
// 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()));
}
foreach (var index in entityTypeBuilder.Metadata.GetIndexes())
foreach (var index in entityType.GetIndexes())
{
index.Builder.HasDatabaseName(_namingNameRewriter.RewriteName(index.GetDefaultDatabaseName()));
}
if (oldAnnotation?.Value is null &&
annotation?.Value is not null &&
entityTypeBuilder.Metadata.FindOwnership() is IConventionForeignKey ownership &&
if (annotation?.Value is not null &&
entityType.FindOwnership() is IConventionForeignKey ownership &&
(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).
// When the entity became owned, we prefixed all of its properties - we must now undo that.
foreach (var property in entityTypeBuilder.Metadata.GetProperties()
.Except(entityTypeBuilder.Metadata.FindPrimaryKey().Properties)
foreach (var property in entityType.GetProperties()
.Except(entityType.FindPrimaryKey().Properties)
.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.
// 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()));
}
@@ -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);
}
}
}
}
}