From cb3b1b68cc562cdf9166f08a92167449fb02e7fa Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Wed, 7 Feb 2024 02:59:22 +0000 Subject: [PATCH] [release/8.0] Handle complex types in GetDatabaseValues (#32894) * Handle complex types in GetDatabaseValues (#32813) Fixes #32701 Fixes #31353 * Fix merge and add quirks --- .../Internal/ArrayPropertyValues.cs | 7 +- .../Internal/EntryPropertyValues.cs | 8 +- src/EFCore/Internal/EntityFinder.cs | 55 +- .../Metadata/Internal/EntityTypeExtensions.cs | 6 +- .../Internal/EntityMaterializerSource.cs | 10 +- .../PropertyValuesInMemoryTest.cs | 40 +- .../PropertyValuesTestBase.cs | 501 +++++++++++++----- 7 files changed, 481 insertions(+), 146 deletions(-) diff --git a/src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs b/src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs index 011bb336e01..b0b0b9692b6 100644 --- a/src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs +++ b/src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Query.Internal; namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; @@ -99,7 +100,7 @@ public override void SetValues(PropertyValues propertyValues) for (var i = 0; i < _values.Length; i++) { - SetValue(i, propertyValues[Properties[i].Name]); + SetValue(i, EntityMaterializerSource.UseOldBehavior32701 ? propertyValues[Properties[i].Name] : propertyValues[Properties[i]]); } } @@ -110,7 +111,9 @@ public override void SetValues(PropertyValues propertyValues) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override IReadOnlyList Properties - => _properties ??= EntityType.GetProperties().ToList(); + => _properties ??= EntityMaterializerSource.UseOldBehavior32701 + ? EntityType.GetProperties().ToList() + : EntityType.GetFlattenedProperties().ToList(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/EntryPropertyValues.cs b/src/EFCore/ChangeTracking/Internal/EntryPropertyValues.cs index 927f2e1c678..3d3d68b973e 100644 --- a/src/EFCore/ChangeTracking/Internal/EntryPropertyValues.cs +++ b/src/EFCore/ChangeTracking/Internal/EntryPropertyValues.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Query.Internal; namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; @@ -94,7 +95,8 @@ public override void SetValues(PropertyValues propertyValues) foreach (var property in Properties) { - SetValueInternal(property, propertyValues[property.Name]); + SetValueInternal( + property, EntityMaterializerSource.UseOldBehavior32701 ? propertyValues[property.Name] : propertyValues[property]); } } @@ -107,7 +109,9 @@ public override void SetValues(PropertyValues propertyValues) public override IReadOnlyList Properties { [DebuggerStepThrough] - get => _properties ??= EntityType.GetProperties().ToList(); + get => _properties ??= EntityMaterializerSource.UseOldBehavior32701 + ? EntityType.GetProperties().ToList() + : EntityType.GetFlattenedProperties().ToList(); } /// diff --git a/src/EFCore/Internal/EntityFinder.cs b/src/EFCore/Internal/EntityFinder.cs index 6e95ffababe..cc9d612a886 100644 --- a/src/EFCore/Internal/EntityFinder.cs +++ b/src/EFCore/Internal/EntityFinder.cs @@ -3,6 +3,7 @@ using System.Collections; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Query.Internal; namespace Microsoft.EntityFrameworkCore.Internal; @@ -859,17 +860,53 @@ private static Expression> BuildProjection(IEntityType en var entityParameter = Expression.Parameter(typeof(object), "e"); var projections = new List(); - foreach (var property in entityType.GetProperties()) + + if (EntityMaterializerSource.UseOldBehavior32701) { - projections.Add( - Expression.Convert( + foreach (var property in entityType.GetProperties()) + { + projections.Add( Expression.Convert( - Expression.Call( - EF.PropertyMethod.MakeGenericMethod(property.ClrType), - entityParameter, - Expression.Constant(property.Name, typeof(string))), - property.ClrType), - typeof(object))); + Expression.Convert( + Expression.Call( + EF.PropertyMethod.MakeGenericMethod(property.ClrType), + entityParameter, + Expression.Constant(property.Name, typeof(string))), + property.ClrType), + typeof(object))); + } + } + else + { + foreach (var property in entityType.GetFlattenedProperties()) + { + var path = new List { property }; + while (path[^1].DeclaringType is IComplexType complexType) + { + path.Add(complexType.ComplexProperty); + } + + Expression instanceExpression = entityParameter; + for (var i = path.Count - 1; i >= 0; i--) + { + instanceExpression = Expression.Call( + EF.PropertyMethod.MakeGenericMethod(path[i].ClrType), + instanceExpression, + Expression.Constant(path[i].Name, typeof(string))); + + if (i != 0 && instanceExpression.Type.IsValueType) + { + instanceExpression = Expression.Convert(instanceExpression, typeof(object)); + } + } + + projections.Add( + Expression.Convert( + Expression.Convert( + instanceExpression, + property.ClrType), + typeof(object))); + } } return Expression.Lambda>( diff --git a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs index 30476e2396e..fcdf6afcd26 100644 --- a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs +++ b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs @@ -4,6 +4,8 @@ // ReSharper disable ArgumentsStyleOther // ReSharper disable ArgumentsStyleNamedExpression +using Microsoft.EntityFrameworkCore.Query.Internal; + namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// @@ -309,7 +311,9 @@ public static IProperty CheckPropertyBelongsToType( { Check.NotNull(property, nameof(property)); - if ((property.DeclaringType as IEntityType)?.IsAssignableFrom(entityType) != true) + if ((EntityMaterializerSource.UseOldBehavior32701 + && (property.DeclaringType as IEntityType)?.IsAssignableFrom(entityType) != true) + || !property.DeclaringType.ContainingEntityType.IsAssignableFrom(entityType)) { throw new InvalidOperationException( CoreStrings.PropertyDoesNotBelong(property.Name, property.DeclaringType.DisplayName(), entityType.DisplayName())); diff --git a/src/EFCore/Query/Internal/EntityMaterializerSource.cs b/src/EFCore/Query/Internal/EntityMaterializerSource.cs index b1a72bc24c3..7c1d09a73ec 100644 --- a/src/EFCore/Query/Internal/EntityMaterializerSource.cs +++ b/src/EFCore/Query/Internal/EntityMaterializerSource.cs @@ -24,6 +24,9 @@ public class EntityMaterializerSource : IEntityMaterializerSource public static readonly bool UseOldBehavior31866 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue31866", out var enabled31866) && enabled31866; + internal static readonly bool UseOldBehavior32701 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32701", out var enabled32701) && enabled32701; + private static readonly MethodInfo InjectableServiceInjectedMethod = typeof(IInjectableService).GetMethod(nameof(IInjectableService.Injected))!; @@ -117,15 +120,16 @@ public Expression CreateMaterializeExpression( var constructorExpression = constructorBinding.CreateConstructorExpression(bindingInfo); - if (_materializationInterceptor == null) + if (_materializationInterceptor == null + // TODO: This currently applies the materialization interceptor only on the root structural type - any contained complex types + // don't get intercepted. + || (structuralType is not IEntityType && !UseOldBehavior32701)) { return properties.Count == 0 && blockExpressions.Count == 0 ? constructorExpression : CreateMaterializeExpression(blockExpressions, instanceVariable, constructorExpression, properties, bindingInfo); } - // TODO: This currently applies the materialization interceptor only on the root structural type - any contained complex types - // don't get intercepted. return CreateInterceptionMaterializeExpression( structuralType, properties, diff --git a/test/EFCore.InMemory.FunctionalTests/PropertyValuesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/PropertyValuesInMemoryTest.cs index 9e06e8afced..dd2d0dd74e7 100644 --- a/test/EFCore.InMemory.FunctionalTests/PropertyValuesInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/PropertyValuesInMemoryTest.cs @@ -3,19 +3,47 @@ namespace Microsoft.EntityFrameworkCore; -public class PropertyValuesInMemoryTest : PropertyValuesTestBase +public class PropertyValuesInMemoryTest(PropertyValuesInMemoryTest.PropertyValuesInMemoryFixture fixture) + : PropertyValuesTestBase(fixture) { - public PropertyValuesInMemoryTest(PropertyValuesInMemoryFixture fixture) - : base(fixture) - { - } + public override Task Complex_current_values_can_be_accessed_as_a_property_dictionary_using_IProperty() + => Assert.ThrowsAsync( // In-memory database cannot query complex types + () => base.Complex_current_values_can_be_accessed_as_a_property_dictionary_using_IProperty()); + + public override Task Complex_original_values_can_be_accessed_as_a_property_dictionary_using_IProperty() + => Assert.ThrowsAsync( // In-memory database cannot query complex types + () => base.Complex_original_values_can_be_accessed_as_a_property_dictionary_using_IProperty()); + + public override Task Complex_store_values_can_be_accessed_as_a_property_dictionary_using_IProperty() + => Assert.ThrowsAsync( // In-memory database cannot query complex types + () => base.Complex_store_values_can_be_accessed_as_a_property_dictionary_using_IProperty()); + + public override Task Complex_store_values_can_be_accessed_asynchronously_as_a_property_dictionary_using_IProperty() + => Assert.ThrowsAsync( // In-memory database cannot query complex types + () => base.Complex_store_values_can_be_accessed_asynchronously_as_a_property_dictionary_using_IProperty()); public class PropertyValuesInMemoryFixture : PropertyValuesFixtureBase { public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) - => base.AddOptions(builder).EnableSensitiveDataLogging(false); + => base.AddOptions(builder) + .ConfigureWarnings(w => w.Ignore(CoreEventId.MappedComplexPropertyIgnoredWarning)) + .EnableSensitiveDataLogging(false); protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + base.OnModelCreating(modelBuilder, context); + + // In-memory database doesn't support complex type queries + modelBuilder.Entity( + b => + { + b.Ignore(e => e.Culture); + b.Ignore(e => e.Milk); + }); + + } } } diff --git a/test/EFCore.Specification.Tests/PropertyValuesTestBase.cs b/test/EFCore.Specification.Tests/PropertyValuesTestBase.cs index 078d9371725..13a78faf380 100644 --- a/test/EFCore.Specification.Tests/PropertyValuesTestBase.cs +++ b/test/EFCore.Specification.Tests/PropertyValuesTestBase.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + using System.ComponentModel.DataAnnotations.Schema; using System.Globalization; @@ -28,11 +30,11 @@ public virtual Task Scalar_original_values_can_be_accessed_as_a_property_diction [ConditionalFact] public virtual Task Scalar_store_values_can_be_accessed_as_a_property_dictionary() - => TestPropertyValuesScalars(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestPropertyValuesScalars(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Scalar_store_values_can_be_accessed_asynchronously_as_a_property_dictionary() - => TestPropertyValuesScalars(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestPropertyValuesScalars(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestPropertyValuesScalars( Func, Task> getPropertyValues, @@ -78,11 +80,11 @@ public virtual Task Scalar_original_values_can_be_accessed_as_a_property_diction [ConditionalFact] public virtual Task Scalar_store_values_can_be_accessed_as_a_property_dictionary_using_IProperty() - => TestPropertyValuesScalarsIProperty(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestPropertyValuesScalarsIProperty(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Scalar_store_values_can_be_accessed_asynchronously_as_a_property_dictionary_using_IProperty() - => TestPropertyValuesScalarsIProperty(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestPropertyValuesScalarsIProperty(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestPropertyValuesScalarsIProperty( Func, Task> getPropertyValues, @@ -129,11 +131,11 @@ public virtual Task Scalar_original_values_of_a_derived_object_can_be_accessed_a [ConditionalFact] public virtual Task Scalar_store_values_of_a_derived_object_can_be_accessed_as_a_property_dictionary() - => TestPropertyValuesDerivedScalars(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestPropertyValuesDerivedScalars(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Scalar_store_values_of_a_derived_object_can_be_accessed_asynchronously_as_a_property_dictionary() - => TestPropertyValuesDerivedScalars(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestPropertyValuesDerivedScalars(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestPropertyValuesDerivedScalars( Func, Task> getPropertyValues, @@ -182,11 +184,11 @@ public virtual Task Scalar_original_values_can_be_accessed_as_a_non_generic_prop [ConditionalFact] public virtual Task Scalar_store_values_can_be_accessed_as_a_non_generic_property_dictionary() - => TestNonGenericPropertyValuesScalars(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestNonGenericPropertyValuesScalars(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Scalar_store_values_can_be_accessed_asynchronously_as_a_non_generic_property_dictionary() - => TestNonGenericPropertyValuesScalars(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestNonGenericPropertyValuesScalars(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestNonGenericPropertyValuesScalars( Func> getPropertyValues, @@ -238,11 +240,11 @@ public virtual Task Scalar_original_values_can_be_accessed_as_a_non_generic_prop [ConditionalFact] public virtual Task Scalar_store_values_can_be_accessed_as_a_non_generic_property_dictionary_using_IProperty() - => TestNonGenericPropertyValuesScalarsIProperty(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestNonGenericPropertyValuesScalarsIProperty(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Scalar_store_values_can_be_accessed_asynchronously_as_a_non_generic_property_dictionary_using_IProperty() - => TestNonGenericPropertyValuesScalarsIProperty(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestNonGenericPropertyValuesScalarsIProperty(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestNonGenericPropertyValuesScalarsIProperty( Func> getPropertyValues, @@ -291,11 +293,11 @@ public virtual Task Scalar_original_values_of_a_derived_object_can_be_accessed_a [ConditionalFact] public virtual Task Scalar_store_values_of_a_derived_object_can_be_accessed_as_a_non_generic_property_dictionary() - => TestNonGenericPropertyValuesDerivedScalars(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestNonGenericPropertyValuesDerivedScalars(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Scalar_store_values_of_a_derived_object_can_be_accessed_asynchronously_as_a_non_generic_property_dictionary() - => TestNonGenericPropertyValuesDerivedScalars(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestNonGenericPropertyValuesDerivedScalars(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestNonGenericPropertyValuesDerivedScalars( Func> getPropertyValues, @@ -332,11 +334,11 @@ private async Task TestNonGenericPropertyValuesDerivedScalars( [ConditionalFact] public virtual void Scalar_current_values_can_be_set_using_a_property_dictionary() - => TestSetPropertyValuesScalars(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestSetPropertyValuesScalars(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Scalar_original_values_can_be_set_using_a_property_dictionary() - => TestSetPropertyValuesScalars(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestSetPropertyValuesScalars(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestSetPropertyValuesScalars( Func, PropertyValues> getPropertyValues, @@ -365,11 +367,11 @@ private void TestSetPropertyValuesScalars( [ConditionalFact] public virtual void Scalar_current_values_can_be_set_using_a_property_dictionary_with_IProperty() - => TestSetPropertyValuesScalarsIProperty(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestSetPropertyValuesScalarsIProperty(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Scalar_original_values_can_be_set_using_a_property_dictionary_with_IProperty() - => TestSetPropertyValuesScalarsIProperty(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestSetPropertyValuesScalarsIProperty(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestSetPropertyValuesScalarsIProperty( Func, PropertyValues> getPropertyValues, @@ -398,11 +400,11 @@ private void TestSetPropertyValuesScalarsIProperty( [ConditionalFact] public virtual void Scalar_current_values_can_be_set_using_a_non_generic_property_dictionary() - => TestSetNonGenericPropertyValuesScalars(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestSetNonGenericPropertyValuesScalars(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Scalar_original_values_can_be_set_using_a_non_generic_property_dictionary() - => TestSetNonGenericPropertyValuesScalars(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestSetNonGenericPropertyValuesScalars(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestSetNonGenericPropertyValuesScalars( Func getPropertyValues, @@ -429,6 +431,96 @@ private void TestSetNonGenericPropertyValuesScalars( Assert.Equal("Pine Walk", getValue(entry, "Shadow2")); } + [ConditionalFact] + public virtual Task Complex_current_values_can_be_accessed_as_a_property_dictionary_using_IProperty() + => TestPropertyValuesComplexIProperty(e => Task.FromResult(e.CurrentValues), expectOriginalValues: false); + + [ConditionalFact] + public virtual Task Complex_original_values_can_be_accessed_as_a_property_dictionary_using_IProperty() + => TestPropertyValuesComplexIProperty(e => Task.FromResult(e.OriginalValues), expectOriginalValues: true); + + [ConditionalFact] + public virtual Task Complex_store_values_can_be_accessed_as_a_property_dictionary_using_IProperty() + => TestPropertyValuesComplexIProperty(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); + + [ConditionalFact] + public virtual Task Complex_store_values_can_be_accessed_asynchronously_as_a_property_dictionary_using_IProperty() + => TestPropertyValuesComplexIProperty(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); + + private async Task TestPropertyValuesComplexIProperty( + Func, Task> getPropertyValues, + bool expectOriginalValues) + { + using var context = CreateContext(); + var building = context.Set().Single(b => b.Name == "Building One"); + var original = Building.Create(building.BuildingId, building.Name!, building.Value); + var changed = Building.Create(building.BuildingId, building.Name!, building.Value, 1); + + building.Culture = changed.Culture; + building.Milk.Rating = changed.Milk.Rating; + building.Milk.License = changed.Milk.License; + building.Milk.Manufacturer = changed.Milk.Manufacturer; + + var entry = context.Entry(building); + var values = await getPropertyValues(entry); + + var cultureEntry = entry.ComplexProperty(e => e.Culture); + var cultureManufacturerEntry = cultureEntry.ComplexProperty(e => e.Manufacturer); + var cultureLicenseEntry = cultureEntry.ComplexProperty(e => e.License); + var cultureManTogEntry = cultureManufacturerEntry.ComplexProperty(e => e.Tog); + var cultureManTagEntry = cultureManufacturerEntry.ComplexProperty(e => e.Tag); + var cultureLicTogEntry = cultureLicenseEntry.ComplexProperty(e => e.Tog); + var cultureLicTagEntry = cultureLicenseEntry.ComplexProperty(e => e.Tag); + + var milkEntry = entry.ComplexProperty(e => e.Milk); + var milkManufacturerEntry = milkEntry.ComplexProperty(e => e.Manufacturer); + var milkLicenseEntry = milkEntry.ComplexProperty(e => e.License); + var milkManTogEntry = milkManufacturerEntry.ComplexProperty(e => e.Tog); + var milkManTagEntry = milkManufacturerEntry.ComplexProperty(e => e.Tag); + var milkLicTogEntry = milkLicenseEntry.ComplexProperty(e => e.Tog); + var milkLicTagEntry = milkLicenseEntry.ComplexProperty(e => e.Tag); + + var expected = expectOriginalValues ? original : changed; + Assert.Equal(expected.Culture.Rating, values[cultureEntry.Property(e => e.Rating).Metadata]); + Assert.Equal(expected.Culture.Species, values[cultureEntry.Property(e => e.Species).Metadata]); + Assert.Equal(expected.Culture.Subspecies, values[cultureEntry.Property(e => e.Subspecies).Metadata]); + Assert.Equal(expected.Culture.Validation, values[cultureEntry.Property(e => e.Validation).Metadata]); + Assert.Equal(expected.Culture.Manufacturer.Name, values[cultureManufacturerEntry.Property(e => e.Name).Metadata]); + Assert.Equal(expected.Culture.Manufacturer.Rating, values[cultureManufacturerEntry.Property(e => e.Rating).Metadata]); + Assert.Equal(expected.Culture.Manufacturer.Tog.Text, values[cultureManTogEntry.Property(e => e.Text).Metadata]); + Assert.Equal(expected.Culture.Manufacturer.Tag.Text, values[cultureManTagEntry.Property(e => e.Text).Metadata]); + Assert.Equal(expected.Culture.License.Title, values[cultureLicenseEntry.Property(e => e.Title).Metadata]); + Assert.Equal(expected.Culture.License.Charge, values[cultureLicenseEntry.Property(e => e.Charge).Metadata]); + Assert.Equal(expected.Culture.License.Tog.Text, values[cultureLicTogEntry.Property(e => e.Text).Metadata]); + Assert.Equal(expected.Culture.License.Tag.Text, values[cultureLicTagEntry.Property(e => e.Text).Metadata]); + Assert.Equal(expected.Milk.Rating, values[milkEntry.Property(e => e.Rating).Metadata]); + Assert.Equal(expected.Milk.Manufacturer.Name, values[milkManufacturerEntry.Property(e => e.Name).Metadata]); + Assert.Equal(expected.Milk.Manufacturer.Rating, values[milkManufacturerEntry.Property(e => e.Rating).Metadata]); + Assert.Equal(expected.Milk.Manufacturer.Tog.Text, values[milkManTogEntry.Property(e => e.Text).Metadata]); + Assert.Equal(expected.Milk.Manufacturer.Tag.Text, values[milkManTagEntry.Property(e => e.Text).Metadata]); + Assert.Equal(expected.Milk.License.Title, values[milkLicenseEntry.Property(e => e.Title).Metadata]); + Assert.Equal(expected.Milk.License.Charge, values[milkLicenseEntry.Property(e => e.Charge).Metadata]); + Assert.Equal(expected.Milk.License.Tog.Text, values[milkLicTogEntry.Property(e => e.Text).Metadata]); + Assert.Equal(expected.Milk.License.Tag.Text, values[milkLicTagEntry.Property(e => e.Text).Metadata]); + + if (expectOriginalValues) + { + Assert.Equal(original.Milk.Species, values[milkEntry.Property(e => e.Species).Metadata]); + Assert.Equal(original.Milk.Subspecies, values[milkEntry.Property(e => e.Subspecies).Metadata]); + Assert.Equal(original.Milk.Validation, values[milkEntry.Property(e => e.Validation).Metadata]); + } + else + { + Assert.Equal(building.Milk.Species, values[milkEntry.Property(e => e.Species).Metadata]); + Assert.Equal(building.Milk.Subspecies, values[milkEntry.Property(e => e.Subspecies).Metadata]); + Assert.Equal(building.Milk.Validation, values[milkEntry.Property(e => e.Validation).Metadata]); + } + + Assert.True(building.CreatedCalled); + Assert.True(building.InitializingCalled); + Assert.True(building.InitializedCalled); + } + [ConditionalFact] public virtual Task Current_values_can_be_copied_into_an_object() => TestPropertyValuesClone(e => Task.FromResult(e.CurrentValues), expectOriginalValues: false); @@ -439,11 +531,11 @@ public virtual Task Original_values_can_be_copied_into_an_object() [ConditionalFact] public virtual Task Store_values_can_be_copied_into_an_object() - => TestPropertyValuesClone(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestPropertyValuesClone(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_copied_into_an_object_asynchronously() - => TestPropertyValuesClone(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestPropertyValuesClone(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestPropertyValuesClone( Func, Task> getPropertyValues, @@ -485,11 +577,11 @@ public virtual Task Original_values_for_derived_object_can_be_copied_into_an_obj [ConditionalFact] public virtual Task Store_values_for_derived_object_can_be_copied_into_an_object() - => TestPropertyValuesDerivedClone(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestPropertyValuesDerivedClone(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_for_derived_object_can_be_copied_into_an_object_asynchronously() - => TestPropertyValuesDerivedClone(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestPropertyValuesDerivedClone(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestPropertyValuesDerivedClone( Func, Task> getPropertyValues, @@ -534,11 +626,11 @@ public virtual Task Original_values_for_join_entity_can_be_copied_into_an_object [ConditionalFact] public virtual Task Store_values_for_join_entity_can_be_copied_into_an_object() - => TestPropertyValuesJoinEntityClone(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestPropertyValuesJoinEntityClone(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_for_join_entity_can_be_copied_into_an_object_asynchronously() - => TestPropertyValuesJoinEntityClone(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestPropertyValuesJoinEntityClone(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestPropertyValuesJoinEntityClone( Func>, Task> getPropertyValues, @@ -582,11 +674,11 @@ public virtual Task Original_values_can_be_copied_non_generic_property_dictionar [ConditionalFact] public virtual Task Store_values_can_be_copied_non_generic_property_dictionary_into_an_object() - => TestNonGenericPropertyValuesClone(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestNonGenericPropertyValuesClone(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_copied_asynchronously_non_generic_property_dictionary_into_an_object() - => TestNonGenericPropertyValuesClone(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestNonGenericPropertyValuesClone(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestNonGenericPropertyValuesClone( Func> getPropertyValues, @@ -628,11 +720,11 @@ public virtual Task Original_values_can_be_copied_into_a_cloned_dictionary() [ConditionalFact] public virtual Task Store_values_can_be_copied_into_a_cloned_dictionary() - => TestPropertyValuesCloneToValues(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestPropertyValuesCloneToValues(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_copied_into_a_cloned_dictionary_asynchronously() - => TestPropertyValuesCloneToValues(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestPropertyValuesCloneToValues(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestPropertyValuesCloneToValues( Func, Task> getPropertyValues, @@ -770,7 +862,7 @@ public virtual void Using_bad_IProperty_instances_throws() var buildingValues = entry.CurrentValues; var clonedBuildingValues = buildingValues.Clone(); - var property = context.Model.FindEntityType(typeof(Whiteboard)).FindProperty(nameof(Whiteboard.AssetTag)); + var property = context.Model.FindEntityType(typeof(Whiteboard))!.FindProperty(nameof(Whiteboard.AssetTag))!; Assert.Equal( CoreStrings.PropertyDoesNotBelong("AssetTag", nameof(Whiteboard), nameof(Building)), @@ -854,8 +946,8 @@ public virtual void Using_bad_IProperty_instances_throws_derived() var values = entry.CurrentValues; var clonedValues = values.Clone(); - var shadowProperty = context.Model.FindEntityType(typeof(PastEmployee)).FindProperty("Shadow4"); - var termProperty = context.Model.FindEntityType(typeof(PastEmployee)).FindProperty(nameof(PastEmployee.TerminationDate)); + var shadowProperty = context.Model.FindEntityType(typeof(PastEmployee))!.FindProperty("Shadow4")!; + var termProperty = context.Model.FindEntityType(typeof(PastEmployee))!.FindProperty(nameof(PastEmployee.TerminationDate))!; Assert.Equal( CoreStrings.PropertyDoesNotBelong("Shadow4", nameof(PastEmployee), nameof(CurrentEmployee)), @@ -908,11 +1000,11 @@ public virtual Task Original_values_can_be_copied_into_a_non_generic_cloned_dict [ConditionalFact] public virtual Task Store_values_can_be_copied_into_a_non_generic_cloned_dictionary() - => TestNonGenericPropertyValuesCloneToValues(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestNonGenericPropertyValuesCloneToValues(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_copied_asynchronously_into_a_non_generic_cloned_dictionary() - => TestNonGenericPropertyValuesCloneToValues(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestNonGenericPropertyValuesCloneToValues(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestNonGenericPropertyValuesCloneToValues( Func> getPropertyValues, @@ -990,11 +1082,11 @@ public virtual Task Original_values_can_be_read_and_set_for_an_object_in_the_Del [ConditionalFact] public virtual Task Store_values_can_be_read_and_set_for_an_object_in_the_Deleted_state() => TestPropertyValuesPositiveForState( - e => Task.FromResult(e.GetDatabaseValues()), EntityState.Deleted, expectOriginalValues: true); + e => Task.FromResult(e.GetDatabaseValues()!), EntityState.Deleted, expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_read_and_set_for_an_object_in_the_Deleted_state_asynchronously() - => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync(), EntityState.Deleted, expectOriginalValues: true); + => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync()!, EntityState.Deleted, expectOriginalValues: true); [ConditionalFact] public virtual Task Current_values_can_be_read_and_set_for_an_object_in_the_Unchanged_state() @@ -1009,11 +1101,11 @@ public virtual Task Original_values_can_be_read_and_set_for_an_object_in_the_Unc [ConditionalFact] public virtual Task Store_values_can_be_read_and_set_for_an_object_in_the_Unchanged_state() => TestPropertyValuesPositiveForState( - e => Task.FromResult(e.GetDatabaseValues()), EntityState.Unchanged, expectOriginalValues: true); + e => Task.FromResult(e.GetDatabaseValues()!), EntityState.Unchanged, expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_read_and_set_for_an_object_in_the_Unchanged_state_asynchronously() - => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync(), EntityState.Unchanged, expectOriginalValues: true); + => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync()!, EntityState.Unchanged, expectOriginalValues: true); [ConditionalFact] public virtual Task Current_values_can_be_read_and_set_for_an_object_in_the_Modified_state() @@ -1028,11 +1120,11 @@ public virtual Task Original_values_can_be_read_and_set_for_an_object_in_the_Mod [ConditionalFact] public virtual Task Store_values_can_be_read_and_set_for_an_object_in_the_Modified_state() => TestPropertyValuesPositiveForState( - e => Task.FromResult(e.GetDatabaseValues()), EntityState.Modified, expectOriginalValues: true); + e => Task.FromResult(e.GetDatabaseValues()!), EntityState.Modified, expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_read_and_set_for_an_object_in_the_Modified_state_asynchronously() - => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync(), EntityState.Modified, expectOriginalValues: true); + => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync()!, EntityState.Modified, expectOriginalValues: true); [ConditionalFact] public virtual Task Current_values_can_be_read_and_set_for_an_object_in_the_Added_state() @@ -1047,11 +1139,11 @@ public virtual Task Original_values_can_be_read_or_set_for_an_object_in_the_Adde [ConditionalFact] public virtual Task Store_values_can_be_read_or_set_for_an_object_in_the_Added_state() => TestPropertyValuesPositiveForState( - e => Task.FromResult(e.GetDatabaseValues()), EntityState.Detached, expectOriginalValues: true); + e => Task.FromResult(e.GetDatabaseValues()!), EntityState.Detached, expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_read_or_set_for_an_object_in_the_Added_state_asynchronously() - => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync(), EntityState.Detached, expectOriginalValues: true); + => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync()!, EntityState.Detached, expectOriginalValues: true); [ConditionalFact] public virtual Task Current_values_can_be_read_or_set_for_a_Detached_object() @@ -1066,11 +1158,11 @@ public virtual Task Original_values_can_be_read_or_set_for_a_Detached_object() [ConditionalFact] public virtual Task Store_values_can_be_read_or_set_for_a_Detached_object() => TestPropertyValuesPositiveForState( - e => Task.FromResult(e.GetDatabaseValues()), EntityState.Detached, expectOriginalValues: true); + e => Task.FromResult(e.GetDatabaseValues()!), EntityState.Detached, expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_read_or_set_for_a_Detached_object_asynchronously() - => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync(), EntityState.Detached, expectOriginalValues: true); + => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync()!, EntityState.Detached, expectOriginalValues: true); private async Task TestPropertyValuesPositiveForState( Func, Task> getPropertyValues, @@ -1194,11 +1286,11 @@ public async Task Reload_when_entity_deleted_in_store_can_happen_for_any_state(E [ConditionalFact] public virtual void Current_values_can_be_set_from_an_object_using_generic_dictionary() - => TestGenericObjectSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestGenericObjectSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_an_object_using_generic_dictionary() - => TestGenericObjectSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestGenericObjectSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestGenericObjectSetValues( Func, PropertyValues> getPropertyValues, @@ -1245,11 +1337,11 @@ private static void ValidateBuildingPropereties( [ConditionalFact] public virtual void Current_values_can_be_set_from_an_object_using_non_generic_dictionary() - => TestNonGenericObjectSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestNonGenericObjectSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_an_object_using_non_generic_dictionary() - => TestNonGenericObjectSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestNonGenericObjectSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestNonGenericObjectSetValues( Func getPropertyValues, @@ -1278,11 +1370,11 @@ private void TestNonGenericObjectSetValues( [ConditionalFact] public virtual void Current_values_can_be_set_from_DTO_object_using_non_generic_dictionary() - => TestNonGenericDtoSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestNonGenericDtoSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_DTO_object_using_non_generic_dictionary() - => TestNonGenericDtoSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestNonGenericDtoSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestNonGenericDtoSetValues( Func getPropertyValues, @@ -1314,11 +1406,11 @@ private void TestNonGenericDtoSetValues( [ConditionalFact] public virtual void Current_values_can_be_set_from_DTO_object_missing_key_using_non_generic_dictionary() - => TestNonGenericDtoNoKeySetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestNonGenericDtoNoKeySetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_DTO_object_missing_key_using_non_generic_dictionary() - => TestNonGenericDtoNoKeySetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestNonGenericDtoNoKeySetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestNonGenericDtoNoKeySetValues( Func getPropertyValues, @@ -1348,11 +1440,11 @@ private void TestNonGenericDtoNoKeySetValues( [ConditionalFact] public virtual void Current_values_can_be_set_from_dictionary() - => TestDictionarySetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestDictionarySetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_dictionary() - => TestDictionarySetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestDictionarySetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestDictionarySetValues( Func getPropertyValues, @@ -1386,11 +1478,11 @@ private void TestDictionarySetValues( [ConditionalFact] public virtual void Current_values_can_be_set_from_dictionary_typed_int() - => TestDictionarySetValuesTypedInt(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestDictionarySetValuesTypedInt(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_dictionary_typed_int() - => TestDictionarySetValuesTypedInt(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestDictionarySetValuesTypedInt(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestDictionarySetValuesTypedInt( Func getPropertyValues, @@ -1423,11 +1515,11 @@ private void TestDictionarySetValuesTypedInt( [ConditionalFact] public virtual void Current_values_can_be_set_from_dictionary_typed_string() - => TestDictionarySetValuesTypedString(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestDictionarySetValuesTypedString(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_dictionary_typed_string() - => TestDictionarySetValuesTypedString(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestDictionarySetValuesTypedString(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestDictionarySetValuesTypedString( Func getPropertyValues, @@ -1463,11 +1555,11 @@ private void TestDictionarySetValuesTypedString( [ConditionalFact] public virtual void Current_values_can_be_set_from_dictionary_some_missing() - => TestPartialDictionarySetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestPartialDictionarySetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_dictionary_some_missing() - => TestPartialDictionarySetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestPartialDictionarySetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestPartialDictionarySetValues( Func getPropertyValues, @@ -1499,11 +1591,11 @@ private void TestPartialDictionarySetValues( [ConditionalFact] public virtual void Current_values_can_be_set_from_one_generic_dictionary_to_another_generic_dictionary() - => TestGenericValuesSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestGenericValuesSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_one_generic_dictionary_to_another_generic_dictionary() - => TestGenericValuesSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestGenericValuesSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestGenericValuesSetValues( Func, PropertyValues> getPropertyValues, @@ -1535,11 +1627,11 @@ private void TestGenericValuesSetValues( [ConditionalFact] public virtual void Current_values_can_be_set_from_one_non_generic_dictionary_to_another_generic_dictionary() - => TestNonGenericValuesSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestNonGenericValuesSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_one_non_generic_dictionary_to_another_generic_dictionary() - => TestNonGenericValuesSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestNonGenericValuesSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestNonGenericValuesSetValues( Func getPropertyValues, @@ -1606,7 +1698,7 @@ public virtual void Non_nullable_property_in_current_values_results_in_conceptua if (deleteOrphansTiming == CascadeTiming.Immediate) { - if (context.GetService().FindExtension().IsSensitiveDataLoggingEnabled) + if (context.GetService().FindExtension()!.IsSensitiveDataLoggingEnabled) { Assert.Equal( CoreStrings.PropertyConceptualNullSensitive( @@ -1650,7 +1742,7 @@ public virtual void Non_nullable_shadow_property_in_current_values_results_in_co if (deleteOrphansTiming == CascadeTiming.Immediate) { - if (context.GetService().FindExtension().IsSensitiveDataLoggingEnabled) + if (context.GetService().FindExtension()!.IsSensitiveDataLoggingEnabled) { Assert.Equal( CoreStrings.PropertyConceptualNullSensitive("Shadow1", nameof(Building), "{Shadow1: 11}"), @@ -1821,11 +1913,11 @@ public virtual Task Properties_for_original_values_returns_properties() [ConditionalFact] public virtual Task Properties_for_store_values_returns_properties() - => TestProperties(e => Task.FromResult(e.GetDatabaseValues())); + => TestProperties(e => Task.FromResult(e.GetDatabaseValues()!)); [ConditionalFact] public virtual Task Properties_for_store_values_returns_properties_asynchronously() - => TestProperties(e => e.GetDatabaseValuesAsync()); + => TestProperties(e => e.GetDatabaseValuesAsync()!); [ConditionalFact] public virtual Task Properties_for_cloned_dictionary_returns_properties() @@ -1836,27 +1928,67 @@ private async Task TestProperties(Func, Task().Single(b => b.Name == "Building One"); var buildingValues = await getPropertyValues(context.Entry(building)); + var properties = buildingValues.Properties.Select(p => (p.DeclaringType.DisplayName(), p.Name)).ToList(); - Assert.Equal( - new List - { - "BuildingId", - "Name", - "PrincipalMailRoomId", - "Shadow1", - "Shadow2", - "Value" - }, - buildingValues.Properties.Select(p => p.Name).ToList()); + if (context.Model.FindEntityType(typeof(Building))!.GetComplexProperties().Any()) + { + Assert.Equal( + [ + ("Building", "BuildingId"), + ("Building", "Name"), + ("Building", "PrincipalMailRoomId"), + ("Building", "Shadow1"), + ("Building", "Shadow2"), + ("Building", "Value"), + ("Building.Culture#Culture", "Rating"), + ("Building.Culture#Culture", "Species"), + ("Building.Culture#Culture", "Subspecies"), + ("Building.Culture#Culture", "Validation"), + ("Building.Culture#Culture.License#License", "Charge"), + ("Building.Culture#Culture.License#License", "Title"), + ("Building.Culture#Culture.License#License.Tag#Tag", "Text"), + ("Building.Culture#Culture.License#License.Tog#Tog", "Text"), + ("Building.Culture#Culture.Manufacturer#Manufacturer", "Name"), + ("Building.Culture#Culture.Manufacturer#Manufacturer", "Rating"), + ("Building.Culture#Culture.Manufacturer#Manufacturer.Tag#Tag", "Text"), + ("Building.Culture#Culture.Manufacturer#Manufacturer.Tog#Tog", "Text"), + ("Building.Milk#Milk", "Rating"), + ("Building.Milk#Milk", "Species"), + ("Building.Milk#Milk", "Subspecies"), + ("Building.Milk#Milk", "Validation"), + ("Building.Milk#Milk.License#License", "Charge"), + ("Building.Milk#Milk.License#License", "Title"), + ("Building.Milk#Milk.License#License.Tag#Tag", "Text"), + ("Building.Milk#Milk.License#License.Tog#Tog", "Text"), + ("Building.Milk#Milk.Manufacturer#Manufacturer", "Name"), + ("Building.Milk#Milk.Manufacturer#Manufacturer", "Rating"), + ("Building.Milk#Milk.Manufacturer#Manufacturer.Tag#Tag", "Text"), + ("Building.Milk#Milk.Manufacturer#Manufacturer.Tog#Tog", "Text"), + ], + properties); + } + else + { + Assert.Equal( + [ + ("Building", "BuildingId"), + ("Building", "Name"), + ("Building", "PrincipalMailRoomId"), + ("Building", "Shadow1"), + ("Building", "Shadow2"), + ("Building", "Value"), + ], + properties); + } } [ConditionalFact] public virtual Task GetDatabaseValues_for_entity_not_in_the_store_returns_null() - => GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation(e => Task.FromResult(e.GetDatabaseValues())); + => GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation(e => Task.FromResult(e.GetDatabaseValues()!)); [ConditionalFact] public virtual Task GetDatabaseValuesAsync_for_entity_not_in_the_store_returns_null() - => GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation(e => e.GetDatabaseValuesAsync()); + => GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation(e => e.GetDatabaseValuesAsync()!); private async Task GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation( Func> getPropertyValues) @@ -1875,11 +2007,11 @@ private async Task GetDatabaseValues_for_entity_not_in_the_store_returns_null_im [ConditionalFact] public virtual Task NonGeneric_GetDatabaseValues_for_entity_not_in_the_store_returns_null() => NonGeneric_GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation( - e => Task.FromResult(e.GetDatabaseValues())); + e => Task.FromResult(e.GetDatabaseValues()!)); [ConditionalFact] public virtual Task NonGeneric_GetDatabaseValuesAsync_for_entity_not_in_the_store_returns_null() - => NonGeneric_GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation(e => e.GetDatabaseValuesAsync()); + => NonGeneric_GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation(e => e.GetDatabaseValuesAsync()!); private async Task NonGeneric_GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation( Func> getPropertyValues) @@ -1902,11 +2034,11 @@ private async Task NonGeneric_GetDatabaseValues_for_entity_not_in_the_store_retu [ConditionalFact] public virtual Task GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null() => GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null_implementation( - e => Task.FromResult(e.GetDatabaseValues())); + e => Task.FromResult(e.GetDatabaseValues()!)); [ConditionalFact] public virtual Task GetDatabaseValuesAsync_for_derived_entity_not_in_the_store_returns_null() - => GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null_implementation(e => e.GetDatabaseValuesAsync()); + => GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null_implementation(e => e.GetDatabaseValuesAsync()!); private async Task GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null_implementation( Func> getPropertyValues) @@ -1928,12 +2060,12 @@ private async Task GetDatabaseValues_for_derived_entity_not_in_the_store_returns [ConditionalFact] public virtual Task NonGeneric_GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null() => NonGeneric_GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null_implementation( - e => Task.FromResult(e.GetDatabaseValues())); + e => Task.FromResult(e.GetDatabaseValues()!)); [ConditionalFact] public virtual Task NonGeneric_GetDatabaseValuesAsync_for_derived_entity_not_in_the_store_returns_null() => NonGeneric_GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null_implementation( - e => e.GetDatabaseValuesAsync()); + e => e.GetDatabaseValuesAsync()!); private async Task NonGeneric_GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null_implementation( Func> getPropertyValues) @@ -1955,11 +2087,11 @@ private async Task NonGeneric_GetDatabaseValues_for_derived_entity_not_in_the_st [ConditionalFact] public virtual Task GetDatabaseValues_for_the_wrong_type_in_the_store_returns_null() => GetDatabaseValues_for_the_wrong_type_in_the_store_returns_null_implementation( - e => Task.FromResult(e.GetDatabaseValues())); + e => Task.FromResult(e.GetDatabaseValues()!)); [ConditionalFact] public virtual Task GetDatabaseValuesAsync_for_the_wrong_type_in_the_store_returns_null() - => GetDatabaseValues_for_the_wrong_type_in_the_store_returns_null_implementation(e => e.GetDatabaseValuesAsync()); + => GetDatabaseValues_for_the_wrong_type_in_the_store_returns_null_implementation(e => e.GetDatabaseValuesAsync()!); private async Task GetDatabaseValues_for_the_wrong_type_in_the_store_returns_null_implementation( Func> getPropertyValues) @@ -1969,7 +2101,7 @@ private async Task GetDatabaseValues_for_the_wrong_type_in_the_store_returns_nul .OfType() .AsNoTracking() .OrderBy(e => e.EmployeeId) - .FirstOrDefault() + .FirstOrDefault()! .EmployeeId; var employee = (CurrentEmployee)context.Entry( @@ -1988,11 +2120,11 @@ private async Task GetDatabaseValues_for_the_wrong_type_in_the_store_returns_nul [ConditionalFact] public virtual Task NonGeneric_GetDatabaseValues_for_the_wrong_type_in_the_store_throws() => NonGeneric_GetDatabaseValues_for_the_wrong_type_in_the_store_throws_implementation( - e => Task.FromResult(e.GetDatabaseValues())); + e => Task.FromResult(e.GetDatabaseValues()!)); [ConditionalFact] public virtual Task NonGeneric_GetDatabaseValuesAsync_for_the_wrong_type_in_the_store_throws() - => NonGeneric_GetDatabaseValues_for_the_wrong_type_in_the_store_throws_implementation(e => e.GetDatabaseValuesAsync()); + => NonGeneric_GetDatabaseValues_for_the_wrong_type_in_the_store_throws_implementation(e => e.GetDatabaseValuesAsync()!); private async Task NonGeneric_GetDatabaseValues_for_the_wrong_type_in_the_store_throws_implementation( Func> getPropertyValues) @@ -2002,7 +2134,7 @@ private async Task NonGeneric_GetDatabaseValues_for_the_wrong_type_in_the_store_ .OfType() .AsNoTracking() .OrderBy(e => e.EmployeeId) - .FirstOrDefault() + .FirstOrDefault()! .EmployeeId; var employee = (CurrentEmployee)context.Entry( @@ -2021,11 +2153,11 @@ private async Task NonGeneric_GetDatabaseValues_for_the_wrong_type_in_the_store_ [ConditionalFact] public Task Store_values_really_are_store_values_not_current_or_original_values() => Store_values_really_are_store_values_not_current_or_original_values_implementation( - e => Task.FromResult(e.GetDatabaseValues())); + e => Task.FromResult(e.GetDatabaseValues()!)); [ConditionalFact] public Task Store_values_really_are_store_values_not_current_or_original_values_async() - => Store_values_really_are_store_values_not_current_or_original_values_implementation(e => e.GetDatabaseValuesAsync()); + => Store_values_really_are_store_values_not_current_or_original_values_implementation(e => e.GetDatabaseValuesAsync()!); private async Task Store_values_really_are_store_values_not_current_or_original_values_implementation( Func> getPropertyValues) @@ -2047,7 +2179,7 @@ public virtual void Setting_store_values_does_not_change_current_or_original_val using var context = CreateContext(); var building = context.Set().Single(b => b.Name == "Building One"); - var storeValues = context.Entry(building).GetDatabaseValues(); + var storeValues = context.Entry(building).GetDatabaseValues()!; storeValues["Name"] = "Bag End"; var currentValues = (Building)context.Entry(building).CurrentValues.ToObject(); @@ -2085,8 +2217,8 @@ protected abstract class Employee : UnMappedPersonBase protected class VirtualTeam : PropertyValuesBase { public int Id { get; set; } - public string TeamName { get; set; } - public ICollection Employees { get; set; } + public string? TeamName { get; set; } + public ICollection? Employees { get; set; } } protected class Building : PropertyValuesBase @@ -2095,24 +2227,64 @@ private Building() { } - public static Building Create(Guid buildingId, string name, decimal value) + public static Building Create(Guid buildingId, string name, decimal value, int? tag = null) => new() { BuildingId = buildingId, - Name = name, - Value = value + Name = name + tag, + Value = value + (tag ?? 0), + Culture = new Culture + { + License = new License + { + Charge = 1.0m + (tag ?? 0), + Tag = new Tag { Text = "Ta1" + tag }, + Title = "Ti1" + tag, + Tog = new Tog { Text = "To1" + tag } + }, + Manufacturer = new Manufacturer + { + Name = "M1" + tag, + Rating = 7 + (tag ?? 0), + Tag = new Tag { Text = "Ta2" + tag }, + Tog = new Tog { Text = "To2" + tag } + }, + Rating = 8 + (tag ?? 0), + Species = "S1" + tag, + Validation = false + }, + Milk = new Milk + { + License = new License + { + Charge = 1.0m + (tag ?? 0), + Tag = new Tag { Text = "Ta1" + tag }, + Title = "Ti1" + tag, + Tog = new Tog { Text = "To1" + tag } + }, + Manufacturer = new Manufacturer + { + Name = "M1" + tag, + Rating = 7 + (tag ?? 0), + Tag = new Tag { Text = "Ta2" + tag }, + Tog = new Tog { Text = "To2" + tag } + }, + Rating = 8 + (tag ?? 0), + Species = "S1" + tag, + Validation = false + } }; public Guid BuildingId { get; set; } - public string Name { get; set; } + public string? Name { get; set; } public decimal Value { get; set; } public virtual ICollection Offices { get; } = new List(); public virtual IList MailRooms { get; } = new List(); public int? PrincipalMailRoomId { get; set; } - public MailRoom PrincipalMailRoom { get; set; } + public MailRoom? PrincipalMailRoom { get; set; } - public string NotInModel { get; set; } + public string? NotInModel { get; set; } private string _noGetter = "NoGetter"; @@ -2126,17 +2298,66 @@ public string GetNoGetterValue() public string NoSetter => "NoSetter"; + + public Culture Culture { get; set; } + public required Milk Milk { get; set; } + } + + protected struct Culture + { + public string Species { get; set; } + public string? Subspecies { get; set; } + public int Rating { get; set; } + public bool? Validation { get; set; } + public Manufacturer Manufacturer { get; set; } + public License License { get; set; } + } + + protected class Milk + { + public string Species { get; set; } = null!; + public string? Subspecies { get; set; } + public int Rating { get; set; } + public bool? Validation { get; set; } + public Manufacturer Manufacturer { get; set; } = null!; + public License License { get; set; } + } + + protected class Manufacturer + { + public string? Name { get; set; } + public int Rating { get; set; } + public Tag Tag { get; set; } = null!; + public Tog Tog { get; set; } + } + + protected struct License + { + public string Title { get; set; } + public decimal Charge { get; set; } + public Tag Tag { get; set; } + public Tog Tog { get; set; } + } + + protected class Tag + { + public string? Text { get; set; } + } + + protected struct Tog + { + public string? Text { get; set; } } protected class BuildingDto { public Guid BuildingId { get; set; } - public string Name { get; set; } + public string? Name { get; set; } public decimal Value { get; set; } public int? PrincipalMailRoomId { get; set; } - public string NotInModel { get; set; } + public string? NotInModel { get; set; } private string _noGetter = "NoGetter"; @@ -2156,9 +2377,9 @@ public string NoSetter protected class BuildingDtoNoKey { - public string Name { get; set; } + public string? Name { get; set; } public decimal Value { get; set; } - public string Shadow2 { get; set; } + public string? Shadow2 { get; set; } } protected class MailRoom : PropertyValuesBase @@ -2166,51 +2387,51 @@ protected class MailRoom : PropertyValuesBase #pragma warning disable IDE1006 // Naming Styles public int id { get; set; } #pragma warning restore IDE1006 // Naming Styles - public Building Building { get; set; } + public Building? Building { get; set; } public Guid BuildingId { get; set; } } protected class Office : UnMappedOfficeBase { public Guid BuildingId { get; set; } - public Building Building { get; set; } + public Building? Building { get; set; } public IList WhiteBoards { get; } = new List(); } protected abstract class UnMappedOfficeBase : PropertyValuesBase { - public string Number { get; set; } - public string Description { get; set; } + public string? Number { get; set; } + public string? Description { get; set; } } protected class BuildingDetail : PropertyValuesBase { public Guid BuildingId { get; set; } - public Building Building { get; set; } - public string Details { get; set; } + public Building? Building { get; set; } + public string? Details { get; set; } } protected class WorkOrder : PropertyValuesBase { public int WorkOrderId { get; set; } public int EmployeeId { get; set; } - public Employee Employee { get; set; } - public string Details { get; set; } + public Employee? Employee { get; set; } + public string? Details { get; set; } } protected class Whiteboard : PropertyValuesBase { #pragma warning disable IDE1006 // Naming Styles - public byte[] iD { get; set; } + public byte[]? iD { get; set; } #pragma warning restore IDE1006 // Naming Styles - public string AssetTag { get; set; } - public Office Office { get; set; } + public string? AssetTag { get; set; } + public Office? Office { get; set; } } protected class UnMappedPersonBase : PropertyValuesBase { - public string FirstName { get; set; } - public string LastName { get; set; } + public string? FirstName { get; set; } + public string? LastName { get; set; } } protected class UnMappedOffice : Office @@ -2219,10 +2440,10 @@ protected class UnMappedOffice : Office protected class CurrentEmployee : Employee { - public CurrentEmployee Manager { get; set; } + public CurrentEmployee? Manager { get; set; } public decimal LeaveBalance { get; set; } - public Office Office { get; set; } - public ICollection VirtualTeams { get; set; } + public Office? Office { get; set; } + public ICollection? VirtualTeams { get; set; } } protected class PastEmployee : Employee @@ -2303,6 +2524,40 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con b.Ignore(e => e.NotInModel); b.Property("Shadow1"); b.Property("Shadow2"); + + b.ComplexProperty( + e => e.Culture, b => + { + b.ComplexProperty( + e => e.License, b => + { + b.ComplexProperty(e => e.Tag); + b.ComplexProperty(e => e.Tog); + }); + b.ComplexProperty( + e => e.Manufacturer, b => + { + b.ComplexProperty(e => e.Tag); + b.ComplexProperty(e => e.Tog); + }); + }); + + b.ComplexProperty( + e => e.Milk, b => + { + b.ComplexProperty( + e => e.License, b => + { + b.ComplexProperty(e => e.Tag); + b.ComplexProperty(e => e.Tog); + }); + b.ComplexProperty( + e => e.Manufacturer, b => + { + b.ComplexProperty(e => e.Tag); + b.ComplexProperty(e => e.Tog); + }); + }); }); }