diff --git a/src/EFCore/EFCore.baseline.json b/src/EFCore/EFCore.baseline.json index 5741dadeed3..8ae8981dda9 100644 --- a/src/EFCore/EFCore.baseline.json +++ b/src/EFCore/EFCore.baseline.json @@ -3787,6 +3787,18 @@ { "Member": "static string ComplexCollectionWrongClrType(object? property, object? type, object? clrType, object? targetType);" }, + { + "Member": "static string ComplexPropertyChainIntermediateNotFound(object? member, object? type);" + }, + { + "Member": "static string ComplexPropertyChainInvalidMember(object? member, object? type);" + }, + { + "Member": "static string ComplexPropertyChainInvalidSegment(object? dottedName);" + }, + { + "Member": "static string ComplexPropertyChainOnCollection(object? member, object? type);" + }, { "Member": "static string ComplexPropertyIndexer(object? type, object? property);" }, @@ -4154,6 +4166,9 @@ { "Member": "static string InvalidKeyValue(object? entityType, object? keyProperty);" }, + { + "Member": "static string InvalidMemberAccessChainExpression(object? expression);" + }, { "Member": "static string InvalidMemberExpression(object? expression);" }, diff --git a/src/EFCore/Extensions/Internal/ExpressionExtensions.cs b/src/EFCore/Extensions/Internal/ExpressionExtensions.cs index 087fb8e7410..fb3344d3f33 100644 --- a/src/EFCore/Extensions/Internal/ExpressionExtensions.cs +++ b/src/EFCore/Extensions/Internal/ExpressionExtensions.cs @@ -121,7 +121,37 @@ var memberInfos return memberInfos?.Count == 1 ? memberInfos[0] : null; } - private static IReadOnlyList? MatchMemberAccess( + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static List? MatchMemberAccessChain( + this LambdaExpression lambdaExpression) + { + Check.DebugAssert( + lambdaExpression.Parameters.Count == 1, + $"Parameters.Count is {lambdaExpression.Parameters.Count}"); + + return MatchMemberAccess(lambdaExpression.Parameters[0], lambdaExpression.Body); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static List GetMemberAccessChain( + this LambdaExpression expression, + string parameterName) + => expression.MatchMemberAccessChain() + ?? throw new ArgumentException( + CoreStrings.InvalidMemberAccessChainExpression(expression), + parameterName); + + private static List? MatchMemberAccess( this Expression parameterExpression, Expression memberAccessExpression) where TMemberInfo : MemberInfo @@ -132,8 +162,7 @@ var memberInfos do { var memberExpression = unwrappedExpression as MemberExpression; - - if (!(memberExpression?.Member is TMemberInfo memberInfo)) + if (memberExpression?.Member is not TMemberInfo memberInfo) { return null; } diff --git a/src/EFCore/Infrastructure/ExpressionExtensions.cs b/src/EFCore/Infrastructure/ExpressionExtensions.cs index 3eed5f195a2..948819e1c37 100644 --- a/src/EFCore/Infrastructure/ExpressionExtensions.cs +++ b/src/EFCore/Infrastructure/ExpressionExtensions.cs @@ -176,29 +176,7 @@ private static TMemberInfo GetInternalMemberAccess(this LambdaExpre nameof(memberAccessExpression)); } - var declaringType = memberInfo.DeclaringType; - var parameterType = parameterExpression.Type; - - if (declaringType != null - && declaringType != parameterType - && declaringType.IsInterface - && declaringType.IsAssignableFrom(parameterType) - && memberInfo is PropertyInfo propertyInfo) - { - var propertyGetter = propertyInfo.GetMethod; - var interfaceMapping = parameterType.GetTypeInfo().GetRuntimeInterfaceMap(declaringType); - var index = Array.FindIndex(interfaceMapping.InterfaceMethods, p => p.Equals(propertyGetter)); - var targetMethod = interfaceMapping.TargetMethods[index]; - foreach (var runtimeProperty in parameterType.GetRuntimeProperties()) - { - if (targetMethod.Equals(runtimeProperty.GetMethod)) - { - return (TMemberInfo)(object)runtimeProperty; - } - } - } - - return memberInfo; + return (TMemberInfo)memberInfo.ResolveMemberForType(parameterExpression.Type); } /// diff --git a/src/EFCore/Metadata/Builders/ComplexCollectionBuilder.cs b/src/EFCore/Metadata/Builders/ComplexCollectionBuilder.cs index 8fa60e248dc..cc7e8f2ee12 100644 --- a/src/EFCore/Metadata/Builders/ComplexCollectionBuilder.cs +++ b/src/EFCore/Metadata/Builders/ComplexCollectionBuilder.cs @@ -119,10 +119,12 @@ public virtual ComplexCollectionBuilder IsRequired(bool required = true) /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexCollectionTypePropertyBuilder Property(string propertyName) - => new( - TypeBuilder.Property( - Check.NotEmpty(propertyName), - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.Property(leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the complex type. @@ -139,10 +141,12 @@ public virtual ComplexCollectionTypePropertyBuilder Property(string propertyName /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexCollectionTypePropertyBuilder Property(string propertyName) - => new( - TypeBuilder.Property( - typeof(TProperty), - Check.NotEmpty(propertyName), ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.Property(typeof(TProperty), leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the complex type. @@ -159,10 +163,13 @@ public virtual ComplexCollectionTypePropertyBuilder PropertyThe name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexCollectionTypePropertyBuilder Property(Type propertyType, string propertyName) - => new( - TypeBuilder.Property( - Check.NotNull(propertyType), - Check.NotEmpty(propertyName), ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyType); + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.Property(propertyType, leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the complex type where that property represents @@ -177,10 +184,12 @@ public virtual ComplexCollectionTypePropertyBuilder Property(Type propertyType, /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string propertyName) - => new( - TypeBuilder.PrimitiveCollection( - Check.NotEmpty(propertyName), - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.PrimitiveCollection(leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the complex type where that property represents @@ -198,10 +207,12 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string propertyName) - => new( - TypeBuilder.PrimitiveCollection( - typeof(TProperty), - Check.NotEmpty(propertyName), ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.PrimitiveCollection(typeof(TProperty), leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the complex type where that property represents @@ -219,10 +230,13 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollect /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(Type propertyType, string propertyName) - => new( - TypeBuilder.PrimitiveCollection( - Check.NotNull(propertyType), - Check.NotEmpty(propertyName), ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyType); + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.PrimitiveCollection(propertyType, leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the complex type. @@ -279,13 +293,13 @@ public virtual ComplexCollectionTypePropertyBuilder IndexerProperty( /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) - => new( - TypeBuilder.ComplexProperty( - propertyType: null, - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: false, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + propertyType: null, leafName, complexTypeName: null, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex property of the complex type. @@ -303,13 +317,13 @@ public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) /// An object that can be used to configure the property. public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) where TProperty : notnull - => new( - TypeBuilder.ComplexProperty( - typeof(TProperty), - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: false, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + typeof(TProperty), leafName, complexTypeName: null, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex property of the complex type. @@ -351,13 +365,14 @@ public virtual ComplexPropertyBuilder ComplexProperty(stri /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexPropertyBuilder ComplexProperty(Type propertyType, string propertyName) - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyType), - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: false, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyType); + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + propertyType, leafName, complexTypeName: null, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex property of the complex type. @@ -528,13 +543,13 @@ public virtual ComplexCollectionBuilder ComplexProperty( /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexCollectionBuilder ComplexCollection(string propertyName) - => new( - TypeBuilder.ComplexProperty( - propertyType: null, - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + propertyType: null, leafName, complexTypeName: null, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex collection of the complex type. @@ -554,13 +569,13 @@ public virtual ComplexCollectionBuilder ComplexCollection(string propertyName) public virtual ComplexCollectionBuilder ComplexCollection(string propertyName) where TProperty : IEnumerable where TElement : notnull - => new( - TypeBuilder.ComplexProperty( - typeof(TProperty), - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + typeof(TProperty), leafName, complexTypeName: null, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex collection of the complex type. @@ -604,13 +619,14 @@ public virtual ComplexCollectionBuilder ComplexCollectionThe name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexCollectionBuilder ComplexCollection(Type propertyType, string propertyName) - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyType), - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyType); + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + propertyType, leafName, complexTypeName: null, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex collection of the complex type. @@ -782,7 +798,8 @@ public virtual ComplexCollectionBuilder Ignore(string propertyName) { Check.NotEmpty(propertyName); - TypeBuilder.Ignore(propertyName, ConfigurationSource.Explicit); + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + innerBuilder.Ignore(leafName, ConfigurationSource.Explicit); return this; } diff --git a/src/EFCore/Metadata/Builders/ComplexCollectionBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexCollectionBuilder`.cs index 71cf58a58ad..54211d2a78e 100644 --- a/src/EFCore/Metadata/Builders/ComplexCollectionBuilder`.cs +++ b/src/EFCore/Metadata/Builders/ComplexCollectionBuilder`.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. +using Microsoft.EntityFrameworkCore.Internal; + namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// @@ -71,10 +73,13 @@ public ComplexCollectionBuilder(IMutableComplexProperty complexProperty) /// An object that can be used to configure the property. public virtual ComplexCollectionTypePropertyBuilder Property( Expression> propertyExpression) - => new( - TypeBuilder.Property( - Check.NotNull(propertyExpression).GetMemberAccess(), ConfigurationSource.Explicit)! - .Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.Property(leafMember, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the complex type where that property represents @@ -88,10 +93,13 @@ public virtual ComplexCollectionTypePropertyBuilder PropertyAn object that can be used to configure the property. public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection( Expression> propertyExpression) - => new( - TypeBuilder.PrimitiveCollection( - Check.NotNull(propertyExpression).GetMemberAccess(), ConfigurationSource.Explicit)! - .Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.PrimitiveCollection(leafMember, ConfigurationSource.Explicit)!.Metadata); + } /// /// Configures a complex property of the complex type. @@ -216,12 +224,14 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollect public virtual ComplexPropertyBuilder ComplexProperty( Expression> propertyExpression) where TProperty : notnull - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - complexTypeName: null, - collection: false, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName: null, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex property of the complex type. @@ -245,12 +255,15 @@ public virtual ComplexPropertyBuilder ComplexProperty( Expression> propertyExpression, string complexTypeName) where TProperty : notnull - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - Check.NotEmpty(complexTypeName), - collection: false, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + Check.NotEmpty(complexTypeName); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Configures a complex property of the complex type. @@ -334,12 +347,14 @@ public virtual ComplexCollectionBuilder ComplexProperty( public virtual ComplexPropertyBuilder ComplexProperty( Expression> propertyExpression) where TProperty : struct - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - complexTypeName: null, - collection: false, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName: null, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex property of the complex type. @@ -363,12 +378,15 @@ public virtual ComplexPropertyBuilder ComplexProperty( Expression> propertyExpression, string complexTypeName) where TProperty : struct - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - Check.NotEmpty(complexTypeName), - collection: false, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + Check.NotEmpty(complexTypeName); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Configures a complex property of the complex type. @@ -554,12 +572,14 @@ public virtual ComplexCollectionBuilder ComplexProperty( public virtual ComplexCollectionBuilder ComplexCollection( Expression?>> propertyExpression) where TElement : notnull - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - complexTypeName: null, - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName: null, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex collection property of the complex type. @@ -576,12 +596,15 @@ public virtual ComplexCollectionBuilder ComplexCollection( Expression?>> propertyExpression, string complexTypeName) where TElement : notnull - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - Check.NotEmpty(complexTypeName), - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + Check.NotEmpty(complexTypeName); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Configures a complex collection property of the complex type. @@ -644,12 +667,14 @@ public virtual ComplexCollectionBuilder ComplexCollection( public virtual ComplexCollectionBuilder ComplexCollection( Expression?>> propertyExpression) where TElement : struct - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - complexTypeName: null, - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName: null, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex collection property of the complex type. @@ -666,12 +691,15 @@ public virtual ComplexCollectionBuilder ComplexCollection( Expression?>> propertyExpression, string complexTypeName) where TElement : struct - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - Check.NotEmpty(complexTypeName), - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + Check.NotEmpty(complexTypeName); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Configures a complex collection property of the complex type. @@ -730,8 +758,14 @@ public virtual ComplexCollectionBuilder ComplexCollection( /// (blog => blog.Url). /// public virtual ComplexCollectionBuilder Ignore(Expression> propertyExpression) - => (ComplexCollectionBuilder)base.Ignore( - Check.NotNull(propertyExpression).GetMemberAccess().GetSimpleMemberName()); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + innerBuilder.Ignore(leafMember.GetSimpleMemberName(), ConfigurationSource.Explicit); + return this; + } /// /// Excludes the given property from the entity type. This method is typically used to remove properties diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs index ab082ed3c08..55483368e87 100644 --- a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs @@ -157,10 +157,12 @@ public virtual ComplexPropertyBuilder HasField(string fieldName) /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexTypePropertyBuilder Property(string propertyName) - => new( - TypeBuilder.Property( - Check.NotEmpty(propertyName), - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.Property(leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the complex type. @@ -177,10 +179,12 @@ public virtual ComplexTypePropertyBuilder Property(string propertyName) /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexTypePropertyBuilder Property(string propertyName) - => new( - TypeBuilder.Property( - typeof(TProperty), - Check.NotEmpty(propertyName), ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.Property(typeof(TProperty), leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the complex type. @@ -197,10 +201,13 @@ public virtual ComplexTypePropertyBuilder Property(string /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexTypePropertyBuilder Property(Type propertyType, string propertyName) - => new( - TypeBuilder.Property( - Check.NotNull(propertyType), - Check.NotEmpty(propertyName), ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyType); + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.Property(propertyType, leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the complex type where that property represents @@ -215,10 +222,12 @@ public virtual ComplexTypePropertyBuilder Property(Type propertyType, string pro /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string propertyName) - => new( - TypeBuilder.PrimitiveCollection( - Check.NotEmpty(propertyName), - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.PrimitiveCollection(leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the complex type where that property represents @@ -236,10 +245,12 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string propertyName) - => new( - TypeBuilder.PrimitiveCollection( - typeof(TProperty), - Check.NotEmpty(propertyName), ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.PrimitiveCollection(typeof(TProperty), leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the complex type where that property represents @@ -257,10 +268,13 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollect /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(Type propertyType, string propertyName) - => new( - TypeBuilder.PrimitiveCollection( - Check.NotNull(propertyType), - Check.NotEmpty(propertyName), ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyType); + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.PrimitiveCollection(propertyType, leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the complex type. @@ -317,13 +331,13 @@ public virtual ComplexTypePropertyBuilder IndexerProperty( /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) - => new( - TypeBuilder.ComplexProperty( - propertyType: null, - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: false, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + propertyType: null, leafName, complexTypeName: null, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex property of the complex type. @@ -341,13 +355,13 @@ public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) /// An object that can be used to configure the property. public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) where TProperty : notnull - => new( - TypeBuilder.ComplexProperty( - typeof(TProperty), - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: false, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + typeof(TProperty), leafName, complexTypeName: null, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex property of the complex type. @@ -389,13 +403,14 @@ public virtual ComplexPropertyBuilder ComplexProperty(stri /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexPropertyBuilder ComplexProperty(Type propertyType, string propertyName) - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyType), - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: false, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyType); + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + propertyType, leafName, complexTypeName: null, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex property of the complex type. @@ -566,13 +581,13 @@ public virtual ComplexPropertyBuilder ComplexProperty( /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexCollectionBuilder ComplexCollection(string propertyName) - => new( - TypeBuilder.ComplexProperty( - propertyType: null, - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + propertyType: null, leafName, complexTypeName: null, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex collection of the complex type. @@ -592,13 +607,13 @@ public virtual ComplexCollectionBuilder ComplexCollection(string propertyName) public virtual ComplexCollectionBuilder ComplexCollection(string propertyName) where TProperty : IEnumerable where TElement : notnull - => new( - TypeBuilder.ComplexProperty( - typeof(TProperty), - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + typeof(TProperty), leafName, complexTypeName: null, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex collection of the complex type. @@ -642,13 +657,14 @@ public virtual ComplexCollectionBuilder ComplexCollectionThe name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexCollectionBuilder ComplexCollection(Type propertyType, string propertyName) - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyType), - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyType); + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + propertyType, leafName, complexTypeName: null, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex collection of the complex type. @@ -820,7 +836,8 @@ public virtual ComplexPropertyBuilder Ignore(string propertyName) { Check.NotEmpty(propertyName); - TypeBuilder.Ignore(propertyName, ConfigurationSource.Explicit); + var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); + innerBuilder.Ignore(leafName, ConfigurationSource.Explicit); return this; } diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs index 733198f0262..f5eae964eff 100644 --- a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs +++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.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. +using Microsoft.EntityFrameworkCore.Internal; + namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// @@ -94,10 +96,13 @@ public ComplexPropertyBuilder(IMutableComplexProperty complexProperty) /// /// An object that can be used to configure the property. public virtual ComplexTypePropertyBuilder Property(Expression> propertyExpression) - => new( - TypeBuilder.Property( - Check.NotNull(propertyExpression).GetMemberAccess(), ConfigurationSource.Explicit)! - .Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.Property(leafMember, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the complex type where that property represents @@ -111,10 +116,13 @@ public virtual ComplexTypePropertyBuilder Property(Express /// An object that can be used to configure the property. public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection( Expression> propertyExpression) - => new( - TypeBuilder.PrimitiveCollection( - Check.NotNull(propertyExpression).GetMemberAccess(), ConfigurationSource.Explicit)! - .Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.PrimitiveCollection(leafMember, ConfigurationSource.Explicit)!.Metadata); + } /// /// Configures a complex property of the complex type. @@ -239,12 +247,14 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollect public virtual ComplexPropertyBuilder ComplexProperty( Expression> propertyExpression) where TProperty : notnull - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - complexTypeName: null, - collection: false, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName: null, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex property of the complex type. @@ -268,12 +278,15 @@ public virtual ComplexPropertyBuilder ComplexProperty( Expression> propertyExpression, string complexTypeName) where TProperty : notnull - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - Check.NotEmpty(complexTypeName), - collection: false, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + Check.NotEmpty(complexTypeName); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex property of the complex type. @@ -357,12 +370,14 @@ public virtual ComplexPropertyBuilder ComplexProperty( public virtual ComplexPropertyBuilder ComplexProperty( Expression> propertyExpression) where TProperty : struct - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - complexTypeName: null, - collection: false, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName: null, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex property of the complex type. @@ -386,12 +401,15 @@ public virtual ComplexPropertyBuilder ComplexProperty( Expression> propertyExpression, string complexTypeName) where TProperty : struct - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - Check.NotEmpty(complexTypeName), - collection: false, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + Check.NotEmpty(complexTypeName); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Configures a complex property of the complex type. @@ -575,12 +593,14 @@ public virtual ComplexPropertyBuilder ComplexProperty( public virtual ComplexCollectionBuilder ComplexCollection( Expression?>> propertyExpression) where TElement : notnull - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - complexTypeName: null, - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName: null, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex collection property of the complex type. @@ -597,12 +617,15 @@ public virtual ComplexCollectionBuilder ComplexCollection( Expression?>> propertyExpression, string complexTypeName) where TElement : notnull - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - Check.NotEmpty(complexTypeName), - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + Check.NotEmpty(complexTypeName); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Configures a complex collection property of the complex type. @@ -665,12 +688,14 @@ public virtual ComplexPropertyBuilder ComplexCollection( public virtual ComplexCollectionBuilder ComplexCollection( Expression?>> propertyExpression) where TElement : struct - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - complexTypeName: null, - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName: null, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex collection property of the complex type. @@ -687,12 +712,15 @@ public virtual ComplexCollectionBuilder ComplexCollection( Expression?>> propertyExpression, string complexTypeName) where TElement : struct - => new( - TypeBuilder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - Check.NotEmpty(complexTypeName), - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + Check.NotEmpty(complexTypeName); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Configures a complex collection property of the complex type. @@ -751,8 +779,14 @@ public virtual ComplexPropertyBuilder ComplexCollection( /// (blog => blog.Url). /// public virtual ComplexPropertyBuilder Ignore(Expression> propertyExpression) - => (ComplexPropertyBuilder)base.Ignore( - Check.NotNull(propertyExpression).GetMemberAccess().GetSimpleMemberName()); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); + innerBuilder.Ignore(leafMember.GetSimpleMemberName(), ConfigurationSource.Explicit); + return this; + } /// /// Excludes the given property from the complex type. This method is typically used to remove properties diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs index 16d89c0f017..ae87c28bcb9 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs @@ -121,10 +121,12 @@ public virtual EntityTypeBuilder HasNoKey() /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual PropertyBuilder Property(string propertyName) - => new( - Builder.Property( - Check.NotEmpty(propertyName), - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = Builder.ResolveComplexChainByName(propertyName); + return new PropertyBuilder(innerBuilder.Property(leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the entity type. @@ -141,10 +143,12 @@ public virtual PropertyBuilder Property(string propertyName) /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual PropertyBuilder Property(string propertyName) - => new( - Builder.Property( - typeof(TProperty), - Check.NotEmpty(propertyName), ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = Builder.ResolveComplexChainByName(propertyName); + return new PropertyBuilder(innerBuilder.Property(typeof(TProperty), leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the entity type. @@ -161,10 +165,13 @@ public virtual PropertyBuilder Property(string propertyNam /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual PropertyBuilder Property(Type propertyType, string propertyName) - => new( - Builder.Property( - Check.NotNull(propertyType), - Check.NotEmpty(propertyName), ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyType); + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = Builder.ResolveComplexChainByName(propertyName); + return new PropertyBuilder(innerBuilder.Property(propertyType, leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the entity type where that property represents @@ -179,9 +186,12 @@ public virtual PropertyBuilder Property(Type propertyType, string propertyName) /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual PrimitiveCollectionBuilder PrimitiveCollection(string propertyName) - => new( - Builder.PrimitiveCollection( - Check.NotEmpty(propertyName), ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = Builder.ResolveComplexChainByName(propertyName); + return new PrimitiveCollectionBuilder(innerBuilder.PrimitiveCollection(leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the entity type where that property represents @@ -199,10 +209,12 @@ public virtual PrimitiveCollectionBuilder PrimitiveCollection(string propertyNam /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual PrimitiveCollectionBuilder PrimitiveCollection(string propertyName) - => new( - Builder.PrimitiveCollection( - typeof(TProperty), - Check.NotEmpty(propertyName), ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = Builder.ResolveComplexChainByName(propertyName); + return new PrimitiveCollectionBuilder(innerBuilder.PrimitiveCollection(typeof(TProperty), leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the entity type where that property represents @@ -220,10 +232,13 @@ public virtual PrimitiveCollectionBuilder PrimitiveCollectionThe name of the property to be configured. /// An object that can be used to configure the property. public virtual PrimitiveCollectionBuilder PrimitiveCollection(Type propertyType, string propertyName) - => new( - Builder.PrimitiveCollection( - Check.NotNull(propertyType), - Check.NotEmpty(propertyName), ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyType); + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = Builder.ResolveComplexChainByName(propertyName); + return new PrimitiveCollectionBuilder(innerBuilder.PrimitiveCollection(propertyType, leafName, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the entity type. @@ -280,13 +295,13 @@ public virtual PropertyBuilder IndexerProperty( /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) - => new( - Builder.ComplexProperty( - propertyType: null, - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: false, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = Builder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + propertyType: null, leafName, complexTypeName: null, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex property of the entity type. @@ -304,13 +319,13 @@ public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) /// An object that can be used to configure the property. public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) where TProperty : notnull - => new( - Builder.ComplexProperty( - typeof(TProperty), - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: false, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = Builder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + typeof(TProperty), leafName, complexTypeName: null, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex property of the entity type. @@ -354,13 +369,14 @@ public virtual ComplexPropertyBuilder ComplexProperty( /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexPropertyBuilder ComplexProperty(Type propertyType, string propertyName) - => new( - Builder.ComplexProperty( - Check.NotNull(propertyType), - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: false, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyType); + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = Builder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + propertyType, leafName, complexTypeName: null, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex property of the entity type. @@ -531,13 +547,13 @@ public virtual EntityTypeBuilder ComplexProperty( /// The name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexCollectionBuilder ComplexCollection(string propertyName) - => new( - Builder.ComplexProperty( - propertyType: null, - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = Builder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + propertyType: null, leafName, complexTypeName: null, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex collection of the entity type. @@ -557,13 +573,13 @@ public virtual ComplexCollectionBuilder ComplexCollection(string propertyName) public virtual ComplexCollectionBuilder ComplexCollection(string propertyName) where TProperty : IEnumerable where TElement : notnull - => new( - Builder.ComplexProperty( - typeof(TProperty), - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = Builder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + typeof(TProperty), leafName, complexTypeName: null, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex collection of the entity type. @@ -607,13 +623,14 @@ public virtual ComplexCollectionBuilder ComplexCollectionThe name of the property to be configured. /// An object that can be used to configure the property. public virtual ComplexCollectionBuilder ComplexCollection(Type propertyType, string propertyName) - => new( - Builder.ComplexProperty( - Check.NotNull(propertyType), - Check.NotEmpty(propertyName), - complexTypeName: null, - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyType); + Check.NotEmpty(propertyName); + + var (innerBuilder, leafName) = Builder.ResolveComplexChainByName(propertyName); + return new(innerBuilder.ComplexProperty( + propertyType, leafName, complexTypeName: null, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex collection of the entity type. @@ -795,7 +812,8 @@ public virtual EntityTypeBuilder Ignore(string propertyName) { Check.NotEmpty(propertyName); - Builder.Ignore(propertyName, ConfigurationSource.Explicit); + var (innerBuilder, leafName) = Builder.ResolveComplexChainByName(propertyName); + innerBuilder.Ignore(leafName, ConfigurationSource.Explicit); return this; } diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs index 22c37b8bf82..d6e27881858 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -146,10 +147,13 @@ public virtual KeyBuilder HasAlternateKey(Expression /// An object that can be used to configure the property. public virtual PropertyBuilder Property(Expression> propertyExpression) - => new( - Builder.Property( - Check.NotNull(propertyExpression).GetMemberAccess(), ConfigurationSource.Explicit)! - .Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = Builder.ResolveComplexChain(memberChain); + return new(innerBuilder.Property(leafMember, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a property of the entity type where that property represents @@ -162,10 +166,13 @@ public virtual PropertyBuilder Property(ExpressionAn object that can be used to configure the property. public virtual PrimitiveCollectionBuilder PrimitiveCollection( Expression> propertyExpression) - => new( - Builder.PrimitiveCollection( - Check.NotNull(propertyExpression).GetMemberAccess(), - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = Builder.ResolveComplexChain(memberChain); + return new(innerBuilder.PrimitiveCollection(leafMember, ConfigurationSource.Explicit)!.Metadata); + } /// /// Configures a complex property of the entity type. @@ -282,13 +289,14 @@ public virtual PrimitiveCollectionBuilder PrimitiveCollection ComplexProperty( Expression> propertyExpression) where TProperty : notnull - => new( - Builder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - complexTypeName: null, - collection: false, - ConfigurationSource.Explicit)! - .Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = Builder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName: null, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex property of the entity type. @@ -304,13 +312,15 @@ public virtual ComplexPropertyBuilder ComplexProperty( Expression> propertyExpression, string complexTypeName) where TProperty : notnull - => new( - Builder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - Check.NotEmpty(complexTypeName), - collection: false, - ConfigurationSource.Explicit)! - .Metadata); + { + Check.NotNull(propertyExpression); + Check.NotEmpty(complexTypeName); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = Builder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Configures a complex property of the entity type. @@ -370,13 +380,14 @@ public virtual EntityTypeBuilder ComplexProperty( public virtual ComplexPropertyBuilder ComplexProperty( Expression> propertyExpression) where TProperty : struct - => new( - Builder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - complexTypeName: null, - collection: false, - ConfigurationSource.Explicit)! - .Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = Builder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName: null, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex property of the entity type. @@ -392,13 +403,15 @@ public virtual ComplexPropertyBuilder ComplexProperty( Expression> propertyExpression, string complexTypeName) where TProperty : struct - => new( - Builder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - Check.NotEmpty(complexTypeName), - collection: false, - ConfigurationSource.Explicit)! - .Metadata); + { + Check.NotNull(propertyExpression); + Check.NotEmpty(complexTypeName); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = Builder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName, collection: false, ConfigurationSource.Explicit)!.Metadata); + } /// /// Configures a complex property of the entity type. @@ -566,12 +579,14 @@ public virtual EntityTypeBuilder ComplexProperty( public virtual ComplexCollectionBuilder ComplexCollection( Expression?>> propertyExpression) where TElement : notnull - => new( - Builder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - complexTypeName: null, - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = Builder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName: null, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex collection property of the entity type. @@ -588,12 +603,15 @@ public virtual ComplexCollectionBuilder ComplexCollection( Expression?>> propertyExpression, string complexTypeName) where TElement : notnull - => new( - Builder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - Check.NotEmpty(complexTypeName), - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + Check.NotEmpty(complexTypeName); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = Builder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Configures a complex collection property of the entity type. @@ -656,12 +674,14 @@ public virtual EntityTypeBuilder ComplexCollection( public virtual ComplexCollectionBuilder ComplexCollection( Expression?>> propertyExpression) where TElement : struct - => new( - Builder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - complexTypeName: null, - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = Builder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName: null, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Returns an object that can be used to configure a complex collection property of the entity type. @@ -678,12 +698,15 @@ public virtual ComplexCollectionBuilder ComplexCollection( Expression?>> propertyExpression, string complexTypeName) where TElement : struct - => new( - Builder.ComplexProperty( - Check.NotNull(propertyExpression).GetMemberAccess(), - Check.NotEmpty(complexTypeName), - collection: true, - ConfigurationSource.Explicit)!.Metadata); + { + Check.NotNull(propertyExpression); + Check.NotEmpty(complexTypeName); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = Builder.ResolveComplexChain(memberChain); + return new(innerBuilder.ComplexProperty( + leafMember, complexTypeName, collection: true, ConfigurationSource.Explicit)!.Metadata); + } /// /// Configures a complex collection property of the entity type. @@ -776,8 +799,14 @@ public virtual NavigationBuilder Navigation( /// (blog => blog.Url). /// public virtual EntityTypeBuilder Ignore(Expression> propertyExpression) - => (EntityTypeBuilder)base.Ignore( - Check.NotNull(propertyExpression).GetMemberAccess().GetSimpleMemberName()); + { + Check.NotNull(propertyExpression); + + var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); + var (innerBuilder, leafMember) = Builder.ResolveComplexChain(memberChain); + innerBuilder.Ignore(leafMember.GetSimpleMemberName(), ConfigurationSource.Explicit); + return this; + } /// /// Excludes the given property from the entity type. This method is typically used to remove properties diff --git a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs index 0e13bcac7bc..a25af2b88c6 100644 --- a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs @@ -1555,6 +1555,112 @@ protected virtual void RemoveIncompatibleDiscriminatorValues( } } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual (InternalTypeBaseBuilder Builder, MemberInfo FinalMember) ResolveComplexChain( + IReadOnlyList memberChain) + { + if (memberChain.Count == 1) + { + return (this, memberChain[0].ResolveMemberForType(Metadata.ClrType)); + } + + var builder = this; + for (var i = 0; i < memberChain.Count - 1; i++) + { + var member = memberChain[i].ResolveMemberForType(builder.Metadata.ClrType); + var memberName = member.GetSimpleMemberName(); + var metadata = builder.Metadata; + + var conflictingMember = metadata.FindMembersInHierarchy(memberName).FirstOrDefault(); + builder = conflictingMember switch + { + ComplexProperty { IsCollection: true } => + throw new InvalidOperationException( + CoreStrings.ComplexPropertyChainOnCollection(memberName, ((IReadOnlyTypeBase)metadata).DisplayName())), + not null and not Internal.ComplexProperty when conflictingMember.GetConfigurationSource() == ConfigurationSource.Explicit => + throw new InvalidOperationException( + CoreStrings.ComplexPropertyChainInvalidMember(memberName, ((IReadOnlyTypeBase)metadata).DisplayName())), + _ => builder.ComplexProperty( + member, + complexTypeName: null, + collection: false, + ConfigurationSource.Explicit)!.Metadata.ComplexType.Builder + }; + } + + return (builder, memberChain[^1].ResolveMemberForType(builder.Metadata.ClrType)); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual (InternalTypeBaseBuilder Builder, string FinalName) ResolveComplexChainByName(string dottedName) + { + var segments = dottedName.Split('.'); + if (segments.Length == 1) + { + return (this, dottedName); + } + + // Validate no empty segments (including the final one, e.g. "Address." or "Address..Street") + foreach (var segment in segments) + { + if (string.IsNullOrEmpty(segment)) + { + throw new InvalidOperationException( + CoreStrings.ComplexPropertyChainInvalidSegment(dottedName)); + } + } + + var builder = this; + for (var i = 0; i < segments.Length - 1; i++) + { + var segment = segments[i]; + var metadata = builder.Metadata; + + var conflictingMember = metadata.FindMembersInHierarchy(segment).FirstOrDefault(); + switch (conflictingMember) + { + case ComplexProperty { IsCollection: true }: + throw new InvalidOperationException( + CoreStrings.ComplexPropertyChainOnCollection(segment, ((IReadOnlyTypeBase)metadata).DisplayName())); + case not null and not Internal.ComplexProperty when conflictingMember.GetConfigurationSource() == ConfigurationSource.Explicit: + throw new InvalidOperationException( + CoreStrings.ComplexPropertyChainInvalidMember(segment, ((IReadOnlyTypeBase)metadata).DisplayName())); + } + + var memberInfo = metadata.ClrType.GetMembersInHierarchy(segment).FirstOrDefault(); + if (conflictingMember is ComplexProperty complexProperty) + { + complexProperty.UpdateConfigurationSource(ConfigurationSource.Explicit); + + builder = complexProperty.ComplexType.Builder; + continue; + } + else if (memberInfo == null) + { + throw new InvalidOperationException( + CoreStrings.ComplexPropertyChainIntermediateNotFound(segment, ((IReadOnlyTypeBase)metadata).DisplayName())); + } + + var complexBuilder = builder.ComplexProperty( + propertyType: null, segment, memberInfo, complexTypeName: null, + complexType: null, collection: false, ConfigurationSource.Explicit); + + builder = complexBuilder!.Metadata.ComplexType.Builder; + } + + return (builder, segments[^1]); + } + IConventionTypeBase IConventionTypeBaseBuilder.Metadata { [DebuggerStepThrough] diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index cc2c2446906..3113e0a7063 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -686,6 +686,38 @@ public static string ComplexCollectionWrongClrType(object? property, object? typ GetString("ComplexCollectionWrongClrType", nameof(property), nameof(type), nameof(clrType), nameof(targetType)), property, type, clrType, targetType); + /// + /// The intermediate complex property '{member}' was not found on type '{type}'. When using dotted property names, all intermediate segments must refer to complex properties. + /// + public static string ComplexPropertyChainIntermediateNotFound(object? member, object? type) + => string.Format( + GetString("ComplexPropertyChainIntermediateNotFound", nameof(member), nameof(type)), + member, type); + + /// + /// The member '{member}' on type '{type}' is configured as a non-complex property and cannot be used as an intermediate in a chained property access. Configure it as a complex property first if that's the intention. + /// + public static string ComplexPropertyChainInvalidMember(object? member, object? type) + => string.Format( + GetString("ComplexPropertyChainInvalidMember", nameof(member), nameof(type)), + member, type); + + /// + /// The dotted property name '{dottedName}' contains an empty segment. Dotted property names must consist of non-empty segments separated by '.'. + /// + public static string ComplexPropertyChainInvalidSegment(object? dottedName) + => string.Format( + GetString("ComplexPropertyChainInvalidSegment", nameof(dottedName)), + dottedName); + + /// + /// The member '{member}' on type '{type}' is a complex collection property and cannot be traversed in a chained property access. Configure properties on complex collection element types using the 'ComplexCollection' method. + /// + public static string ComplexPropertyChainOnCollection(object? member, object? type) + => string.Format( + GetString("ComplexPropertyChainOnCollection", nameof(member), nameof(type)), + member, type); + /// /// Adding the complex property '{type}.{property}' as an indexer property isn't supported. See https://github.com/dotnet/efcore/issues/31244 for more information. /// @@ -1765,6 +1797,14 @@ public static string InvalidKeyValue(object? entityType, object? keyProperty) GetString("InvalidKeyValue", nameof(entityType), nameof(keyProperty)), entityType, keyProperty); + /// + /// The expression '{expression}' is not a valid member access expression. The expression should represent a simple property or field access: 't => t.MyProperty' or a chain of member accesses through non-collection complex properties: 't => t.MyComplex.MyProperty'. + /// + public static string InvalidMemberAccessChainExpression(object? expression) + => string.Format( + GetString("InvalidMemberAccessChainExpression", nameof(expression)), + expression); + /// /// The expression '{expression}' is not a valid member access expression. The expression should represent a simple property or field access: 't => t.MyProperty'. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 903879229d3..e68c7ce76bc 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -366,6 +366,18 @@ The collection complex property '{property}' cannot be added to the type '{type}' because its CLR type '{clrType}' does not implement 'IEnumerable<{targetType}>'. Collection complex property must implement IEnumerable<> of the complex type. + + The intermediate complex property '{member}' was not found on type '{type}'. When using dotted property names, all intermediate segments must refer to complex properties. + + + The member '{member}' on type '{type}' is configured as a non-complex property and cannot be used as an intermediate in a chained property access. Configure it as a complex property first if that's the intention. + + + The dotted property name '{dottedName}' contains an empty segment. Dotted property names must consist of non-empty segments separated by '.'. + + + The member '{member}' on type '{type}' is a complex collection property and cannot be traversed in a chained property access. Configure properties on complex collection element types using the 'ComplexCollection' method. + Adding the complex property '{type}.{property}' as an indexer property isn't supported. See https://github.com/dotnet/efcore/issues/31244 for more information. @@ -784,6 +796,9 @@ Unable to track an entity of type '{entityType}' because its primary key property '{keyProperty}' is null. + + The expression '{expression}' is not a valid member access expression. The expression should represent a simple property or field access: 't => t.MyProperty' or a chain of member accesses through non-collection complex properties: 't => t.MyComplex.MyProperty'. + The expression '{expression}' is not a valid member access expression. The expression should represent a simple property or field access: 't => t.MyProperty'. diff --git a/src/Shared/MemberInfoExtensions.cs b/src/Shared/MemberInfoExtensions.cs index 06564f5c027..3628603c6ca 100644 --- a/src/Shared/MemberInfoExtensions.cs +++ b/src/Shared/MemberInfoExtensions.cs @@ -7,6 +7,47 @@ namespace System.Reflection; internal static class EntityFrameworkMemberInfoExtensions { + public static MemberInfo ResolveMemberForType(this MemberInfo memberInfo, Type clrType) + { + if (memberInfo.DeclaringType is { IsInterface: true } interfaceType + && interfaceType != clrType + && interfaceType.IsAssignableFrom(clrType) + && memberInfo is PropertyInfo propertyInfo) + { + var propertyGetter = propertyInfo.GetMethod; + if (propertyGetter != null) + { + var interfaceMapping = clrType.GetInterfaceMap(interfaceType); + var index = Array.FindIndex(interfaceMapping.InterfaceMethods, p => p.Equals(propertyGetter)); + if (index >= 0) + { + var targetMethod = interfaceMapping.TargetMethods[index]; + foreach (var runtimeProperty in clrType.GetProperties( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + { + if (targetMethod.Equals(runtimeProperty.GetMethod)) + { + return runtimeProperty; + } + } + } + } + } + + return memberInfo; + } + + public static bool IsCandidateProperty(this MemberInfo memberInfo, bool needsWrite = true, bool publicOnly = true) + => memberInfo is PropertyInfo propertyInfo + ? !propertyInfo.IsStatic() + && propertyInfo.CanRead + && (!needsWrite || propertyInfo.FindSetterProperty() != null) + && propertyInfo.GetMethod != null + && (!publicOnly || propertyInfo.GetMethod.IsPublic) + && propertyInfo.GetIndexParameters().Length == 0 + : memberInfo is FieldInfo { IsStatic: false } fieldInfo + && (!publicOnly || fieldInfo.IsPublic); + public static Type GetMemberType(this MemberInfo memberInfo) => (memberInfo as PropertyInfo)?.PropertyType ?? ((FieldInfo)memberInfo).FieldType; diff --git a/src/Shared/PropertyInfoExtensions.cs b/src/Shared/PropertyInfoExtensions.cs index e93d8c475ea..af5b2fe6f3c 100644 --- a/src/Shared/PropertyInfoExtensions.cs +++ b/src/Shared/PropertyInfoExtensions.cs @@ -13,17 +13,6 @@ internal static class PropertyInfoExtensions public static bool IsStatic(this PropertyInfo property) => (property.GetMethod ?? property.SetMethod)!.IsStatic; - public static bool IsCandidateProperty(this MemberInfo memberInfo, bool needsWrite = true, bool publicOnly = true) - => memberInfo is PropertyInfo propertyInfo - ? !propertyInfo.IsStatic() - && propertyInfo.CanRead - && (!needsWrite || propertyInfo.FindSetterProperty() != null) - && propertyInfo.GetMethod != null - && (!publicOnly || propertyInfo.GetMethod.IsPublic) - && propertyInfo.GetIndexParameters().Length == 0 - : memberInfo is FieldInfo { IsStatic: false } fieldInfo - && (!publicOnly || fieldInfo.IsPublic); - public static bool IsIndexerProperty(this PropertyInfo propertyInfo) { var indexParams = propertyInfo.GetIndexParameters(); diff --git a/test/EFCore.InMemory.FunctionalTests/EFCore.InMemory.FunctionalTests.csproj b/test/EFCore.InMemory.FunctionalTests/EFCore.InMemory.FunctionalTests.csproj index b630896bc90..7191c2db044 100644 --- a/test/EFCore.InMemory.FunctionalTests/EFCore.InMemory.FunctionalTests.csproj +++ b/test/EFCore.InMemory.FunctionalTests/EFCore.InMemory.FunctionalTests.csproj @@ -20,7 +20,7 @@ - + diff --git a/test/EFCore.Relational.Specification.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Specification.Tests/ModelBuilding/RelationalModelBuilderTest.cs index cffd0c597ab..ccf9c70365e 100644 --- a/test/EFCore.Relational.Specification.Tests/ModelBuilding/RelationalModelBuilderTest.cs +++ b/test/EFCore.Relational.Specification.Tests/ModelBuilding/RelationalModelBuilderTest.cs @@ -780,6 +780,44 @@ protected override TestComplexCollectionBuilder ConfigureComplexCollec TestComplexCollectionBuilder builder) => builder.ToJson(); + public override void Dotted_complex_collection_string_configures_nested_collection() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity(b => + { + b.Ignore(e => e.Customers); + b.ComplexProperty(e => e.Customer, cb => + { + cb.ToJson(); + cb.Ignore(c => c.Orders); + cb.Ignore(c => c.Details); + }); + b.ComplexCollection, SpecialOrder>("Customer.SomeOrders", ob => + { + ob.Ignore(o => o.Customer); + ob.Ignore(o => o.Products); + ob.Ignore(o => o.Details); + ob.Ignore(o => o.OrderCombination); + ob.Ignore(o => o.SpecialCustomer); + ob.Ignore(o => o.BackOrder); + ob.Ignore(o => o.SpecialOrderCombination); + ob.Ignore(o => o.ShippingAddress); + }); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(ComplexProperties))!; + var customerType = entityType.FindComplexProperty("Customer")!.ComplexType; + var someOrdersComplex = customerType.FindComplexProperty("SomeOrders"); + + Assert.NotNull(someOrdersComplex); + Assert.True(someOrdersComplex!.IsCollection); + } + [ConditionalFact] public virtual void Complex_collection_mapped_to_json_uses_property_name_when_column_name_not_specified() { diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexCollections.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexCollections.cs index b81c0101dd1..6c775bff78d 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexCollections.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexCollections.cs @@ -1382,5 +1382,43 @@ public virtual void PrimitiveCollectionBuilder_methods_can_be_chained() .HasValueGeneratorFactory() .HasValueGeneratorFactory(typeof(CustomValueGeneratorFactory)) .IsRequired(); + + [ConditionalFact] + public virtual void Dotted_complex_collection_string_configures_nested_collection() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity(b => + { + b.Ignore(e => e.Customers); + b.ComplexProperty(e => e.Customer, cb => + { + cb.Ignore(c => c.Orders); + cb.Ignore(c => c.Details); + }); + b.ComplexCollection, SpecialOrder>("Customer.SomeOrders", ob => + { + ob.Ignore(o => o.Customer); + ob.Ignore(o => o.Products); + ob.Ignore(o => o.Details); + ob.Ignore(o => o.OrderCombination); + ob.Ignore(o => o.SpecialCustomer); + ob.Ignore(o => o.BackOrder); + ob.Ignore(o => o.SpecialOrderCombination); + ob.Ignore(o => o.ShippingAddress); + }); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(ComplexProperties))!; + var customerType = entityType.FindComplexProperty("Customer")!.ComplexType; + var someOrdersComplex = customerType.FindComplexProperty("SomeOrders"); + + Assert.NotNull(someOrdersComplex); + Assert.True(someOrdersComplex!.IsCollection); + } } } diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexType.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexType.cs index fcd5eaf7c39..62f45f39f67 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexType.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexType.cs @@ -2285,5 +2285,365 @@ public virtual void Can_specify_discriminator_value() var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; Assert.Equal(BasicEnum.Two, complexType.GetDiscriminatorValue()); } + + [ConditionalFact] + public virtual void Chained_property_lambda_configures_nested_complex_property() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity(b => + { + b.Ignore(e => e.Customers); + b.ComplexProperty(e => e.Customer, cb => + { + cb.Ignore(c => c.Orders); + cb.Ignore(c => c.Details); + }); + b.Property(e => e.Customer.Name).IsRequired(); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(ComplexProperties))!; + var customerType = entityType.FindComplexProperty("Customer")!.ComplexType; + var nameProperty = customerType.FindProperty("Name")!; + + Assert.False(nameProperty.IsNullable); + } + + [ConditionalFact] + public virtual void Chained_property_lambda_auto_creates_intermediate_complex_property() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity(b => + { + b.Ignore(e => e.Customers); + b.ComplexProperty(e => e.Customer, cb => + { + cb.Ignore(c => c.Orders); + cb.ComplexProperty(c => c.Details, db => + { + db.Ignore(d => d.Customer); + }); + }); + b.Property(e => e.Customer.Details.CustomerId).HasMaxLength(50); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(ComplexProperties))!; + var customerType = entityType.FindComplexProperty("Customer")!.ComplexType; + var detailsComplex = customerType.FindComplexProperty("Details"); + + Assert.NotNull(detailsComplex); + Assert.Equal(50, detailsComplex!.ComplexType.FindProperty("CustomerId")!.GetMaxLength()); + } + + [ConditionalFact] + public virtual void Chained_complex_property_lambda_configures_nested_complex() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity(b => + { + b.Ignore(e => e.Customers); + b.ComplexProperty(e => e.Customer, cb => + { + cb.Ignore(c => c.Orders); + }); + b.ComplexProperty(e => e.Customer.Details, cb => + { + cb.Ignore(c => c.Customer); + cb.Property(c => c.CustomerId).HasMaxLength(3); + }); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(ComplexProperties))!; + var customerType = entityType.FindComplexProperty("Customer")!.ComplexType; + var detailsType = customerType.FindComplexProperty("Details")!.ComplexType; + var customerIdProperty = detailsType.FindProperty("CustomerId")!; + + Assert.Equal(3, customerIdProperty.GetMaxLength()); + } + + [ConditionalFact] + public virtual void Chained_ignore_lambda_ignores_nested_member() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity(b => + { + b.Ignore(e => e.Customers); + b.ComplexProperty(e => e.Customer, cb => + { + cb.Ignore(c => c.Orders); + cb.ComplexProperty(c => c.Details, db => + { + db.Ignore(d => d.Customer); + }); + }); + b.Ignore(e => e.Customer.Details); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(ComplexProperties))!; + var customerType = entityType.FindComplexProperty("Customer")!.ComplexType; + var detailsComplex = customerType.FindComplexProperty("Details"); + + Assert.Null(detailsComplex); + } + + [ConditionalFact] + public virtual void Nested_chained_property_lambda_resolves_through_complex() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity(b => + { + b.Ignore(e => e.Customers); + b.ComplexProperty(e => e.Customer, cb => + { + cb.Ignore(c => c.Orders); + cb.ComplexProperty(c => c.Details, db => + { + db.Ignore(d => d.Customer); + }); + cb.Property(c => c.Details.CustomerId).HasMaxLength(50); + }); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(ComplexProperties))!; + var customerType = entityType.FindComplexProperty("Customer")!.ComplexType; + var detailsType = customerType.FindComplexProperty("Details")!.ComplexType; + var customerIdProperty = detailsType.FindProperty("CustomerId")!; + + Assert.Equal(50, customerIdProperty.GetMaxLength()); + } + + [ConditionalFact] + public virtual void Nested_chained_ignore_lambda_ignores_nested_member() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity(b => + { + b.Ignore(e => e.Customers); + b.ComplexProperty(e => e.Customer, cb => + { + cb.Ignore(c => c.Orders); + cb.ComplexProperty(c => c.Details, db => + { + db.Ignore(d => d.Customer); + }); + cb.Ignore(c => c.Details.CustomerId); + }); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(ComplexProperties))!; + var customerType = entityType.FindComplexProperty("Customer")!.ComplexType; + var detailsType = customerType.FindComplexProperty("Details")!.ComplexType; + + Assert.Null(detailsType.FindProperty("CustomerId")); + } + + [ConditionalFact] + public virtual void Chained_primitive_collection_lambda_configures_nested_collection() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity(b => + { + b.Ignore(e => e.Customers); + b.ComplexProperty(e => e.Customer, cb => + { + cb.Ignore(c => c.Orders); + cb.Ignore(c => c.Details); + }); + b.PrimitiveCollection(e => e.Customer.Notes).HasMaxLength(50); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(ComplexProperties))!; + var customerType = entityType.FindComplexProperty("Customer")!.ComplexType; + var notesProperty = customerType.FindProperty("Notes")!; + + Assert.Equal(50, notesProperty.GetMaxLength()); + } + + [ConditionalFact] + public virtual void Dotted_string_throws_for_nonexistent_intermediate_complex() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + + Assert.Equal( + CoreStrings.ComplexPropertyChainIntermediateNotFound("MissingComplex", nameof(ComplexProperties)), + Assert.Throws( + () => modelBuilder.Entity(b => + { + b.Ignore(e => e.Customer); + b.Ignore(e => e.Customers); + b.Property("MissingComplex.Foo"); + })).Message); + } + + [ConditionalFact] + public virtual void Dotted_string_throws_for_empty_segment() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + + Assert.Equal( + CoreStrings.ComplexPropertyChainInvalidSegment("Customer..Name"), + Assert.Throws( + () => modelBuilder.Entity(b => + { + b.Ignore(e => e.Customers); + b.ComplexProperty(e => e.Customer); + b.Property("Customer..Name"); + })).Message); + } + + [ConditionalFact] + public virtual void Dotted_string_throws_for_trailing_dot() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + + Assert.Equal( + CoreStrings.ComplexPropertyChainInvalidSegment("Customer.Name."), + Assert.Throws( + () => modelBuilder.Entity(b => + { + b.Ignore(e => e.Customers); + b.ComplexProperty(e => e.Customer); + b.Property("Customer.Name."); + })).Message); + } + + [ConditionalFact] + public virtual void Dotted_string_throws_for_leading_dot() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + + Assert.Equal( + CoreStrings.ComplexPropertyChainInvalidSegment(".Customer.Name"), + Assert.Throws( + () => modelBuilder.Entity(b => + { + b.Ignore(e => e.Customers); + b.ComplexProperty(e => e.Customer); + b.Property(".Customer.Name"); + })).Message); + } + + [ConditionalFact] + public virtual void Dotted_string_throws_when_intermediate_is_navigation() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder.Entity(b => + { + b.HasOne(e => e.Customer); + b.Ignore(e => e.Customers); + }); + + Assert.Equal( + CoreStrings.ComplexPropertyChainInvalidMember("Customer", nameof(ComplexProperties)), + Assert.Throws( + () => modelBuilder.Entity(b => + { + b.Property("Customer.Name"); + })).Message); + } + + [ConditionalFact] + public virtual void Dotted_string_throws_when_intermediate_is_scalar_property() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder.Entity(b => + { + b.Ignore(e => e.Customer); + b.Ignore(e => e.Customers); + b.Property(e => e.Id); + }); + + Assert.Equal( + CoreStrings.ComplexPropertyChainInvalidMember("Id", nameof(ComplexProperties)), + Assert.Throws( + () => modelBuilder.Entity(b => + { + b.Property("Id.Something"); + })).Message); + } + + [ConditionalFact] + public virtual void Dotted_string_throws_when_intermediate_is_skip_navigation() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder.Entity(b => + { + b.Ignore(e => e.Customer); + b.HasMany(e => e.Customers).WithMany(); + }); + + Assert.Equal( + CoreStrings.ComplexPropertyChainInvalidMember("Customers", nameof(ComplexProperties)), + Assert.Throws( + () => modelBuilder.Entity(b => + { + b.Property("Customers.Name"); + })).Message); + } + + [ConditionalFact] + public virtual void Dotted_string_throws_when_intermediate_is_service_property() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder.Entity(b => + { + b.Ignore(e => e.Customer); + b.Ignore(e => e.Customers); + b.Metadata.AddServiceProperty( + typeof(ComplexProperties).GetProperty(nameof(ComplexProperties.Customer))!, + serviceType: null); + }); + + Assert.Equal( + CoreStrings.ComplexPropertyChainInvalidMember("Customer", nameof(ComplexProperties)), + Assert.Throws( + () => modelBuilder.Entity(b => + { + b.Property("Customer.Name"); + })).Message); + } } } diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs index d2c38751240..8b2ef81ab84 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs @@ -1,10 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Reflection; +using Microsoft.EntityFrameworkCore.Internal; + namespace Microsoft.EntityFrameworkCore.ModelBuilding; public abstract partial class ModelBuilderTest { + private static string ToDottedName(IReadOnlyList memberChain) + => string.Join(".", memberChain.Select(m => m.GetSimpleMemberName())); + public class NonGenericTestModelBuilder(ModelBuilderFixtureBase fixture, Action? configure) : TestModelBuilder(fixture, configure) { @@ -94,8 +100,8 @@ public override TestEntityTypeBuilder HasNoKey() public override TestPropertyBuilder Property(Expression> propertyExpression) { - var memberInfo = propertyExpression.GetMemberAccess(); - return Wrap(EntityTypeBuilder.Property(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(EntityTypeBuilder.Property(memberChain[^1].GetMemberType(), ToDottedName(memberChain))); } public override TestPropertyBuilder Property(string propertyName) @@ -104,8 +110,8 @@ public override TestPropertyBuilder Property(string proper public override TestPrimitiveCollectionBuilder PrimitiveCollection( Expression> propertyExpression) { - var memberInfo = propertyExpression.GetMemberAccess(); - return Wrap(EntityTypeBuilder.PrimitiveCollection(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(EntityTypeBuilder.PrimitiveCollection(memberChain[^1].GetMemberType(), ToDottedName(memberChain))); } public override TestPrimitiveCollectionBuilder PrimitiveCollection(string propertyName) @@ -121,8 +127,8 @@ public override TestComplexPropertyBuilder ComplexProperty Expression> propertyExpression) where TProperty : default { - var memberInfo = propertyExpression.GetMemberAccess(); - return Wrap(EntityTypeBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(EntityTypeBuilder.ComplexProperty(memberChain[^1].GetMemberType(), ToDottedName(memberChain))); } public override TestComplexPropertyBuilder ComplexProperty( @@ -130,9 +136,8 @@ public override TestComplexPropertyBuilder ComplexProperty string complexTypeName) where TProperty : default { - var memberInfo = propertyExpression.GetMemberAccess(); - return Wrap( - EntityTypeBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), complexTypeName)); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(EntityTypeBuilder.ComplexProperty(memberChain[^1].GetMemberType(), ToDottedName(memberChain), complexTypeName)); } public override TestEntityTypeBuilder ComplexProperty( @@ -140,8 +145,8 @@ public override TestEntityTypeBuilder ComplexProperty( Action> buildAction) where TProperty : default { - var memberInfo = propertyExpression.GetMemberAccess(); - buildAction(Wrap(EntityTypeBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName()))); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + buildAction(Wrap(EntityTypeBuilder.ComplexProperty(memberChain[^1].GetMemberType(), ToDottedName(memberChain)))); return this; } @@ -152,10 +157,8 @@ public override TestEntityTypeBuilder ComplexProperty( Action> buildAction) where TProperty : default { - var memberInfo = propertyExpression.GetMemberAccess(); - buildAction( - Wrap( - EntityTypeBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), complexTypeName))); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + buildAction(Wrap(EntityTypeBuilder.ComplexProperty(memberChain[^1].GetMemberType(), ToDottedName(memberChain), complexTypeName))); return this; } @@ -181,8 +184,8 @@ public override TestComplexCollectionBuilder ComplexCollection?>> propertyExpression) where TElement : default { - var memberInfo = propertyExpression.GetMemberAccess(); - return Wrap(EntityTypeBuilder.ComplexCollection(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(EntityTypeBuilder.ComplexCollection(memberChain[^1].GetMemberType(), ToDottedName(memberChain))); } public override TestComplexCollectionBuilder ComplexCollection( @@ -190,9 +193,8 @@ public override TestComplexCollectionBuilder ComplexCollection( - EntityTypeBuilder.ComplexCollection(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), complexTypeName)); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(EntityTypeBuilder.ComplexCollection(memberChain[^1].GetMemberType(), ToDottedName(memberChain), complexTypeName)); } public override TestEntityTypeBuilder ComplexCollection( @@ -217,8 +219,9 @@ public override TestEntityTypeBuilder ComplexCollection( Action> buildAction) where TElement : default { - var memberInfo = propertyExpression.GetMemberAccess(); - buildAction(Wrap(EntityTypeBuilder.ComplexCollection(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName()))); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + buildAction(Wrap(EntityTypeBuilder.ComplexCollection(memberChain[^1].GetMemberType(), ToDottedName(memberChain)))); + return this; } @@ -228,10 +231,9 @@ public override TestEntityTypeBuilder ComplexCollection( Action> buildAction) where TElement : default { - var memberInfo = propertyExpression.GetMemberAccess(); - buildAction( - Wrap( - EntityTypeBuilder.ComplexCollection(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), complexTypeName))); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + buildAction(Wrap(EntityTypeBuilder.ComplexCollection(memberChain[^1].GetMemberType(), ToDottedName(memberChain), complexTypeName))); + return this; } @@ -247,7 +249,10 @@ public override TestNavigationBuilder Navigation( EntityTypeBuilder.Navigation(navigationExpression.GetMemberAccess().GetSimpleMemberName())); public override TestEntityTypeBuilder Ignore(Expression> propertyExpression) - => Wrap(EntityTypeBuilder.Ignore(propertyExpression.GetMemberAccess().GetSimpleMemberName())); + { + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(EntityTypeBuilder.Ignore(ToDottedName(memberChain))); + } public override TestEntityTypeBuilder Ignore(string propertyName) => Wrap(EntityTypeBuilder.Ignore(propertyName)); @@ -498,8 +503,8 @@ public override TestComplexPropertyBuilder HasTypeAnnotation(string an public override TestComplexTypePropertyBuilder Property( Expression> propertyExpression) { - var memberInfo = propertyExpression.GetMemberAccess(); - return Wrap(PropertyBuilder.Property(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(PropertyBuilder.Property(memberChain[^1].GetMemberType(), ToDottedName(memberChain))); } public override TestComplexTypePropertyBuilder Property(string propertyName) @@ -508,8 +513,8 @@ public override TestComplexTypePropertyBuilder Property(st public override TestComplexTypePrimitiveCollectionBuilder PrimitiveCollection( Expression> propertyExpression) { - var memberInfo = propertyExpression.GetMemberAccess(); - return Wrap(PropertyBuilder.PrimitiveCollection(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(PropertyBuilder.PrimitiveCollection(memberChain[^1].GetMemberType(), ToDottedName(memberChain))); } public override TestComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string propertyName) @@ -525,8 +530,8 @@ public override TestComplexPropertyBuilder ComplexProperty Expression> propertyExpression) where TProperty : default { - var memberInfo = propertyExpression.GetMemberAccess(); - return Wrap(PropertyBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(PropertyBuilder.ComplexProperty(memberChain[^1].GetMemberType(), ToDottedName(memberChain))); } public override TestComplexPropertyBuilder ComplexProperty( @@ -534,10 +539,8 @@ public override TestComplexPropertyBuilder ComplexProperty string complexTypeName) where TProperty : default { - var memberInfo = propertyExpression.GetMemberAccess(); - return Wrap( - PropertyBuilder.ComplexProperty( - memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), complexTypeName)); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(PropertyBuilder.ComplexProperty(memberChain[^1].GetMemberType(), ToDottedName(memberChain), complexTypeName)); } public override TestComplexPropertyBuilder ComplexProperty( @@ -554,8 +557,8 @@ public override TestComplexPropertyBuilder ComplexProperty( Action> buildAction) where TProperty : default { - var memberInfo = propertyExpression.GetMemberAccess(); - buildAction(Wrap(PropertyBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName()))); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + buildAction(Wrap(PropertyBuilder.ComplexProperty(memberChain[^1].GetMemberType(), ToDottedName(memberChain)))); return this; } @@ -566,11 +569,8 @@ public override TestComplexPropertyBuilder ComplexProperty( Action> buildAction) where TProperty : default { - var memberInfo = propertyExpression.GetMemberAccess(); - buildAction( - Wrap( - PropertyBuilder.ComplexProperty( - memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), complexTypeName))); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + buildAction(Wrap(PropertyBuilder.ComplexProperty(memberChain[^1].GetMemberType(), ToDottedName(memberChain), complexTypeName))); return this; } @@ -587,8 +587,8 @@ public override TestComplexCollectionBuilder ComplexCollection?>> propertyExpression) where TElement : default { - var memberInfo = propertyExpression.GetMemberAccess(); - return Wrap(PropertyBuilder.ComplexCollection(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(PropertyBuilder.ComplexCollection(memberChain[^1].GetMemberType(), ToDottedName(memberChain))); } public override TestComplexCollectionBuilder ComplexCollection( @@ -596,9 +596,8 @@ public override TestComplexCollectionBuilder ComplexCollection( - PropertyBuilder.ComplexCollection(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), complexTypeName)); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(PropertyBuilder.ComplexCollection(memberChain[^1].GetMemberType(), ToDottedName(memberChain), complexTypeName)); } public override TestComplexPropertyBuilder ComplexCollection( @@ -623,8 +622,9 @@ public override TestComplexPropertyBuilder ComplexCollection Action> buildAction) where TElement : default { - var memberInfo = propertyExpression.GetMemberAccess(); - buildAction(Wrap(PropertyBuilder.ComplexCollection(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName()))); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + buildAction(Wrap(PropertyBuilder.ComplexCollection(memberChain[^1].GetMemberType(), ToDottedName(memberChain)))); + return this; } @@ -634,15 +634,17 @@ public override TestComplexPropertyBuilder ComplexCollection Action> buildAction) where TElement : default { - var memberInfo = propertyExpression.GetMemberAccess(); - buildAction( - Wrap( - PropertyBuilder.ComplexCollection(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), complexTypeName))); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + buildAction(Wrap(PropertyBuilder.ComplexCollection(memberChain[^1].GetMemberType(), ToDottedName(memberChain), complexTypeName))); + return this; } public override TestComplexPropertyBuilder Ignore(Expression> propertyExpression) - => Wrap(PropertyBuilder.Ignore(propertyExpression.GetMemberAccess().GetSimpleMemberName())); + { + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(PropertyBuilder.Ignore(ToDottedName(memberChain))); + } public override TestComplexPropertyBuilder Ignore(string propertyName) => Wrap(PropertyBuilder.Ignore(propertyName)); @@ -710,8 +712,8 @@ public override TestComplexCollectionBuilder HasTypeAnnotation(string public override TestComplexCollectionTypePropertyBuilder Property( Expression> propertyExpression) { - var memberInfo = propertyExpression.GetMemberAccess(); - return Wrap(PropertyBuilder.Property(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(PropertyBuilder.Property(memberChain[^1].GetMemberType(), ToDottedName(memberChain))); } public override TestComplexCollectionTypePropertyBuilder Property(string propertyName) @@ -720,8 +722,8 @@ public override TestComplexCollectionTypePropertyBuilder Property PrimitiveCollection( Expression> propertyExpression) { - var memberInfo = propertyExpression.GetMemberAccess(); - return Wrap(PropertyBuilder.PrimitiveCollection(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(PropertyBuilder.PrimitiveCollection(memberChain[^1].GetMemberType(), ToDottedName(memberChain))); } public override TestComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string propertyName) @@ -737,8 +739,8 @@ public override TestComplexPropertyBuilder ComplexProperty Expression> propertyExpression) where TProperty : default { - var memberInfo = propertyExpression.GetMemberAccess(); - return Wrap(PropertyBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(PropertyBuilder.ComplexProperty(memberChain[^1].GetMemberType(), ToDottedName(memberChain))); } public override TestComplexPropertyBuilder ComplexProperty( @@ -746,10 +748,8 @@ public override TestComplexPropertyBuilder ComplexProperty string complexTypeName) where TProperty : default { - var memberInfo = propertyExpression.GetMemberAccess(); - return Wrap( - PropertyBuilder.ComplexProperty( - memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), complexTypeName)); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(PropertyBuilder.ComplexProperty(memberChain[^1].GetMemberType(), ToDottedName(memberChain), complexTypeName)); } public override TestComplexCollectionBuilder ComplexProperty( @@ -766,8 +766,8 @@ public override TestComplexCollectionBuilder ComplexProperty> buildAction) where TProperty : default { - var memberInfo = propertyExpression.GetMemberAccess(); - buildAction(Wrap(PropertyBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName()))); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + buildAction(Wrap(PropertyBuilder.ComplexProperty(memberChain[^1].GetMemberType(), ToDottedName(memberChain)))); return this; } @@ -778,11 +778,8 @@ public override TestComplexCollectionBuilder ComplexProperty> buildAction) where TProperty : default { - var memberInfo = propertyExpression.GetMemberAccess(); - buildAction( - Wrap( - PropertyBuilder.ComplexProperty( - memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), complexTypeName))); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + buildAction(Wrap(PropertyBuilder.ComplexProperty(memberChain[^1].GetMemberType(), ToDottedName(memberChain), complexTypeName))); return this; } @@ -799,8 +796,8 @@ public override TestComplexCollectionBuilder ComplexCollection?>> propertyExpression) where TElement : default { - var memberInfo = propertyExpression.GetMemberAccess(); - return Wrap(PropertyBuilder.ComplexCollection(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(PropertyBuilder.ComplexCollection(memberChain[^1].GetMemberType(), ToDottedName(memberChain))); } public override TestComplexCollectionBuilder ComplexCollection( @@ -808,9 +805,8 @@ public override TestComplexCollectionBuilder ComplexCollection( - PropertyBuilder.ComplexCollection(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), complexTypeName)); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(PropertyBuilder.ComplexCollection(memberChain[^1].GetMemberType(), ToDottedName(memberChain), complexTypeName)); } public override TestComplexCollectionBuilder ComplexCollection( @@ -835,8 +831,9 @@ public override TestComplexCollectionBuilder ComplexCollection> buildAction) where TElement : default { - var memberInfo = propertyExpression.GetMemberAccess(); - buildAction(Wrap(PropertyBuilder.ComplexCollection(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName()))); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + buildAction(Wrap(PropertyBuilder.ComplexCollection(memberChain[^1].GetMemberType(), ToDottedName(memberChain)))); + return this; } @@ -846,15 +843,17 @@ public override TestComplexCollectionBuilder ComplexCollection> buildAction) where TElement : default { - var memberInfo = propertyExpression.GetMemberAccess(); - buildAction( - Wrap( - PropertyBuilder.ComplexCollection(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), complexTypeName))); + var memberChain = propertyExpression.MatchMemberAccessChain()!; + buildAction(Wrap(PropertyBuilder.ComplexCollection(memberChain[^1].GetMemberType(), ToDottedName(memberChain), complexTypeName))); + return this; } public override TestComplexCollectionBuilder Ignore(Expression> propertyExpression) - => Wrap(PropertyBuilder.Ignore(propertyExpression.GetMemberAccess().GetSimpleMemberName())); + { + var memberChain = propertyExpression.MatchMemberAccessChain()!; + return Wrap(PropertyBuilder.Ignore(ToDottedName(memberChain))); + } public override TestComplexCollectionBuilder Ignore(string propertyName) => Wrap(PropertyBuilder.Ignore(propertyName)); diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OwnedTypes.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OwnedTypes.cs index d157758611e..dd0e37daf36 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OwnedTypes.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OwnedTypes.cs @@ -2138,5 +2138,36 @@ public override ValueGenerator Create(IProperty property, ITypeBase entityType) } private class CustomValueComparer() : ValueComparer(false); + + [ConditionalFact] + public virtual void Lambda_throws_when_intermediate_is_owned_navigation() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder.Entity().OwnsOne(c => c.Details); + + Assert.Equal( + CoreStrings.ComplexPropertyChainInvalidMember(nameof(Customer.Details), nameof(Customer)), + Assert.Throws( + () => modelBuilder.Entity().Property(c => c.Details.CustomerId)).Message); + } + + [ConditionalFact] + public virtual void Dotted_string_throws_when_intermediate_is_nested_owned_navigation() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder.Ignore(); + modelBuilder.Entity().OwnsOne( + o => o.Customer, + cb => cb.OwnsMany(c => c.Orders)); + + Assert.Equal( + CoreStrings.ComplexPropertyChainInvalidMember(nameof(CustomerDetails.Customer), "CustomerDetails"), + Assert.Throws( + () => modelBuilder.Entity().Property("Customer.Name")).Message); + } } }