Skip to content

Commit

Permalink
[release/8.0] Handle complex types in GetDatabaseValues (#32894)
Browse files Browse the repository at this point in the history
* Handle complex types in GetDatabaseValues (#32813)

Fixes #32701
Fixes #31353

* Fix merge and add quirks
  • Loading branch information
ajcvickers authored Feb 7, 2024
1 parent 3d7dda7 commit cb3b1b6
Show file tree
Hide file tree
Showing 7 changed files with 481 additions and 146 deletions.
7 changes: 5 additions & 2 deletions src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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]]);
}
}

Expand All @@ -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.
/// </summary>
public override IReadOnlyList<IProperty> Properties
=> _properties ??= EntityType.GetProperties().ToList();
=> _properties ??= EntityMaterializerSource.UseOldBehavior32701
? EntityType.GetProperties().ToList()
: EntityType.GetFlattenedProperties().ToList();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
8 changes: 6 additions & 2 deletions src/EFCore/ChangeTracking/Internal/EntryPropertyValues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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]);
}
}

Expand All @@ -107,7 +109,9 @@ public override void SetValues(PropertyValues propertyValues)
public override IReadOnlyList<IProperty> Properties
{
[DebuggerStepThrough]
get => _properties ??= EntityType.GetProperties().ToList();
get => _properties ??= EntityMaterializerSource.UseOldBehavior32701
? EntityType.GetProperties().ToList()
: EntityType.GetFlattenedProperties().ToList();
}

/// <summary>
Expand Down
55 changes: 46 additions & 9 deletions src/EFCore/Internal/EntityFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Query.Internal;

namespace Microsoft.EntityFrameworkCore.Internal;

Expand Down Expand Up @@ -859,17 +860,53 @@ private static Expression<Func<object, object[]>> BuildProjection(IEntityType en
var entityParameter = Expression.Parameter(typeof(object), "e");

var projections = new List<Expression>();
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<IPropertyBase> { 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<Func<object, object[]>>(
Expand Down
6 changes: 5 additions & 1 deletion src/EFCore/Metadata/Internal/EntityTypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// ReSharper disable ArgumentsStyleOther
// ReSharper disable ArgumentsStyleNamedExpression

using Microsoft.EntityFrameworkCore.Query.Internal;

namespace Microsoft.EntityFrameworkCore.Metadata.Internal;

/// <summary>
Expand Down Expand Up @@ -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()));
Expand Down
10 changes: 7 additions & 3 deletions src/EFCore/Query/Internal/EntityMaterializerSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))!;

Expand Down Expand Up @@ -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,
Expand Down
40 changes: 34 additions & 6 deletions test/EFCore.InMemory.FunctionalTests/PropertyValuesInMemoryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,47 @@

namespace Microsoft.EntityFrameworkCore;

public class PropertyValuesInMemoryTest : PropertyValuesTestBase<PropertyValuesInMemoryTest.PropertyValuesInMemoryFixture>
public class PropertyValuesInMemoryTest(PropertyValuesInMemoryTest.PropertyValuesInMemoryFixture fixture)
: PropertyValuesTestBase<PropertyValuesInMemoryTest.PropertyValuesInMemoryFixture>(fixture)
{
public PropertyValuesInMemoryTest(PropertyValuesInMemoryFixture fixture)
: base(fixture)
{
}
public override Task Complex_current_values_can_be_accessed_as_a_property_dictionary_using_IProperty()
=> Assert.ThrowsAsync<NullReferenceException>( // 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<NullReferenceException>( // 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<NullReferenceException>( // 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<NullReferenceException>( // 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<Building>(
b =>
{
b.Ignore(e => e.Culture);
b.Ignore(e => e.Milk);
});

}
}
}
Loading

0 comments on commit cb3b1b6

Please sign in to comment.