using EntityFrameworkCore.Jet.FunctionalTests.TestUtilities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.ModelBuilding; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Xunit; namespace EntityFrameworkCore.Jet.FunctionalTests.ModelBuilding; public class JetModelBuilderTestBase : RelationalModelBuilderTest { public abstract class JetNonRelationship(JetModelBuilderFixture fixture) : RelationalNonRelationshipTestBase(fixture), IClassFixture { [ConditionalFact] public virtual void Index_has_a_filter_if_nonclustered_unique_with_nullable_properties() { var modelBuilder = CreateModelBuilder(); var entityTypeBuilder = modelBuilder .Entity(); var indexBuilder = entityTypeBuilder .HasIndex(ix => ix.Name) .IsUnique(); var entityType = modelBuilder.Model.FindEntityType(typeof(Customer))!; var index = entityType.GetIndexes().Single(); Assert.Equal("IGNORE NULL", index.GetFilter()); indexBuilder.IsUnique(false); Assert.Null(index.GetFilter()); indexBuilder.IsUnique(); Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); /*indexBuilder.IsClustered(); Assert.Null(index.GetFilter()); indexBuilder.IsClustered(false);*/ Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); entityTypeBuilder.Property(e => e.Name).IsRequired(); Assert.Null(index.GetFilter()); entityTypeBuilder.Property(e => e.Name).IsRequired(false); Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); entityTypeBuilder.Property(e => e.Name).HasColumnName("RelationalName"); Assert.Equal("[RelationalName] IS NOT NULL", index.GetFilter()); entityTypeBuilder.Property(e => e.Name).HasColumnName("JetName"); Assert.Equal("[JetName] IS NOT NULL", index.GetFilter()); entityTypeBuilder.Property(e => e.Name).HasColumnName(null); Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); indexBuilder.HasFilter("Foo"); Assert.Equal("Foo", index.GetFilter()); indexBuilder.HasFilter(null); Assert.Null(index.GetFilter()); } [ConditionalFact] public void Indexes_can_have_same_name_across_tables() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity() .HasIndex(e => e.Id, "Ix_Id") .IsUnique(); modelBuilder.Entity() .HasIndex(e => e.CustomerId, "Ix_Id") .IsUnique(); var model = modelBuilder.FinalizeModel(); var customerIndex = model.FindEntityType(typeof(Customer))!.GetIndexes().Single(); Assert.Equal("Ix_Id", customerIndex.Name); Assert.Equal("Ix_Id", customerIndex.GetDatabaseName()); Assert.Equal( "Ix_Id", customerIndex.GetDatabaseName( StoreObjectIdentifier.Table("Customer"))); var detailsIndex = model.FindEntityType(typeof(CustomerDetails))!.GetIndexes().Single(); Assert.Equal("Ix_Id", detailsIndex.Name); Assert.Equal("Ix_Id", detailsIndex.GetDatabaseName()); Assert.Equal( "Ix_Id", detailsIndex.GetDatabaseName( StoreObjectIdentifier.Table("CustomerDetails"))); } [ConditionalFact] public virtual void Can_set_store_type_for_property_type() { var modelBuilder = CreateModelBuilder(c => { c.Properties().HaveColumnType("smallint"); c.Properties().HaveColumnType("nchar(max)"); c.Properties(typeof(Nullable<>)).HavePrecision(2); }); modelBuilder.Entity(b => { b.Property("Charm"); b.Property("Strange"); b.Property("Top"); b.Property("Bottom"); }); var model = modelBuilder.FinalizeModel(); var entityType = model.FindEntityType(typeof(Quarks))!; Assert.Equal("smallint", entityType.FindProperty(Customer.IdProperty.Name)!.GetColumnType()); Assert.Equal("smallint", entityType.FindProperty("Up")!.GetColumnType()); Assert.Equal("nchar(max)", entityType.FindProperty("Down")!.GetColumnType()); var charm = entityType.FindProperty("Charm")!; Assert.Equal("smallint", charm.GetColumnType()); Assert.Null(charm.GetPrecision()); Assert.Equal("nchar(max)", entityType.FindProperty("Strange")!.GetColumnType()); var top = entityType.FindProperty("Top")!; Assert.Equal("smallint", top.GetColumnType()); Assert.Equal(2, top.GetPrecision()); Assert.Equal("nchar(max)", entityType.FindProperty("Bottom")!.GetColumnType()); } [ConditionalFact] public virtual void Can_set_fixed_length_for_property_type() { var modelBuilder = CreateModelBuilder(c => { c.Properties().AreFixedLength(false); c.Properties().AreFixedLength(); }); modelBuilder.Entity(b => { b.Property("Charm"); b.Property("Strange"); b.Property("Top"); b.Property("Bottom"); }); var model = modelBuilder.FinalizeModel(); var entityType = model.FindEntityType(typeof(Quarks))!; Assert.False(entityType.FindProperty(Customer.IdProperty.Name)!.IsFixedLength()); Assert.False(entityType.FindProperty("Up")!.IsFixedLength()); Assert.True(entityType.FindProperty("Down")!.IsFixedLength()); Assert.False(entityType.FindProperty("Charm")!.IsFixedLength()); Assert.True(entityType.FindProperty("Strange")!.IsFixedLength()); Assert.False(entityType.FindProperty("Top")!.IsFixedLength()); Assert.True(entityType.FindProperty("Bottom")!.IsFixedLength()); } [ConditionalFact] public virtual void Can_set_collation_for_property_type() { var modelBuilder = CreateModelBuilder(c => { c.Properties().UseCollation("Latin1_General_CS_AS_KS_WS"); c.Properties().UseCollation("Latin1_General_BIN"); }); modelBuilder.Entity(b => { b.Property("Charm"); b.Property("Strange"); b.Property("Top"); b.Property("Bottom"); }); var model = modelBuilder.FinalizeModel(); var entityType = model.FindEntityType(typeof(Quarks))!; Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty(Customer.IdProperty.Name)!.GetCollation()); Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Up")!.GetCollation()); Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Down")!.GetCollation()); Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Charm")!.GetCollation()); Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Strange")!.GetCollation()); Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Top")!.GetCollation()); Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Bottom")!.GetCollation()); } [ConditionalFact] public virtual void Can_set_store_type_for_primitive_collection() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity(b => { b.PrimitiveCollection(e => e.Up).HasColumnType("national character varying(255)"); b.PrimitiveCollection(e => e.Down).HasColumnType("nchar(10)"); b.PrimitiveCollection("Charm").HasColumnType("nvarchar(25)"); b.PrimitiveCollection("Strange").HasColumnType("text"); b.PrimitiveCollection>("Top").HasColumnType("char(100)"); b.PrimitiveCollection?>("Bottom").HasColumnType("varchar(max)"); }); var model = modelBuilder.FinalizeModel(); var entityType = model.FindEntityType(typeof(CollectionQuarks))!; Assert.Equal("integer", entityType.FindProperty(nameof(CollectionQuarks.Id))!.GetColumnType()); Assert.Equal("national character varying(255)", entityType.FindProperty("Up")!.GetColumnType()); Assert.Equal("nchar(10)", entityType.FindProperty("Down")!.GetColumnType()); Assert.Equal("nvarchar(25)", entityType.FindProperty("Charm")!.GetColumnType()); Assert.Equal("text", entityType.FindProperty("Strange")!.GetColumnType()); Assert.Equal("char(100)", entityType.FindProperty("Top")!.GetColumnType()); Assert.Equal("varchar(max)", entityType.FindProperty("Bottom")!.GetColumnType()); } [ConditionalFact] public virtual void Can_set_fixed_length_for_primitive_collection() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity(b => { b.PrimitiveCollection(e => e.Up).IsFixedLength(false); b.PrimitiveCollection(e => e.Down).IsFixedLength(); b.PrimitiveCollection("Charm").IsFixedLength(); }); var model = modelBuilder.FinalizeModel(); var entityType = model.FindEntityType(typeof(CollectionQuarks))!; Assert.False(entityType.FindProperty("Up")!.IsFixedLength()); Assert.True(entityType.FindProperty("Down")!.IsFixedLength()); Assert.True(entityType.FindProperty("Charm")!.IsFixedLength()); } [ConditionalFact] public virtual void Can_set_collation_for_primitive_collection() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity(b => { b.PrimitiveCollection(e => e.Up).UseCollation("Latin1_General_CS_AS_KS_WS"); b.PrimitiveCollection(e => e.Down).UseCollation("Latin1_General_BIN"); b.PrimitiveCollection("Charm").UseCollation("Latin1_General_CI_AI"); }); var model = modelBuilder.FinalizeModel(); var entityType = model.FindEntityType(typeof(CollectionQuarks))!; Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Up")!.GetCollation()); Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Down")!.GetCollation()); Assert.Equal("Latin1_General_CI_AI", entityType.FindProperty("Charm")!.GetCollation()); } [ConditionalTheory, InlineData(true), InlineData(false)] public virtual void Can_avoid_attributes_when_discovering_properties(bool useAttributes) { var modelBuilder = CreateModelBuilder(c => c.Conventions.Replace(s => new PropertyDiscoveryConvention( s.GetService()!, useAttributes))); modelBuilder.Entity(); if (useAttributes) { var model = modelBuilder.FinalizeModel(); var entityType = model.FindEntityType(typeof(SqlVariantEntity))!; Assert.Equal( [nameof(SqlVariantEntity.Id), nameof(SqlVariantEntity.Value),], entityType.GetProperties().Select(p => p.Name)); } else { Assert.Equal( CoreStrings.PropertyNotAdded(nameof(SqlVariantEntity), nameof(SqlVariantEntity.Value), "object"), Assert.Throws(modelBuilder.FinalizeModel).Message); } } protected class SqlVariantEntity { public int Id { get; set; } [Column(TypeName = "sql_variant")] public object? Value { get; set; } } } public abstract class JetComplexType(JetModelBuilderFixture fixture) : RelationalComplexTypeTestBase(fixture), IClassFixture; public abstract class JetComplexCollection(JetModelBuilderFixture fixture) : RelationalComplexCollectionTestBase(fixture), IClassFixture; public abstract class JetInheritance(JetModelBuilderFixture fixture) : RelationalInheritanceTestBase(fixture), IClassFixture { [ConditionalFact] // #7240 public void Can_use_shadow_FK_that_collides_with_convention_shadow_FK_on_other_derived_type() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity(); modelBuilder.Entity() .HasOne(p => p.A) .WithOne() .HasForeignKey("ParentId"); var model = modelBuilder.FinalizeModel(); var property1 = model.FindEntityType(typeof(DisjointChildSubclass1))!.FindProperty("ParentId")!; Assert.True(property1.IsForeignKey()); Assert.Equal("ParentId", property1.GetColumnName()); var property2 = model.FindEntityType(typeof(DisjointChildSubclass2))!.FindProperty("ParentId")!; Assert.True(property2.IsForeignKey()); Assert.Equal("ParentId", property2.GetColumnName()); Assert.Equal("DisjointChildSubclass2_ParentId", property2.GetColumnName(StoreObjectIdentifier.Table(nameof(Child)))); } [ConditionalFact] public void Inherited_clr_properties_are_mapped_to_the_same_column() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity(); modelBuilder.Ignore(); modelBuilder.Entity(); modelBuilder.Entity(); var model = modelBuilder.FinalizeModel(); var property1 = model.FindEntityType(typeof(DisjointChildSubclass1))!.FindProperty(nameof(Child.Name))!; Assert.Equal(nameof(Child.Name), property1.GetColumnName()); var property2 = model.FindEntityType(typeof(DisjointChildSubclass2))!.FindProperty(nameof(Child.Name))!; Assert.Equal(nameof(Child.Name), property2.GetColumnName()); } [ConditionalFact] //Issue#10659 public void Index_convention_run_for_fk_when_derived_type_discovered_before_base_type() { var modelBuilder = CreateModelBuilder(); modelBuilder.Ignore(); modelBuilder.Entity(); modelBuilder.Entity(); var index = modelBuilder.Model.FindEntityType(typeof(CustomerDetails))!.GetIndexes().Single(); Assert.Equal("[CustomerId] IS NOT NULL", index.GetFilter()); } [ConditionalFact] public void Index_convention_sets_filter_for_unique_index_when_base_type_changed() { var modelBuilder = CreateModelBuilder(); modelBuilder.Ignore(); modelBuilder.Entity() .HasIndex(e => e.CustomerId) .IsUnique(); modelBuilder.Entity(); var index = modelBuilder.Model.FindEntityType(typeof(CustomerDetails))!.GetIndexes().Single(); Assert.Equal("[CustomerId] IS NOT NULL", index.GetFilter()); modelBuilder.Ignore(); Assert.Null(index.GetFilter()); } [ConditionalFact] public virtual void Can_override_TPC_with_TPH() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity

(); modelBuilder.Entity(); modelBuilder.Entity(); modelBuilder.Entity() .UseTpcMappingStrategy() .UseTphMappingStrategy(); var model = modelBuilder.FinalizeModel(); Assert.Equal("Discriminator", model.FindEntityType(typeof(PBase))!.GetDiscriminatorPropertyName()); Assert.Equal(nameof(PBase), model.FindEntityType(typeof(PBase))!.GetDiscriminatorValue()); Assert.Equal(nameof(P), model.FindEntityType(typeof(P))!.GetDiscriminatorValue()); Assert.Equal(nameof(Q), model.FindEntityType(typeof(Q))!.GetDiscriminatorValue()); } [ConditionalFact] public virtual void TPT_identifying_FK_is_created_only_on_declaring_table() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity() .Ignore(b => b.Bun) .Ignore(b => b.Pickles); modelBuilder.Entity(b => { b.ToTable("Ingredients"); b.Ignore(i => i.BigMak); }); modelBuilder.Entity(b => { b.ToTable("Buns"); b.HasOne(i => i.BigMak).WithOne().HasForeignKey(i => i.Id); }); modelBuilder.Entity(b => { b.ToTable("SesameBuns"); }); var model = modelBuilder.FinalizeModel(); var principalType = model.FindEntityType(typeof(BigMak))!; Assert.Empty(principalType.GetForeignKeys()); Assert.Empty(principalType.GetIndexes()); Assert.Null(principalType.FindDiscriminatorProperty()); var ingredientType = model.FindEntityType(typeof(Ingredient))!; var bunType = model.FindEntityType(typeof(Bun))!; Assert.Empty(bunType.GetIndexes()); Assert.Null(bunType.FindDiscriminatorProperty()); var bunFk = bunType.GetDeclaredForeignKeys().Single(fk => !fk.IsBaseLinking()); Assert.Equal("FK_Buns_BigMak_Id", bunFk.GetConstraintName()); Assert.Equal( "FK_Buns_BigMak_Id", bunFk.GetConstraintName( StoreObjectIdentifier.Create(bunType, StoreObjectType.Table)!.Value, StoreObjectIdentifier.Create(principalType, StoreObjectType.Table)!.Value)); Assert.Single(bunFk.GetMappedConstraints()); var bunLinkingFk = bunType.GetDeclaredForeignKeys().Single(fk => fk.IsBaseLinking()); Assert.Equal("FK_Buns_Ingredients_Id", bunLinkingFk.GetConstraintName()); Assert.Equal( "FK_Buns_Ingredients_Id", bunLinkingFk.GetConstraintName( StoreObjectIdentifier.Create(bunType, StoreObjectType.Table)!.Value, StoreObjectIdentifier.Create(ingredientType, StoreObjectType.Table)!.Value)); Assert.Single(bunLinkingFk.GetMappedConstraints()); var sesameBunType = model.FindEntityType(typeof(SesameBun))!; Assert.Empty(sesameBunType.GetIndexes()); var sesameBunFk = sesameBunType.GetDeclaredForeignKeys().Single(); Assert.True(sesameBunFk.IsBaseLinking()); Assert.Equal("FK_SesameBuns_Buns_Id", sesameBunFk.GetConstraintName()); Assert.Equal( "FK_SesameBuns_Buns_Id", sesameBunFk.GetConstraintName( StoreObjectIdentifier.Create(sesameBunType, StoreObjectType.Table)!.Value, StoreObjectIdentifier.Create(bunType, StoreObjectType.Table)!.Value)); Assert.Single(sesameBunFk.GetMappedConstraints()); } [ConditionalFact] public virtual void TPC_identifying_FKs_are_created_on_all_tables() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity() .Ignore(b => b.Bun) .Ignore(b => b.Pickles); modelBuilder.Entity(b => { b.ToTable("Ingredients"); b.Ignore(i => i.BigMak); b.HasIndex(e => e.BurgerId); b.UseTpcMappingStrategy(); }); modelBuilder.Entity(b => { b.ToTable("Buns"); b.HasOne(i => i.BigMak).WithOne().HasForeignKey(i => i.Id); b.UseTpcMappingStrategy(); }); modelBuilder.Entity(b => { b.ToTable("SesameBuns"); }); var model = modelBuilder.FinalizeModel(); var principalType = model.FindEntityType(typeof(BigMak))!; Assert.Empty(principalType.GetForeignKeys()); Assert.Empty(principalType.GetIndexes()); Assert.Null(principalType.FindDiscriminatorProperty()); var bunType = model.FindEntityType(typeof(Bun))!; Assert.Empty(bunType.GetDeclaredIndexes()); Assert.Null(bunType.FindDiscriminatorProperty()); var bunFk = bunType.GetDeclaredForeignKeys().Single(); Assert.Equal("FK_Buns_BigMak_Id", bunFk.GetConstraintName()); Assert.Equal( "FK_Buns_BigMak_Id", bunFk.GetConstraintName( StoreObjectIdentifier.Create(bunType, StoreObjectType.Table)!.Value, StoreObjectIdentifier.Create(principalType, StoreObjectType.Table)!.Value)); Assert.Equal(2, bunFk.GetMappedConstraints().Count()); Assert.DoesNotContain(bunType.GetDeclaredForeignKeys(), fk => fk.IsBaseLinking()); var sesameBunType = model.FindEntityType(typeof(SesameBun))!; Assert.Empty(sesameBunType.GetDeclaredIndexes()); Assert.Empty(sesameBunType.GetDeclaredForeignKeys()); Assert.Equal( "FK_SesameBuns_BigMak_Id", bunFk.GetConstraintName( StoreObjectIdentifier.Create(sesameBunType, StoreObjectType.Table)!.Value, StoreObjectIdentifier.Create(principalType, StoreObjectType.Table)!.Value)); var ingredientType = model.FindEntityType(typeof(Ingredient))!; var ingredientIndex = ingredientType.GetDeclaredIndexes().Single(); Assert.Equal("IX_Ingredients_BurgerId", ingredientIndex.GetDatabaseName()); Assert.Equal( "IX_SesameBuns_BurgerId", ingredientIndex.GetDatabaseName(StoreObjectIdentifier.Create(sesameBunType, StoreObjectType.Table)!.Value)); Assert.Equal( "IX_Buns_BurgerId", ingredientIndex.GetDatabaseName(StoreObjectIdentifier.Create(bunType, StoreObjectType.Table)!.Value)); } [ConditionalFact] public virtual void TPT_index_can_use_inherited_properties() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity() .Ignore(b => b.Bun) .Ignore(b => b.Pickles); modelBuilder.Entity(b => { b.ToTable("Ingredients"); b.Property("NullableProp"); b.Ignore(i => i.BigMak); }); modelBuilder.Entity(b => { b.ToTable("Buns"); b.HasIndex(bun => bun.BurgerId); b.HasIndex("NullableProp"); b.HasOne(i => i.BigMak).WithOne().HasForeignKey(i => i.Id); }); var model = modelBuilder.FinalizeModel(); var bunType = model.FindEntityType(typeof(Bun))!; Assert.All(bunType.GetIndexes(), i => Assert.Null(i.GetFilter())); } [ConditionalFact] public void Can_add_check_constraints() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity() .HasBaseType(null) .ToTable(tb => tb.HasCheckConstraint("CK_ChildBase_LargeId", "Id > 1000").HasName("CK_LargeId")); modelBuilder.Entity() .ToTable(tb => { tb.HasCheckConstraint("PositiveId", "Id > 0"); tb.HasCheckConstraint("CK_ChildBase_LargeId", "Id > 1000"); }); modelBuilder.Entity() .HasBaseType(); modelBuilder.Entity(); var model = modelBuilder.FinalizeModel(); var @base = model.FindEntityType(typeof(ChildBase))!; Assert.Equal(2, @base.GetCheckConstraints().Count()); var firstCheckConstraint = @base.FindCheckConstraint("PositiveId")!; Assert.Equal("PositiveId", firstCheckConstraint.ModelName); Assert.Equal("Id > 0", firstCheckConstraint.Sql); Assert.Equal("PositiveId", firstCheckConstraint.Name); var secondCheckConstraint = @base.FindCheckConstraint("CK_ChildBase_LargeId")!; Assert.Equal("CK_ChildBase_LargeId", secondCheckConstraint.ModelName); Assert.Equal("Id > 1000", secondCheckConstraint.Sql); Assert.Equal("CK_LargeId", secondCheckConstraint.Name); var child = model.FindEntityType(typeof(Child))!; Assert.Equal(@base.GetCheckConstraints(), child.GetCheckConstraints()); Assert.Empty(child.GetDeclaredCheckConstraints()); } [ConditionalFact] public void Adding_conflicting_check_constraint_to_derived_type_throws() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity() .ToTable(tb => tb.HasCheckConstraint("LargeId", "Id > 100").HasName("CK_LargeId")); Assert.Equal( RelationalStrings.DuplicateCheckConstraint("LargeId", nameof(Child), nameof(ChildBase)), Assert.Throws(() => modelBuilder.Entity().ToTable(tb => tb.HasCheckConstraint("LargeId", "Id > 1000"))).Message); } [ConditionalFact] public void Adding_conflicting_check_constraint_to_derived_type_before_base_throws() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity() .HasBaseType(null) .ToTable(tb => tb.HasCheckConstraint("LargeId", "Id > 1000")); modelBuilder.Entity() .ToTable(tb => tb.HasCheckConstraint("LargeId", "Id > 100").HasName("CK_LargeId")); Assert.Equal( RelationalStrings.DuplicateCheckConstraint("LargeId", nameof(Child), nameof(ChildBase)), Assert.Throws(() => modelBuilder.Entity().HasBaseType()).Message); } protected class Parent { public int Id { get; set; } public DisjointChildSubclass1? A { get; set; } public IList? B { get; set; } } protected abstract class ChildBase { public int Id { get; set; } } protected abstract class Child : ChildBase { public string? Name { get; set; } } protected class DisjointChildSubclass1 : Child; protected class DisjointChildSubclass2 : Child; } public abstract class JetOneToMany(JetModelBuilderFixture fixture) : RelationalOneToManyTestBase(fixture), IClassFixture { [ConditionalFact] public virtual void Shadow_foreign_keys_to_generic_types_have_terrible_names_that_should_not_change() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity().ToTable("Events"); modelBuilder.Entity>().ToTable("CompanyActivities"); modelBuilder.Entity>().ToTable("UserActivities"); var model = modelBuilder.FinalizeModel(); var companyActivityEventType = model.FindEntityType(typeof(ActivityEvent))!; var eventTable = StoreObjectIdentifier.Create(companyActivityEventType, StoreObjectType.Table)!.Value; var companyActivityEventFk = companyActivityEventType.GetForeignKeys().Single(); var companyActivityEventFkProperty = companyActivityEventFk.Properties.Single(); Assert.Equal("ActivityId", companyActivityEventFkProperty.GetColumnName(eventTable)); Assert.Equal("FK_Events_CompanyActivities_ActivityId", companyActivityEventFk.GetConstraintName()); Assert.Equal( "FK_Events_CompanyActivities_ActivityId", companyActivityEventFk.GetConstraintName( eventTable, StoreObjectIdentifier.Create(companyActivityEventFk.PrincipalEntityType, StoreObjectType.Table)!.Value)); var userActivityEventType = model.FindEntityType(typeof(ActivityEvent))!; var userActivityEventFk = userActivityEventType.GetForeignKeys().Single(); var userActivityEventFkProperty = userActivityEventFk.Properties.Single(); Assert.Equal("ActivityId", userActivityEventFkProperty.GetColumnName(eventTable)); Assert.Equal("FK_Events_UserActivities_ActivityId", userActivityEventFk.GetConstraintName()); Assert.Equal( "FK_Events_UserActivities_ActivityId", userActivityEventFk.GetConstraintName( eventTable, StoreObjectIdentifier.Create(userActivityEventFk.PrincipalEntityType, StoreObjectType.Table)!.Value)); } protected abstract class EventBase { public string? Id { get; set; } } protected class Activity { public string? Id { get; set; } public virtual List> Events { get; } = null!; } protected class ActivityEvent : EventBase; protected class Company; protected class User; } public abstract class JetManyToOne(JetModelBuilderFixture fixture) : RelationalManyToOneTestBase(fixture), IClassFixture; public abstract class JetOneToOne(JetModelBuilderFixture fixture) : RelationalOneToOneTestBase(fixture), IClassFixture; public abstract class JetManyToMany(JetModelBuilderFixture fixture) : RelationalManyToManyTestBase(fixture), IClassFixture { [ConditionalFact] public virtual void Join_entity_type_uses_same_schema() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity().ToTable("Category", "mySchema").Ignore(c => c.ProductCategories); modelBuilder.Entity().ToTable("Product", "mySchema"); modelBuilder.Entity(); var model = modelBuilder.FinalizeModel(); var productType = model.FindEntityType(typeof(Product))!; var categoryType = model.FindEntityType(typeof(Category))!; var categoriesNavigation = productType.GetSkipNavigations().Single(); var productsNavigation = categoryType.GetSkipNavigations().Single(); var categoriesFk = categoriesNavigation.ForeignKey; var productsFk = productsNavigation.ForeignKey; var productCategoryType = categoriesFk.DeclaringEntityType; Assert.Equal(typeof(Dictionary), productCategoryType.ClrType); Assert.Equal("mySchema", productCategoryType.GetSchema()); Assert.Same(categoriesFk, productCategoryType.GetForeignKeys().Last()); Assert.Same(productsFk, productCategoryType.GetForeignKeys().First()); Assert.Equal(2, productCategoryType.GetForeignKeys().Count()); } [ConditionalFact] public virtual void Join_entity_type_uses_default_schema_if_related_are_different() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity().ToTable("Category").Ignore(c => c.ProductCategories); modelBuilder.Entity().ToTable("Product", "dbo"); modelBuilder.Entity(); var model = modelBuilder.FinalizeModel(); var productType = model.FindEntityType(typeof(Product))!; var categoryType = model.FindEntityType(typeof(Category))!; var categoriesNavigation = productType.GetSkipNavigations().Single(); var productsNavigation = categoryType.GetSkipNavigations().Single(); var categoriesFk = categoriesNavigation.ForeignKey; var productsFk = productsNavigation.ForeignKey; var productCategoryType = categoriesFk.DeclaringEntityType; Assert.Equal(typeof(Dictionary), productCategoryType.ClrType); Assert.Null(productCategoryType.GetSchema()); Assert.Same(categoriesFk, productCategoryType.GetForeignKeys().Last()); Assert.Same(productsFk, productCategoryType.GetForeignKeys().First()); Assert.Equal(2, productCategoryType.GetForeignKeys().Count()); } } public abstract class JetOwnedTypes(JetModelBuilderFixture fixture) : RelationalOwnedTypesTestBase(fixture), IClassFixture { [ConditionalFact] public virtual void Owned_types_use_table_splitting_by_default() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity().OwnsOne( b => b.AlternateLabel, b => { b.Ignore(l => l.Book); b.OwnsOne( l => l.AnotherBookLabel, ab => { ab.Property(l => l.BookId).HasColumnName("BookId2"); ab.Ignore(l => l.Book); ab.OwnsOne( s => s.SpecialBookLabel, s => { s.Property(l => l.BookId).HasColumnName("BookId2"); s.Ignore(l => l.Book); s.Ignore(l => l.BookLabel); }); }); }); modelBuilder.Entity().OwnsOne(b => b.Label) .Ignore(l => l.Book) .OwnsOne(l => l.SpecialBookLabel) .Ignore(l => l.Book) .OwnsOne(a => a.AnotherBookLabel) .Ignore(l => l.Book); modelBuilder.Entity().OwnsOne(b => b.Label) .OwnsOne(l => l.AnotherBookLabel) .Ignore(l => l.Book) .OwnsOne(a => a.SpecialBookLabel) .Ignore(l => l.Book) .Ignore(l => l.BookLabel); modelBuilder.Entity().OwnsOne( b => b.AlternateLabel, b => { b.Ignore(l => l.Book); b.OwnsOne( l => l.SpecialBookLabel, ab => { ab.Property(l => l.BookId).HasColumnName("BookId2"); ab.Ignore(l => l.Book); ab.OwnsOne( s => s.AnotherBookLabel, s => { s.Property(l => l.BookId).HasColumnName("BookId2"); s.Ignore(l => l.Book); }); }); }); var model = (IModel)modelBuilder.Model; var book = model.FindEntityType(typeof(Book))!; var bookOwnership1 = book.FindNavigation(nameof(Book.Label))!.ForeignKey; var bookOwnership2 = book.FindNavigation(nameof(Book.AlternateLabel))!.ForeignKey; var bookLabel1Ownership1 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))!.ForeignKey; var bookLabel1Ownership2 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))!.ForeignKey; var bookLabel2Ownership1 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))!.ForeignKey; var bookLabel2Ownership2 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))!.ForeignKey; Assert.Equal(book.GetTableName(), bookOwnership1.DeclaringEntityType.GetTableName()); Assert.Equal(book.GetTableName(), bookOwnership2.DeclaringEntityType.GetTableName()); Assert.Equal(book.GetTableName(), bookLabel1Ownership1.DeclaringEntityType.GetTableName()); Assert.Equal(book.GetTableName(), bookLabel1Ownership2.DeclaringEntityType.GetTableName()); Assert.Equal(book.GetTableName(), bookLabel2Ownership1.DeclaringEntityType.GetTableName()); Assert.Equal(book.GetTableName(), bookLabel2Ownership2.DeclaringEntityType.GetTableName()); Assert.NotSame(bookOwnership1.DeclaringEntityType, bookOwnership2.DeclaringEntityType); Assert.Single(bookOwnership1.DeclaringEntityType.GetForeignKeys()); Assert.Single(bookOwnership1.DeclaringEntityType.GetForeignKeys()); Assert.NotSame(bookLabel1Ownership1.DeclaringEntityType, bookLabel2Ownership1.DeclaringEntityType); Assert.NotSame(bookLabel1Ownership2.DeclaringEntityType, bookLabel2Ownership2.DeclaringEntityType); Assert.Single(bookLabel1Ownership1.DeclaringEntityType.GetForeignKeys()); Assert.Single(bookLabel1Ownership2.DeclaringEntityType.GetForeignKeys()); Assert.Single(bookLabel2Ownership1.DeclaringEntityType.GetForeignKeys()); Assert.Single(bookLabel2Ownership2.DeclaringEntityType.GetForeignKeys()); Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(BookLabel))); Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(AnotherBookLabel))); Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(SpecialBookLabel))); Assert.Null( bookOwnership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id))! .GetColumnName(StoreObjectIdentifier.Table("Label"))); Assert.Null( bookLabel2Ownership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id))! .GetColumnName(StoreObjectIdentifier.Table("AlternateLabel"))); modelBuilder.Entity().OwnsOne(b => b.Label).ToTable("Label"); modelBuilder.Entity().OwnsOne(b => b.AlternateLabel).ToTable("AlternateLabel"); model = modelBuilder.FinalizeModel(); Assert.Equal( nameof(BookLabel.Id), bookOwnership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id))! .GetColumnName(StoreObjectIdentifier.Table("Label"))); Assert.Equal( nameof(BookLabel.AnotherBookLabel) + "_" + nameof(BookLabel.Id), bookLabel2Ownership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id))! .GetColumnName(StoreObjectIdentifier.Table("AlternateLabel"))); var alternateTable = model.GetRelationalModel().FindTable("AlternateLabel", null)!; var bookId = alternateTable.FindColumn("BookId2")!; Assert.Equal(4, bookId.PropertyMappings.Count()); Assert.All(bookId.PropertyMappings, m => Assert.Equal(ValueGenerated.OnUpdateSometimes, m.Property.ValueGenerated)); } /*[ConditionalFact] public virtual void Owned_types_can_be_mapped_to_different_tables() { var modelBuilder = CreateModelBuilder(); var model = modelBuilder.Model; modelBuilder.Entity(bb => { bb.ToTable( "BT", "BS", t => { t.ExcludeFromMigrations(); Assert.Equal("BT", t.Name); Assert.Equal("BS", t.Schema); }); bb.OwnsOne( b => b.AlternateLabel, tb => { tb.Ignore(l => l.Book); tb.WithOwner() .HasConstraintName("AlternateLabelFK"); tb.ToTable("TT", "TS", tb => tb.IsMemoryOptimized()); tb.OwnsOne( l => l.AnotherBookLabel, ab => { ab.Ignore(l => l.Book); ab.ToTable( "AT1", "AS1", t => { t.ExcludeFromMigrations(false); Assert.Equal("AT1", t.Name); Assert.Equal("AS1", t.Schema); }); ab.OwnsOne(s => s.SpecialBookLabel) .ToTable("ST11", "SS11") .Ignore(l => l.Book) .Ignore(l => l.BookLabel); ab.OwnedEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))! .AddAnnotation("Foo", "Bar"); }); tb.OwnsOne( l => l.SpecialBookLabel, sb => { sb.Ignore(l => l.Book); sb.ToTable("ST2", "SS2"); sb.OwnsOne(s => s.AnotherBookLabel) .ToTable("AT21", "AS21") .Ignore(l => l.Book); }); }); bb.OwnsOne( b => b.Label, lb => { lb.Ignore(l => l.Book); lb.ToTable("LT", "LS"); lb.OwnsOne( l => l.SpecialBookLabel, sb => { sb.Ignore(l => l.Book); sb.ToTable("ST1", "SS1"); sb.OwnsOne(a => a.AnotherBookLabel) .ToTable("AT11", "AS11") .Ignore(l => l.Book); }); lb.OwnsOne( l => l.AnotherBookLabel, ab => { ab.Ignore(l => l.Book); ab.ToTable("AT2", "AS2"); ab.OwnsOne(a => a.SpecialBookLabel) .ToTable("ST21", "SS21") .Ignore(l => l.BookLabel) .Ignore(l => l.Book); }); }); }); modelBuilder.FinalizeModel(); var book = model.FindEntityType(typeof(Book))!; var bookOwnership1 = book.FindNavigation(nameof(Book.Label))!.ForeignKey; var bookOwnership2 = book.FindNavigation(nameof(Book.AlternateLabel))!.ForeignKey; var bookLabel1Ownership1 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))!.ForeignKey; var bookLabel1Ownership2 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))!.ForeignKey; var bookLabel2Ownership1 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))!.ForeignKey; var bookLabel2Ownership2 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))!.ForeignKey; var bookLabel1Ownership11 = bookLabel1Ownership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))! .ForeignKey; var bookLabel1Ownership21 = bookLabel1Ownership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))! .ForeignKey; var bookLabel2Ownership11 = bookLabel2Ownership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))! .ForeignKey; var bookLabel2Ownership21 = bookLabel2Ownership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))! .ForeignKey; Assert.Equal("AlternateLabelFK", bookOwnership2.GetConstraintName()); Assert.Equal("BS", book.GetSchema()); Assert.Equal("BT", book.GetTableName()); Assert.True(book.IsTableExcludedFromMigrations()); Assert.Equal("LS", bookOwnership1.DeclaringEntityType.GetSchema()); Assert.Equal("LT", bookOwnership1.DeclaringEntityType.GetTableName()); //Assert.False(bookOwnership1.DeclaringEntityType.IsMemoryOptimized()); Assert.True(bookOwnership1.DeclaringEntityType.IsTableExcludedFromMigrations()); Assert.Equal("TS", bookOwnership2.DeclaringEntityType.GetSchema()); Assert.Equal("TT", bookOwnership2.DeclaringEntityType.GetTableName()); //Assert.True(bookOwnership2.DeclaringEntityType.IsMemoryOptimized()); Assert.True(bookOwnership2.DeclaringEntityType.IsTableExcludedFromMigrations()); Assert.Equal("AS2", bookLabel1Ownership1.DeclaringEntityType.GetSchema()); Assert.Equal("AT2", bookLabel1Ownership1.DeclaringEntityType.GetTableName()); Assert.Equal("SS1", bookLabel1Ownership2.DeclaringEntityType.GetSchema()); Assert.Equal("ST1", bookLabel1Ownership2.DeclaringEntityType.GetTableName()); Assert.Equal("AS1", bookLabel2Ownership1.DeclaringEntityType.GetSchema()); Assert.Equal("AT1", bookLabel2Ownership1.DeclaringEntityType.GetTableName()); Assert.False(bookLabel2Ownership1.DeclaringEntityType.IsTableExcludedFromMigrations()); Assert.Equal("SS2", bookLabel2Ownership2.DeclaringEntityType.GetSchema()); Assert.Equal("ST2", bookLabel2Ownership2.DeclaringEntityType.GetTableName()); Assert.Equal("SS21", bookLabel1Ownership11.DeclaringEntityType.GetSchema()); Assert.Equal("ST21", bookLabel1Ownership11.DeclaringEntityType.GetTableName()); Assert.Equal("AS11", bookLabel1Ownership21.DeclaringEntityType.GetSchema()); Assert.Equal("AT11", bookLabel1Ownership21.DeclaringEntityType.GetTableName()); Assert.Equal("SS11", bookLabel2Ownership11.DeclaringEntityType.GetSchema()); Assert.Equal("ST11", bookLabel2Ownership11.DeclaringEntityType.GetTableName()); Assert.Equal("AS21", bookLabel2Ownership21.DeclaringEntityType.GetSchema()); Assert.Equal("AT21", bookLabel2Ownership21.DeclaringEntityType.GetTableName()); Assert.Equal("Bar", bookLabel2Ownership11.PrincipalToDependent?["Foo"]); Assert.NotSame(bookOwnership1.DeclaringEntityType, bookOwnership2.DeclaringEntityType); Assert.Single(bookOwnership1.DeclaringEntityType.GetForeignKeys()); Assert.Single(bookOwnership2.DeclaringEntityType.GetForeignKeys()); Assert.NotSame(bookLabel1Ownership1.DeclaringEntityType, bookLabel2Ownership1.DeclaringEntityType); Assert.NotSame(bookLabel1Ownership2.DeclaringEntityType, bookLabel2Ownership2.DeclaringEntityType); Assert.Single(bookLabel1Ownership1.DeclaringEntityType.GetForeignKeys()); Assert.Single(bookLabel1Ownership2.DeclaringEntityType.GetForeignKeys()); Assert.Single(bookLabel2Ownership1.DeclaringEntityType.GetForeignKeys()); Assert.Single(bookLabel2Ownership2.DeclaringEntityType.GetForeignKeys()); Assert.NotSame(bookLabel1Ownership11.DeclaringEntityType, bookLabel2Ownership11.DeclaringEntityType); Assert.NotSame(bookLabel1Ownership21.DeclaringEntityType, bookLabel2Ownership21.DeclaringEntityType); Assert.Single(bookLabel1Ownership11.DeclaringEntityType.GetForeignKeys()); Assert.Single(bookLabel1Ownership21.DeclaringEntityType.GetForeignKeys()); Assert.Single(bookLabel2Ownership11.DeclaringEntityType.GetForeignKeys()); Assert.Single(bookLabel2Ownership21.DeclaringEntityType.GetForeignKeys()); Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(BookLabel))); Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(AnotherBookLabel))); Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(SpecialBookLabel))); Assert.Equal(ValueGenerated.Never, bookOwnership1.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); Assert.Equal(ValueGenerated.Never, bookOwnership2.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); Assert.Equal( ValueGenerated.Never, bookLabel1Ownership1.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); Assert.Equal( ValueGenerated.Never, bookLabel1Ownership2.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); Assert.Equal( ValueGenerated.Never, bookLabel2Ownership1.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); Assert.Equal( ValueGenerated.Never, bookLabel2Ownership2.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); Assert.Equal( ValueGenerated.Never, bookLabel1Ownership11.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); Assert.Equal( ValueGenerated.Never, bookLabel1Ownership21.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); Assert.Equal( ValueGenerated.Never, bookLabel2Ownership11.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); Assert.Equal( ValueGenerated.Never, bookLabel2Ownership21.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); } [ConditionalFact] public virtual void Owned_type_collections_can_be_mapped_to_different_tables() { var modelBuilder = CreateModelBuilder(); var model = modelBuilder.Model; modelBuilder.Entity().OwnsMany( c => c.Orders, r => { r.HasKey(o => o.OrderId); r.ToTable(tb => tb.IsMemoryOptimized()); r.Ignore(o => o.OrderCombination); r.Ignore(o => o.Details); }); var ownership = model.FindEntityType(typeof(Customer))!.FindNavigation(nameof(Customer.Orders))!.ForeignKey; var owned = ownership.DeclaringEntityType; Assert.True(ownership.IsOwnership); Assert.Equal(nameof(Order.Customer), ownership.DependentToPrincipal?.Name); Assert.Equal("FK_Order_Customer_CustomerId", ownership.GetConstraintName()); Assert.Single(owned.GetForeignKeys()); Assert.Single(owned.GetIndexes()); Assert.Equal( [nameof(Order.OrderId), nameof(Order.AnotherCustomerId), nameof(Order.CustomerId)], owned.GetProperties().Select(p => p.GetColumnName())); Assert.Equal(nameof(Order), owned.GetTableName()); Assert.Null(owned.GetSchema()); Assert.True(owned.IsMemoryOptimized()); modelBuilder.Entity().OwnsMany( c => c.Orders, r => { r.WithOwner(o => o.Customer).HasConstraintName("Owned"); r.ToTable("bar", "foo"); }); Assert.Equal("bar", owned.GetTableName()); Assert.Equal("foo", owned.GetSchema()); Assert.Equal("Owned", ownership.GetConstraintName()); modelBuilder.Entity().OwnsMany( c => c.Orders, r => r.ToTable("blah")); modelBuilder.FinalizeModel(); Assert.Equal("blah", owned.GetTableName()); Assert.Null(owned.GetSchema()); }*/ [ConditionalFact] public virtual void Owned_type_collections_are_mapped_to_same_tables_by_default() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity(b => { b.OwnsOne(x => x.OwnedReference1, bb => { bb.OwnsOne(x => x.Reference1); bb.OwnsOne(x => x.Reference2); bb.OwnsMany(x => x.Collection1); bb.OwnsMany(x => x.Collection2); }); b.OwnsOne(x => x.OwnedReference2, bb => { bb.OwnsOne(x => x.Reference1); bb.OwnsOne(x => x.Reference2); bb.OwnsMany(x => x.Collection1); bb.OwnsMany(x => x.Collection2); }); b.OwnsMany(x => x.OwnedCollection1, bb => { bb.OwnsOne(x => x.Reference1); bb.OwnsOne(x => x.Reference2); bb.OwnsMany(x => x.Collection1); bb.OwnsMany(x => x.Collection2); }); b.OwnsMany(x => x.OwnedCollection2, bb => { bb.OwnsOne(x => x.Reference1); bb.OwnsOne(x => x.Reference2); bb.OwnsMany(x => x.Collection1); bb.OwnsMany(x => x.Collection2); }); }); Assert.Equal(RelationalStrings.IncompatibleTableNoRelationship( "JsonEntityWithNesting_Collection1", "JsonEntityWithNesting.OwnedReference2#OwnedEntityExtraLevel.Collection1#OwnedEntity", "JsonEntityWithNesting.OwnedReference1#OwnedEntityExtraLevel.Collection1#OwnedEntity"), Assert.Throws(() => modelBuilder.FinalizeModel()).Message); } [ConditionalFact] public virtual void Owned_type_collections_can_be_mapped_to_a_view() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity().OwnsMany( c => c.Orders, r => { r.HasKey(o => o.OrderId); r.Ignore(o => o.OrderCombination); r.Ignore(o => o.Details); r.ToView("bar", "foo"); }); var model = modelBuilder.FinalizeModel(); var owner = model.FindEntityType(typeof(Customer))!; var ownership = owner.FindNavigation(nameof(Customer.Orders))!.ForeignKey; var owned = ownership.DeclaringEntityType; Assert.True(ownership.IsOwnership); Assert.Equal(nameof(Order.Customer), ownership.DependentToPrincipal?.Name); Assert.Empty(ownership.GetMappedConstraints()); Assert.Equal(nameof(Customer), owner.GetTableName()); Assert.Null(owner.GetSchema()); Assert.Null(owned.GetForeignKeys().Single().GetConstraintName()); Assert.Single(owned.GetIndexes()); Assert.Null(owned.FindPrimaryKey()!.GetName()); Assert.Equal( [nameof(Order.OrderId), nameof(Order.AnotherCustomerId), nameof(Order.CustomerId)], owned.GetProperties().Select(p => p.GetColumnName())); Assert.Null(owned.GetTableName()); Assert.Null(owned.GetSchema()); Assert.Equal("bar", owned.GetViewName()); Assert.Equal("foo", owned.GetViewSchema()); } [ConditionalFact] public virtual void Owner_can_be_mapped_to_a_view() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity().OwnsMany( c => c.Orders, r => { r.HasKey(o => o.OrderId); r.Ignore(o => o.OrderCombination); r.Ignore(o => o.Details); }) .ToView("bar", "foo"); var model = modelBuilder.FinalizeModel(); var owner = model.FindEntityType(typeof(Customer))!; var ownership = owner.FindNavigation(nameof(Customer.Orders))!.ForeignKey; var owned = ownership.DeclaringEntityType; Assert.True(ownership.IsOwnership); Assert.Equal(nameof(Order.Customer), ownership.DependentToPrincipal?.Name); Assert.Empty(ownership.GetMappedConstraints()); Assert.Null(owner.GetTableName()); Assert.Null(owner.GetSchema()); Assert.Equal("bar", owner.GetViewName()); Assert.Equal("foo", owner.GetViewSchema()); Assert.Null(owned.GetForeignKeys().Single().GetConstraintName()); Assert.Equal("IX_Order_CustomerId", owned.GetIndexes().Single().GetDatabaseName()); Assert.Equal("PK_Order", owned.FindPrimaryKey()!.GetName()); Assert.Equal( [nameof(Order.OrderId), nameof(Order.AnotherCustomerId), nameof(Order.CustomerId)], owned.GetProperties().Select(p => p.GetColumnName())); Assert.Equal(nameof(Order), owned.GetTableName()); Assert.Null(owned.GetSchema()); } [ConditionalFact] public virtual void Json_entity_and_normal_owned_can_exist_side_by_side_on_same_entity() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity(b => { b.OwnsOne(x => x.OwnedReference1); b.OwnsOne(x => x.OwnedReference2, bb => bb.ToJson("reference")); b.OwnsMany(x => x.OwnedCollection1); b.OwnsMany(x => x.OwnedCollection2, bb => bb.ToJson("collection")); }); var model = modelBuilder.FinalizeModel(); var owner = model.FindEntityType(typeof(JsonEntity))!; Assert.False(owner.IsMappedToJson()); Assert.True(owner.GetDeclaredProperties().All(x => x.GetJsonPropertyName() == null)); var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)); Assert.Equal(4, ownedEntities.Count()); Assert.Equal(2, ownedEntities.Where(e => e.IsMappedToJson()).Count()); Assert.Equal(2, ownedEntities.Where(e => e.IsOwned() && !e.IsMappedToJson()).Count()); var reference = ownedEntities.Where(e => e.GetContainerColumnName() == "reference").Single(); Assert.Equal("Date", reference.GetProperty("Date").GetJsonPropertyName()); Assert.Equal("Fraction", reference.GetProperty("Fraction").GetJsonPropertyName()); Assert.Equal("Enum", reference.GetProperty("Enum").GetJsonPropertyName()); var collection = ownedEntities.Where(e => e.GetContainerColumnName() == "collection").Single(); Assert.Equal("Date", collection.GetProperty("Date").GetJsonPropertyName()); Assert.Equal("Fraction", collection.GetProperty("Fraction").GetJsonPropertyName()); Assert.Equal("Enum", collection.GetProperty("Enum").GetJsonPropertyName()); var nonJson = ownedEntities.Where(e => !e.IsMappedToJson()).ToList(); Assert.True(nonJson.All(x => x.GetProperty("Date").GetJsonPropertyName() == null)); Assert.True(nonJson.All(x => x.GetProperty("Fraction").GetJsonPropertyName() == null)); Assert.True(nonJson.All(x => x.GetProperty("Enum").GetJsonPropertyName() == null)); } [ConditionalFact] public virtual void Json_entity_with_tph_inheritance() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity(b => { b.OwnsOne(x => x.OwnedReferenceOnBase, bb => bb.ToJson("reference_on_base")); b.OwnsMany(x => x.OwnedCollectionOnBase, bb => bb.ToJson("collection_on_base")); }); modelBuilder.Entity(b => { b.HasBaseType(); b.OwnsOne(x => x.OwnedReferenceOnDerived, bb => bb.ToJson("reference_on_derived")); b.OwnsMany(x => x.OwnedCollectionOnDerived, bb => bb.ToJson("collection_on_derived")); }); var model = modelBuilder.FinalizeModel(); var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)).ToList(); Assert.Equal(4, ownedEntities.Count()); foreach (var ownedEntity in ownedEntities) { Assert.Equal("Date", ownedEntity.GetProperty("Date").GetJsonPropertyName()); Assert.Equal("Fraction", ownedEntity.GetProperty("Fraction").GetJsonPropertyName()); Assert.Equal("Enum", ownedEntity.GetProperty("Enum").GetJsonPropertyName()); } var jsonColumnNames = ownedEntities.Select(x => x.GetContainerColumnName()).OrderBy(x => x).ToList(); Assert.Equal("collection_on_base", jsonColumnNames[0]); Assert.Equal("collection_on_derived", jsonColumnNames[1]); Assert.Equal("reference_on_base", jsonColumnNames[2]); Assert.Equal("reference_on_derived", jsonColumnNames[3]); } [ConditionalFact] public virtual void Json_entity_with_nested_structure_same_property_names() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity(b => { b.OwnsOne( x => x.OwnedReference1, bb => { bb.ToJson("ref1"); bb.OwnsOne(x => x.Reference1); bb.OwnsOne(x => x.Reference2); bb.OwnsMany(x => x.Collection1); bb.OwnsMany(x => x.Collection2); }); b.OwnsOne( x => x.OwnedReference2, bb => { bb.ToJson("ref2"); bb.OwnsOne(x => x.Reference1); bb.OwnsOne(x => x.Reference2); bb.OwnsMany(x => x.Collection1); bb.OwnsMany(x => x.Collection2); }); b.OwnsMany( x => x.OwnedCollection1, bb => { bb.ToJson("col1"); bb.OwnsOne(x => x.Reference1); bb.OwnsOne(x => x.Reference2); bb.OwnsMany(x => x.Collection1); bb.OwnsMany(x => x.Collection2); }); b.OwnsMany( x => x.OwnedCollection2, bb => { bb.ToJson("col2"); bb.OwnsOne(x => x.Reference1) .HasAnnotation(RelationalAnnotationNames.JsonPropertyName, null); bb.OwnsOne(x => x.Reference2) .ToTable("Ref2") .HasAnnotation(RelationalAnnotationNames.ContainerColumnName, null); bb.OwnsMany(x => x.Collection1); bb.OwnsMany(x => x.Collection2); }); }); var model = modelBuilder.FinalizeModel(); var outerOwnedEntities = model.FindEntityTypes(typeof(OwnedEntityExtraLevel)); Assert.Collection( outerOwnedEntities, e => Assert.Equal("col1", e.GetContainerColumnName()), e => Assert.Equal("col2", e.GetContainerColumnName()), e => Assert.Equal("ref1", e.GetContainerColumnName()), e => Assert.Equal("ref2", e.GetContainerColumnName())); foreach (var outerOwnedEntity in outerOwnedEntities) { Assert.Equal("Date", outerOwnedEntity.GetProperty("Date").GetJsonPropertyName()); Assert.Equal("Fraction", outerOwnedEntity.GetProperty("Fraction").GetJsonPropertyName()); Assert.Equal("Enum", outerOwnedEntity.GetProperty("Enum").GetJsonPropertyName()); var nestedOwnedTypes = outerOwnedEntity.GetNavigations().Select(n => n.TargetEntityType).ToList(); Assert.Collection( nestedOwnedTypes, e => Assert.Equal("Collection1", e.GetJsonPropertyName()), e => Assert.Equal("Collection2", e.GetJsonPropertyName()), e => Assert.Equal( outerOwnedEntity.GetContainerColumnName() == "col2" ? null : "Reference1", e.GetJsonPropertyName()), e => Assert.Equal( outerOwnedEntity.GetContainerColumnName() == "col2" ? null : "Reference2", e.GetJsonPropertyName())); Assert.Collection( nestedOwnedTypes, e => Assert.Equal(outerOwnedEntity.GetContainerColumnName(), e.GetContainerColumnName()), e => Assert.Equal(outerOwnedEntity.GetContainerColumnName(), e.GetContainerColumnName()), e => Assert.Equal(outerOwnedEntity.GetContainerColumnName(), e.GetContainerColumnName()), e => Assert.Equal( outerOwnedEntity.GetContainerColumnName() == "col2" ? null : outerOwnedEntity.GetContainerColumnName(), e.GetContainerColumnName())); foreach (var ownedEntity in nestedOwnedTypes) { if (ownedEntity.GetContainerColumnName() == null) { continue; } Assert.Equal("Date", ownedEntity.GetProperty("Date").GetJsonPropertyName()); Assert.Equal("Fraction", ownedEntity.GetProperty("Fraction").GetJsonPropertyName()); Assert.Equal("Enum", ownedEntity.GetProperty("Enum").GetJsonPropertyName()); } } Assert.Equal(16, model.FindEntityTypes(typeof(OwnedEntity)).Count()); } [ConditionalFact] public virtual void Json_entity_nested_enums_have_conversions_to_int_by_default_ToJson_first() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity(b => { b.OwnsOne( x => x.OwnedReference1, bb => { bb.ToJson(); bb.OwnsOne(x => x.Reference1); bb.OwnsOne(x => x.Reference2); bb.OwnsMany(x => x.Collection1); bb.OwnsMany(x => x.Collection2); }); b.Ignore(x => x.OwnedReference2); b.OwnsMany( x => x.OwnedCollection1, bb => { bb.ToJson(); bb.OwnsOne(x => x.Reference1); bb.OwnsOne(x => x.Reference2); bb.OwnsMany(x => x.Collection1); bb.OwnsMany(x => x.Collection2); }); b.Ignore(x => x.OwnedCollection2); }); var model = modelBuilder.FinalizeModel(); var outerOwnedEntities = model.FindEntityTypes(typeof(OwnedEntityExtraLevel)); Assert.Equal(2, outerOwnedEntities.Count()); foreach (var outerOwnedEntity in outerOwnedEntities) { Assert.True(outerOwnedEntity.IsMappedToJson()); var myEnum = outerOwnedEntity.GetDeclaredProperties().Where(p => p.ClrType.IsEnum).Single(); var typeMapping = myEnum.FindRelationalTypeMapping()!; Assert.True(typeMapping.Converter is EnumToNumberConverter); } var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)); Assert.Equal(8, ownedEntities.Count()); foreach (var ownedEntity in ownedEntities) { Assert.True(ownedEntity.IsMappedToJson()); var myEnum = ownedEntity.GetDeclaredProperties().Where(p => p.ClrType.IsEnum).Single(); var typeMapping = myEnum.FindRelationalTypeMapping()!; Assert.True(typeMapping.Converter is EnumToNumberConverter); } } [ConditionalFact] public virtual void Json_entity_nested_enums_have_conversions_to_int_by_default_ToJson_last() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity(b => { b.OwnsOne( x => x.OwnedReference1, bb => { bb.OwnsOne(x => x.Reference1); bb.OwnsOne(x => x.Reference2); bb.OwnsMany(x => x.Collection1); bb.OwnsMany(x => x.Collection2); bb.ToJson(); }); b.Ignore(x => x.OwnedReference2); b.OwnsMany( x => x.OwnedCollection1, bb => { bb.OwnsOne(x => x.Reference1); bb.OwnsOne(x => x.Reference2); bb.OwnsMany(x => x.Collection1); bb.OwnsMany(x => x.Collection2); bb.ToJson(); }); b.Ignore(x => x.OwnedCollection2); }); var model = modelBuilder.FinalizeModel(); var outerOwnedEntities = model.FindEntityTypes(typeof(OwnedEntityExtraLevel)); Assert.Equal(2, outerOwnedEntities.Count()); foreach (var outerOwnedEntity in outerOwnedEntities) { Assert.True(outerOwnedEntity.IsMappedToJson()); var myEnum = outerOwnedEntity.GetDeclaredProperties().Where(p => p.ClrType.IsEnum).Single(); var typeMapping = myEnum.FindRelationalTypeMapping()!; Assert.True(typeMapping.Converter is EnumToNumberConverter); } var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)); Assert.Equal(8, ownedEntities.Count()); foreach (var ownedEntity in ownedEntities) { Assert.True(ownedEntity.IsMappedToJson()); var myEnum = ownedEntity.GetDeclaredProperties().Where(p => p.ClrType.IsEnum).Single(); var typeMapping = myEnum.FindRelationalTypeMapping()!; Assert.True(typeMapping.Converter is EnumToNumberConverter); } } [ConditionalFact] public virtual void Entity_mapped_to_json_and_unwound_afterwards_properly_cleans_up_its_state() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity(b => { b.OwnsOne( x => x.OwnedReference1, bb => { bb.ToJson(); bb.OwnsOne(x => x.Reference1); bb.OwnsOne(x => x.Reference2); bb.OwnsMany(x => x.Collection1); bb.OwnsMany(x => x.Collection2); bb.ToJson(null); }); b.Ignore(x => x.OwnedReference2); b.OwnsMany( x => x.OwnedCollection1, bb => { bb.OwnsOne(x => x.Reference1); bb.OwnsOne(x => x.Reference2); bb.OwnsMany(x => x.Collection1); bb.OwnsMany(x => x.Collection2); bb.ToJson(); bb.ToJson(null); }); b.Ignore(x => x.OwnedCollection2); }); var model = modelBuilder.FinalizeModel(); var outerOwnedEntities = model.FindEntityTypes(typeof(OwnedEntityExtraLevel)); Assert.Equal(2, outerOwnedEntities.Count()); foreach (var outerOwnedEntity in outerOwnedEntities) { Assert.False(outerOwnedEntity.IsMappedToJson()); #pragma warning disable CS0618 Assert.Null(outerOwnedEntity.GetContainerColumnTypeMapping()); #pragma warning restore CS0618 var myEnum = outerOwnedEntity.GetDeclaredProperties().Where(p => p.ClrType.IsEnum).Single(); var typeMapping = myEnum.FindRelationalTypeMapping()!; Assert.True(typeMapping.Converter is EnumToNumberConverter); } var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)); Assert.Equal(8, ownedEntities.Count()); foreach (var ownedEntity in ownedEntities) { Assert.False(ownedEntity.IsMappedToJson()); #pragma warning disable CS0618 Assert.Null(ownedEntity.GetContainerColumnTypeMapping()); #pragma warning restore CS0618 var myEnum = ownedEntity.GetDeclaredProperties().Where(p => p.ClrType.IsEnum).Single(); var typeMapping = myEnum.FindRelationalTypeMapping()!; Assert.True(typeMapping.Converter is EnumToNumberConverter); } } [ConditionalFact] public virtual void Json_entity_mapped_to_view() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity(b => { b.ToView("MyView"); b.OwnsOne(x => x.OwnedReference1, bb => bb.ToJson()); b.Ignore(x => x.OwnedReference2); b.OwnsMany(x => x.OwnedCollection1, bb => bb.ToJson()); b.Ignore(x => x.OwnedCollection2); }); var model = modelBuilder.FinalizeModel(); var owner = model.FindEntityType(typeof(JsonEntity))!; Assert.Equal("MyView", owner.GetViewName()); var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)); Assert.Equal(2, ownedEntities.Count()); Assert.Equal(2, ownedEntities.Where(e => e.IsMappedToJson()).Count()); Assert.True(ownedEntities.All(x => x.GetViewName() == "MyView")); } [ConditionalFact] public virtual void Json_entity_mapped_to_view_with_custom_schema() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity(b => { b.ToView("MyView", "MySchema"); b.OwnsOne(x => x.OwnedReference1, bb => bb.ToJson()); b.Ignore(x => x.OwnedReference2); b.OwnsMany(x => x.OwnedCollection1, bb => bb.ToJson()); b.Ignore(x => x.OwnedCollection2); }); var model = modelBuilder.FinalizeModel(); var owner = model.FindEntityType(typeof(JsonEntity))!; Assert.Equal("MyView", owner.GetViewName()); var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)); Assert.Equal(2, ownedEntities.Count()); Assert.Equal(2, ownedEntities.Where(e => e.IsMappedToJson()).Count()); Assert.True(ownedEntities.All(x => x.GetViewName() == "MyView")); Assert.True(ownedEntities.All(x => x.GetViewSchema() == "MySchema")); } [ConditionalFact] public virtual void Json_entity_with_custom_property_names() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity(b => { b.OwnsOne( x => x.OwnedReference1, bb => { bb.ToJson(); bb.Property(x => x.Date).HasJsonPropertyName("OuterDate"); bb.Property(x => x.Fraction).HasJsonPropertyName("OuterFraction"); bb.Property(x => x.Enum).HasJsonPropertyName("OuterEnum"); bb.OwnsOne( x => x.Reference1, bbb => { bbb.HasJsonPropertyName("RenamedReference1"); bbb.Property(x => x.Date).HasJsonPropertyName("InnerDate"); bbb.Property(x => x.Fraction).HasJsonPropertyName("InnerFraction"); bbb.Property(x => x.Enum).HasJsonPropertyName("InnerEnum"); }); bb.OwnsOne( x => x.Reference2, bbb => { bbb.HasJsonPropertyName("RenamedReference2"); bbb.Property(x => x.Date).HasJsonPropertyName("InnerDate"); bbb.Property(x => x.Fraction).HasJsonPropertyName("InnerFraction"); bbb.Property(x => x.Enum).HasJsonPropertyName("InnerEnum"); }); bb.OwnsMany( x => x.Collection1, bbb => { bbb.HasJsonPropertyName("RenamedCollection1"); bbb.Property(x => x.Date).HasJsonPropertyName("InnerDate"); bbb.Property(x => x.Fraction).HasJsonPropertyName("InnerFraction"); bbb.Property(x => x.Enum).HasJsonPropertyName("InnerEnum"); }); bb.OwnsMany( x => x.Collection2, bbb => { bbb.HasJsonPropertyName("RenamedCollection2"); bbb.Property(x => x.Date).HasJsonPropertyName("InnerDate"); bbb.Property(x => x.Fraction).HasJsonPropertyName("InnerFraction"); bbb.Property(x => x.Enum).HasJsonPropertyName("InnerEnum"); }); }); b.OwnsMany( x => x.OwnedCollection1, bb => { bb.Property(x => x.Date).HasJsonPropertyName("OuterDate"); bb.Property(x => x.Fraction).HasJsonPropertyName("OuterFraction"); bb.Property(x => x.Enum).HasJsonPropertyName("OuterEnum"); bb.OwnsOne( x => x.Reference1, bbb => { bbb.HasJsonPropertyName("RenamedReference1"); bbb.Property(x => x.Date).HasJsonPropertyName("InnerDate"); bbb.Property(x => x.Fraction).HasJsonPropertyName("InnerFraction"); bbb.Property(x => x.Enum).HasJsonPropertyName("InnerEnum"); }); bb.OwnsOne( x => x.Reference2, bbb => { bbb.HasJsonPropertyName("RenamedReference2"); bbb.Property(x => x.Date).HasJsonPropertyName("InnerDate"); bbb.Property(x => x.Fraction).HasJsonPropertyName("InnerFraction"); bbb.Property(x => x.Enum).HasJsonPropertyName("InnerEnum"); }); bb.OwnsMany( x => x.Collection1, bbb => { bbb.HasJsonPropertyName("RenamedCollection1"); bbb.Property(x => x.Date).HasJsonPropertyName("InnerDate"); bbb.Property(x => x.Fraction).HasJsonPropertyName("InnerFraction"); bbb.Property(x => x.Enum).HasJsonPropertyName("InnerEnum"); }); bb.OwnsMany( x => x.Collection2, bbb => { bbb.HasJsonPropertyName("RenamedCollection2"); bbb.Property(x => x.Date).HasJsonPropertyName("InnerDate"); bbb.Property(x => x.Fraction).HasJsonPropertyName("InnerFraction"); bbb.Property(x => x.Enum).HasJsonPropertyName("InnerEnum"); }); bb.ToJson(); }); b.Ignore(x => x.OwnedReference2); b.Ignore(x => x.OwnedCollection2); }); var model = modelBuilder.FinalizeModel(); var outerOwnedEntities = model.FindEntityTypes(typeof(OwnedEntityExtraLevel)); Assert.Equal(2, outerOwnedEntities.Count()); foreach (var outerOwnedEntity in outerOwnedEntities) { Assert.Equal("OuterDate", outerOwnedEntity.GetProperty("Date").GetJsonPropertyName()); Assert.Equal("OuterFraction", outerOwnedEntity.GetProperty("Fraction").GetJsonPropertyName()); Assert.Equal("OuterEnum", outerOwnedEntity.GetProperty("Enum").GetJsonPropertyName()); Assert.Equal( "RenamedReference1", outerOwnedEntity.GetNavigations().Single(n => n.Name == "Reference1").TargetEntityType.GetJsonPropertyName()); Assert.Equal( "RenamedReference2", outerOwnedEntity.GetNavigations().Single(n => n.Name == "Reference2").TargetEntityType.GetJsonPropertyName()); Assert.Equal( "RenamedCollection1", outerOwnedEntity.GetNavigations().Single(n => n.Name == "Collection1").TargetEntityType.GetJsonPropertyName()); Assert.Equal( "RenamedCollection2", outerOwnedEntity.GetNavigations().Single(n => n.Name == "Collection2").TargetEntityType.GetJsonPropertyName()); } var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)); Assert.Equal(8, ownedEntities.Count()); foreach (var ownedEntity in ownedEntities) { Assert.Equal("InnerDate", ownedEntity.GetProperty("Date").GetJsonPropertyName()); Assert.Equal("InnerFraction", ownedEntity.GetProperty("Fraction").GetJsonPropertyName()); Assert.Equal("InnerEnum", ownedEntity.GetProperty("Enum").GetJsonPropertyName()); } } } public class JetModelBuilderFixture : RelationalModelBuilderFixture { public override TestHelpers TestHelpers => JetTestHelpers.Instance; } }