diff --git a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs index b6d9d402eb1..4291c0e25f5 100644 --- a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs +++ b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs @@ -19,6 +19,7 @@ using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.DependencyInjection; +// ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore.Migrations { /// diff --git a/src/EFCore.SqlServer/SqlServerRetryingExecutionStrategy.cs b/src/EFCore.SqlServer/SqlServerRetryingExecutionStrategy.cs index ec38c45a199..a46a62dfe53 100644 --- a/src/EFCore.SqlServer/SqlServerRetryingExecutionStrategy.cs +++ b/src/EFCore.SqlServer/SqlServerRetryingExecutionStrategy.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; using Microsoft.EntityFrameworkCore.Storage; +// ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore { /// diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerDatabaseCreator.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerDatabaseCreator.cs index 2e3add29513..7c32cfcd54f 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerDatabaseCreator.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerDatabaseCreator.cs @@ -349,8 +349,7 @@ private IReadOnlyList CreateDropCommands() } }; - var masterCommands = Dependencies.MigrationsSqlGenerator.Generate(operations); - return masterCommands; + return Dependencies.MigrationsSqlGenerator.Generate(operations); } // Clear connection pools in case there are active connections that are pooled diff --git a/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs b/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs index c77a368638b..df7fa75059a 100644 --- a/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs +++ b/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; @@ -441,20 +442,56 @@ public static IConventionProperty FindDeclaredProperty([NotNull] this IConventio /// Adds a property to this entity type. /// /// The entity type to add the property to. - /// The corresponding property in the entity class. + /// The corresponding member on the entity class. /// Indicates whether the configuration was specified using a data annotation. /// The newly created property. public static IConventionProperty AddProperty( [NotNull] this IConventionEntityType entityType, - [NotNull] PropertyInfo propertyInfo, + [NotNull] MemberInfo memberInfo, bool fromDataAnnotation = false) - { - Check.NotNull(entityType, nameof(entityType)); - Check.NotNull(propertyInfo, nameof(propertyInfo)); + => Check.NotNull(entityType, nameof(entityType)).AddProperty(memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), + memberInfo, setTypeConfigurationSource: true, fromDataAnnotation); - return ((EntityType)entityType).AddProperty( - propertyInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - } + /// + /// Adds a property to this entity type. + /// + /// The entity type to add the property to. + /// The name of the property to add. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + public static IConventionProperty AddProperty( + [NotNull] this IConventionEntityType entityType, [NotNull] string name, + bool fromDataAnnotation = false) + => ((EntityType)entityType).AddProperty(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// Adds a property to this entity type. + /// + /// The entity type to add the property to. + /// The name of the property to add. + /// The type of value the property will hold. + /// Indicates whether the type configuration source should be set. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + public static IConventionProperty AddProperty( + [NotNull] this IConventionEntityType entityType, [NotNull] string name, [NotNull] Type propertyType, + bool setTypeConfigurationSource = true, bool fromDataAnnotation = false) + => entityType.AddProperty(name, propertyType, null, setTypeConfigurationSource, fromDataAnnotation); + + /// + /// Adds a property based on an indexer to this entity type. + /// + /// The entity type to add the property to. + /// The name of the property to add. + /// The type of value the property will hold. + /// Indicates whether the type configuration source should be set. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + public static IConventionProperty AddIndexedProperty( + [NotNull] this IConventionEntityType entityType, [NotNull] string name, [NotNull] Type propertyType, + bool setTypeConfigurationSource = true, bool fromDataAnnotation = false) + => Check.NotNull(entityType, nameof(entityType)) + .AddProperty(name, propertyType, entityType.GetIndexerProperty(), setTypeConfigurationSource, fromDataAnnotation); /// /// Gets the index defined on the given property. Returns null if no index is defined. diff --git a/src/EFCore/Extensions/MutableEntityTypeExtensions.cs b/src/EFCore/Extensions/MutableEntityTypeExtensions.cs index f39ed8a1099..78eff15bfd4 100644 --- a/src/EFCore/Extensions/MutableEntityTypeExtensions.cs +++ b/src/EFCore/Extensions/MutableEntityTypeExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; @@ -434,16 +435,45 @@ public static IMutableProperty FindDeclaredProperty([NotNull] this IMutableEntit /// Adds a property to this entity type. /// /// The entity type to add the property to. - /// The corresponding property in the entity class. + /// The corresponding member on the entity class. /// The newly created property. public static IMutableProperty AddProperty( - [NotNull] this IMutableEntityType entityType, [NotNull] PropertyInfo propertyInfo) - { - Check.NotNull(entityType, nameof(entityType)); - Check.NotNull(propertyInfo, nameof(propertyInfo)); + [NotNull] this IMutableEntityType entityType, [NotNull] MemberInfo memberInfo) + => Check.NotNull(entityType, nameof(entityType)) + .AddProperty(memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), memberInfo); - return entityType.AsEntityType().AddProperty(propertyInfo, ConfigurationSource.Explicit); - } + /// + /// Adds a property to this entity type. + /// + /// The entity type to add the property to. + /// The name of the property to add. + /// The newly created property. + public static IMutableProperty AddProperty( + [NotNull] this IMutableEntityType entityType, [NotNull] string name) + => ((EntityType)entityType).AddProperty(name, ConfigurationSource.Explicit); + + /// + /// Adds a property to this entity type. + /// + /// The entity type to add the property to. + /// The name of the property to add. + /// The type of value the property will hold. + /// The newly created property. + public static IMutableProperty AddProperty( + [NotNull] this IMutableEntityType entityType, [NotNull] string name, [NotNull] Type propertyType) + => entityType.AddProperty(name, propertyType, null); + + /// + /// Adds a property based on an indexer to this entity type. + /// + /// The entity type to add the property to. + /// The name of the property to add. + /// The type of value the property will hold. + /// The newly created property. + public static IMutableProperty AddIndexedProperty( + [NotNull] this IMutableEntityType entityType, [NotNull] string name, [NotNull] Type propertyType) + => Check.NotNull(entityType, nameof(entityType)) + .AddProperty(name, propertyType, entityType.GetIndexerProperty()); /// /// Gets the index defined on the given property. Returns null if no index is defined. diff --git a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs index f6838e2e849..6ec171ed845 100644 --- a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs @@ -98,7 +98,8 @@ IReadOnlyList GetOrCreateProperties( /// /// The properties to remove. /// Indicates whether the configuration was specified using a data annotation. - void RemoveUnusedShadowProperties([NotNull] IReadOnlyList properties, bool fromDataAnnotation = false); + IConventionEntityTypeBuilder RemoveUnusedShadowProperties( + [NotNull] IReadOnlyList properties, bool fromDataAnnotation = false); /// /// Returns an object that can be used to configure the service property with the given member info. diff --git a/src/EFCore/Metadata/IConventionEntityType.cs b/src/EFCore/Metadata/IConventionEntityType.cs index 90f7de23410..e8009545143 100644 --- a/src/EFCore/Metadata/IConventionEntityType.cs +++ b/src/EFCore/Metadata/IConventionEntityType.cs @@ -194,26 +194,21 @@ IConventionForeignKey AddForeignKey( /// /// The name of the property to add. /// The type of value the property will hold. + /// + /// + /// The corresponding CLR type member or null for a shadow property. + /// + /// + /// An indexer with a string parameter and object return type can be used. + /// + /// /// Indicates whether the type configuration source should be set. /// Indicates whether the configuration was specified using a data annotation. /// The newly created property. IConventionProperty AddProperty( - [NotNull] string name, - [CanBeNull] Type propertyType, - bool setTypeConfigurationSource = true, - bool fromDataAnnotation = false); - - /// - /// Adds a property based on an indexer to this entity type. - /// - /// The name of the property to add. - /// The type of value the property will hold. - /// Indicates whether the type configuration source should be set. - /// Indicates whether the configuration was specified using a data annotation. - /// The newly created property. - IConventionProperty AddIndexedProperty( [NotNull] string name, [NotNull] Type propertyType, + [CanBeNull] MemberInfo memberInfo, bool setTypeConfigurationSource = true, bool fromDataAnnotation = false); diff --git a/src/EFCore/Metadata/IMutableEntityType.cs b/src/EFCore/Metadata/IMutableEntityType.cs index 5dd4cd0b6c5..7aba036a97f 100644 --- a/src/EFCore/Metadata/IMutableEntityType.cs +++ b/src/EFCore/Metadata/IMutableEntityType.cs @@ -151,16 +151,16 @@ IMutableForeignKey AddForeignKey( /// /// The name of the property to add. /// The type of value the property will hold. + /// + /// + /// The corresponding CLR type member or null for a shadow property. + /// + /// + /// An indexer with a string parameter and object return type can be used. + /// + /// /// The newly created property. - IMutableProperty AddProperty([NotNull] string name, [CanBeNull] Type propertyType); - - /// - /// Adds a property based on an indexer to this entity type. - /// - /// The name of the property to add. - /// The type of value the property will hold. - /// The newly created property. - IMutableProperty AddIndexedProperty([NotNull] string name, [NotNull] Type propertyType); + IMutableProperty AddProperty([NotNull] string name, [NotNull] Type propertyType, [CanBeNull] MemberInfo memberInfo); /// /// diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index 62c9c997c05..566e71d31c1 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -1751,18 +1751,19 @@ public virtual Index RemoveIndex(Index index) /// public virtual Property AddProperty( [NotNull] string name, - [CanBeNull] Type propertyType, + [NotNull] Type propertyType, ConfigurationSource? typeConfigurationSource, ConfigurationSource configurationSource) { Check.NotNull(name, nameof(name)); + Check.NotNull(propertyType, nameof(propertyType)); return AddProperty( name, propertyType, - ClrType?.GetMembersInHierarchy(name).FirstOrDefault(), - configurationSource, - typeConfigurationSource); + null, + typeConfigurationSource, + configurationSource); } /// @@ -1774,27 +1775,30 @@ public virtual Property AddProperty( public virtual Property AddProperty( [NotNull] MemberInfo memberInfo, ConfigurationSource configurationSource) - { - Check.NotNull(memberInfo, nameof(memberInfo)); - - if (ClrType == null) - { - throw new InvalidOperationException(CoreStrings.ClrPropertyOnShadowEntity(memberInfo.Name, this.DisplayName())); - } - - if (memberInfo.DeclaringType?.GetTypeInfo().IsAssignableFrom(ClrType.GetTypeInfo()) != true) - { - throw new ArgumentException( - CoreStrings.PropertyWrongEntityClrType( - memberInfo.Name, this.DisplayName(), memberInfo.DeclaringType?.ShortDisplayName())); - } - - return AddProperty( + => AddProperty( memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), memberInfo, configurationSource, configurationSource); + + /// + /// 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 Property AddProperty( + [NotNull] string name, + ConfigurationSource configurationSource) + { + var clrMember = ClrType?.GetMembersInHierarchy(name).FirstOrDefault(); + if (clrMember == null) + { + throw new InvalidOperationException(CoreStrings.NoPropertyType(name, this.DisplayName())); + } + + return AddProperty(clrMember, configurationSource); } /// @@ -1808,25 +1812,28 @@ public virtual Property AddIndexedProperty( [NotNull] Type propertyType, ConfigurationSource? typeConfigurationSource, ConfigurationSource configurationSource) - { - Check.NotNull(name, nameof(name)); - Check.NotNull(propertyType, nameof(propertyType)); - - return AddProperty( + => AddProperty( name, propertyType, this.GetIndexerProperty(), - configurationSource, - typeConfigurationSource); - } + typeConfigurationSource, + configurationSource); - private Property AddProperty( + /// + /// 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 Property AddProperty( string name, Type propertyType, MemberInfo memberInfo, - ConfigurationSource configurationSource, - ConfigurationSource? typeConfigurationSource) + ConfigurationSource? typeConfigurationSource, + ConfigurationSource configurationSource) { + Check.NotNull(name, nameof(name)); + Check.NotNull(propertyType, nameof(propertyType)); Debug.Assert(Builder != null); var conflictingMember = FindMembersInHierarchy(name).FirstOrDefault(); @@ -1838,19 +1845,56 @@ private Property AddProperty( conflictingMember.DeclaringType.DisplayName())); } - if (propertyType == null) + if (memberInfo != null) { - if (memberInfo == null) + if (ClrType == null) { - throw new InvalidOperationException(CoreStrings.NoPropertyType(name, this.DisplayName())); + throw new InvalidOperationException(CoreStrings.ClrPropertyOnShadowEntity(memberInfo.Name, this.DisplayName())); } - propertyType = memberInfo.GetMemberType(); - typeConfigurationSource = ConfigurationSource.Convention.Max(typeConfigurationSource); + if (memberInfo.DeclaringType?.GetTypeInfo().IsAssignableFrom(ClrType.GetTypeInfo()) != true) + { + throw new ArgumentException( + CoreStrings.PropertyWrongEntityClrType( + memberInfo.Name, this.DisplayName(), memberInfo.DeclaringType?.ShortDisplayName())); + } + + if (name != memberInfo.GetSimpleMemberName()) + { + if ((memberInfo as PropertyInfo)?.IsEFIndexerProperty() != true) + { + if (typeConfigurationSource != null) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongName( + name, + this.DisplayName(), + memberInfo.GetSimpleMemberName())); + } + + propertyType = memberInfo.GetMemberType(); + } + else + { + var clashingMemberInfo = ClrType?.GetMembersInHierarchy(name).FirstOrDefault(); + if (clashingMemberInfo != null) + { + throw new InvalidOperationException( + CoreStrings.PropertyClashingNonIndexer( + name, + this.DisplayName())); + } + } + } + } + else + { + memberInfo = ClrType?.GetMembersInHierarchy(name).FirstOrDefault(); } - else if (memberInfo != null - && propertyType != memberInfo.GetMemberType() - && (memberInfo as PropertyInfo)?.IsEFIndexerProperty() != true) + + if (memberInfo != null + && propertyType != memberInfo.GetMemberType() + && (memberInfo as PropertyInfo)?.IsEFIndexerProperty() != true) { if (typeConfigurationSource != null) { @@ -2690,12 +2734,8 @@ IMutableIndex IMutableEntityType.AddIndex(IReadOnlyList proper void IMutableEntityType.RemoveIndex(IMutableIndex index) => RemoveIndex((Index)index); /// - IMutableProperty IMutableEntityType.AddProperty(string name, Type propertyType) - => AddProperty(name, propertyType, ConfigurationSource.Explicit, ConfigurationSource.Explicit); - - /// - IMutableProperty IMutableEntityType.AddIndexedProperty(string name, Type propertyType) - => AddIndexedProperty(name, propertyType, ConfigurationSource.Explicit, ConfigurationSource.Explicit); + IMutableProperty IMutableEntityType.AddProperty(string name, Type propertyType, MemberInfo memberInfo) + => AddProperty(name, propertyType, memberInfo, ConfigurationSource.Explicit, ConfigurationSource.Explicit); /// [DebuggerStepThrough] @@ -2820,21 +2860,11 @@ IConventionServiceProperty IConventionEntityType.AddServiceProperty(MemberInfo m /// IConventionProperty IConventionEntityType.AddProperty( - string name, Type propertyType, bool setTypeConfigurationSource, bool fromDataAnnotation) + string name, Type propertyType, MemberInfo memberInfo, bool setTypeConfigurationSource, bool fromDataAnnotation) => AddProperty( name, propertyType, - setTypeConfigurationSource - ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention - : (ConfigurationSource?)null, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - IConventionProperty IConventionEntityType.AddIndexedProperty( - string name, Type propertyType, bool setTypeConfigurationSource, bool fromDataAnnotation) - => AddIndexedProperty( - name, - propertyType, + memberInfo, setTypeConfigurationSource ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention : (ConfigurationSource?)null, diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 8e3e9da8c02..0e41b9f7766 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -397,10 +397,10 @@ public virtual InternalEntityTypeBuilder HasNoKey(ConfigurationSource configurat /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual InternalPropertyBuilder Property( - [NotNull] Type propertyType, + [CanBeNull] Type propertyType, [NotNull] string propertyName, ConfigurationSource? configurationSource) - => Property(propertyType, propertyName, configurationSource, typeConfigurationSource: configurationSource); + => Property(propertyType, propertyName, typeConfigurationSource: configurationSource, configurationSource: configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -409,13 +409,14 @@ public virtual InternalPropertyBuilder Property( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual InternalPropertyBuilder Property( - [NotNull] Type propertyType, + [CanBeNull] Type propertyType, [NotNull] string propertyName, - ConfigurationSource? configurationSource, - ConfigurationSource? typeConfigurationSource) + ConfigurationSource? typeConfigurationSource, + ConfigurationSource? configurationSource) => Property( - propertyType, propertyName, memberInfo: null, configurationSource: configurationSource, - typeConfigurationSource: typeConfigurationSource); + propertyType, propertyName, memberInfo: null, + typeConfigurationSource: typeConfigurationSource, + configurationSource: configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -425,8 +426,8 @@ public virtual InternalPropertyBuilder Property( /// public virtual InternalPropertyBuilder Property([NotNull] string propertyName, ConfigurationSource? configurationSource) => Property( - propertyType: null, propertyName: propertyName, memberInfo: null, configurationSource: configurationSource, - typeConfigurationSource: null); + propertyType: null, propertyName: propertyName, memberInfo: null, typeConfigurationSource: null, + configurationSource: configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -441,8 +442,8 @@ private InternalPropertyBuilder Property( [CanBeNull] Type propertyType, [NotNull] string propertyName, [CanBeNull] MemberInfo memberInfo, - ConfigurationSource? configurationSource, - ConfigurationSource? typeConfigurationSource) + ConfigurationSource? typeConfigurationSource, + ConfigurationSource? configurationSource) { IEnumerable propertiesToDetach = null; var existingProperty = Metadata.FindProperty(propertyName); @@ -474,7 +475,7 @@ private InternalPropertyBuilder Property( return existingProperty.DeclaringEntityType.Builder .Property( - existingProperty, propertyName, propertyType, memberInfo, configurationSource, typeConfigurationSource); + existingProperty, propertyName, propertyType, memberInfo, typeConfigurationSource, configurationSource); } } } @@ -512,6 +513,18 @@ private InternalPropertyBuilder Property( } } + if (propertyType == null) + { + var clrMember = Metadata.ClrType?.GetMembersInHierarchy(propertyName).FirstOrDefault(); + if (clrMember == null) + { + throw new InvalidOperationException(CoreStrings.NoPropertyType(propertyName, Metadata.DisplayName())); + } + + propertyType = clrMember.GetMemberType(); + typeConfigurationSource = ConfigurationSource.Explicit; + } + Metadata.RemoveIgnored(propertyName); propertiesToDetach = Metadata.FindDerivedProperties(propertyName); @@ -523,7 +536,7 @@ private InternalPropertyBuilder Property( var detachedProperties = propertiesToDetach == null ? null : DetachProperties(propertiesToDetach); builder = Property( - existingProperty, propertyName, propertyType, memberInfo, configurationSource, typeConfigurationSource); + existingProperty, propertyName, propertyType, memberInfo, typeConfigurationSource, configurationSource); detachedProperties?.Attach(this); } @@ -539,8 +552,8 @@ private InternalPropertyBuilder Property( [NotNull] string propertyName, [CanBeNull] Type propertyType, [CanBeNull] MemberInfo clrProperty, - ConfigurationSource? configurationSource, - ConfigurationSource? typeConfigurationSource) + ConfigurationSource? typeConfigurationSource, + ConfigurationSource? configurationSource) { Property property; if (existingProperty == null) @@ -1708,7 +1721,7 @@ private void RemoveKeyIfUnused(Key key) /// 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 void RemoveUnusedShadowProperties( + public virtual InternalEntityTypeBuilder RemoveUnusedShadowProperties( [NotNull] IReadOnlyList properties, ConfigurationSource configurationSource = ConfigurationSource.Convention) where T : class, IProperty { @@ -1719,6 +1732,8 @@ public virtual void RemoveUnusedShadowProperties( RemovePropertyIfUnused((Property)(object)property, configurationSource); } } + + return this; } private static void RemovePropertyIfUnused(Property property, ConfigurationSource configurationSource) @@ -3071,7 +3086,7 @@ private IReadOnlyList CreateUniqueProperties( if (currentProperties == null) { var propertyBuilder = Property( - clrType, propertyName, ConfigurationSource.Convention, typeConfigurationSource: null); + clrType, propertyName, typeConfigurationSource: null, configurationSource: ConfigurationSource.Convention); if (clrType.IsNullableType()) { @@ -3139,37 +3154,25 @@ public virtual IReadOnlyList GetOrCreateProperties( var property = Metadata.FindProperty(propertyName); if (property == null) { - var clrProperty = Metadata.ClrType?.GetMembersInHierarchy(propertyName).FirstOrDefault(); var type = referencedProperties == null ? useDefaultType ? typeof(int) : null : referencedProperties[i].ClrType; - InternalPropertyBuilder propertyBuilder; if (!configurationSource.HasValue) { return null; } - if (clrProperty != null) - { - propertyBuilder = Property(clrProperty, configurationSource.Value); - } - else if (type != null) - { - // TODO: Log that a shadow property is created - propertyBuilder = Property( - required - ? type - : type.MakeNullable(), - propertyName, - configurationSource.Value, typeConfigurationSource: ConfigurationSource.Convention); - } - else - { - throw new InvalidOperationException(CoreStrings.NoPropertyType(propertyName, Metadata.DisplayName())); - } + // TODO: Log that a shadow property is created + var propertyBuilder = Property( + required + ? type + : type?.MakeNullable(), + propertyName, + typeConfigurationSource: null, + configurationSource.Value); if (propertyBuilder == null) { @@ -3256,8 +3259,8 @@ public virtual IReadOnlyList GetActualProperties( typeConfigurationSource.Overrides(ConfigurationSource.DataAnnotation) ? property.ClrType : null, property.Name, property.GetIdentifyingMemberInfo(), - configurationSource, - typeConfigurationSource.Overrides(ConfigurationSource.DataAnnotation) ? typeConfigurationSource : null); + typeConfigurationSource.Overrides(ConfigurationSource.DataAnnotation) ? typeConfigurationSource : null, + configurationSource); if (builder == null) { return null; @@ -3352,8 +3355,8 @@ public virtual InternalPropertyBuilder GetOrCreateDiscriminatorProperty(Type typ return Metadata.RootType().Builder.Property( type ?? discriminatorProperty?.ClrType ?? DefaultDiscriminatorType, name ?? discriminatorProperty?.Name ?? DefaultDiscriminatorName, - configurationSource, - typeConfigurationSource: type != null ? configurationSource : (ConfigurationSource?)null); + typeConfigurationSource: type != null ? configurationSource : (ConfigurationSource?)null, + configurationSource: configurationSource); } public virtual DiscriminatorBuilder DiscriminatorBuilder( @@ -3369,7 +3372,7 @@ public virtual DiscriminatorBuilder DiscriminatorBuilder( var discriminatorProperty = discriminatorPropertyBuilder.Metadata; // Make sure the property is on the root type discriminatorPropertyBuilder = rootTypeBuilder.Property( - discriminatorProperty.ClrType, discriminatorProperty.Name, ConfigurationSource.Convention, null); + discriminatorProperty.ClrType, discriminatorProperty.Name, null, ConfigurationSource.Convention); var oldDiscriminatorProperty = Metadata.GetDiscriminatorProperty() as Property; if (oldDiscriminatorProperty?.Builder != null @@ -3428,10 +3431,9 @@ IConventionPropertyBuilder IConventionEntityTypeBuilder.Property( Type propertyType, string propertyName, bool setTypeConfigurationSource, bool fromDataAnnotation) => Property( propertyType, - propertyName, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, setTypeConfigurationSource + propertyName, setTypeConfigurationSource ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention - : (ConfigurationSource?)null); + : (ConfigurationSource?)null, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// IConventionPropertyBuilder IConventionEntityTypeBuilder.Property(MemberInfo memberInfo, bool fromDataAnnotation) @@ -3449,7 +3451,7 @@ IReadOnlyList IConventionEntityTypeBuilder.GetOrCreatePrope => GetOrCreateProperties(memberInfos, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - void IConventionEntityTypeBuilder.RemoveUnusedShadowProperties( + IConventionEntityTypeBuilder IConventionEntityTypeBuilder.RemoveUnusedShadowProperties( IReadOnlyList properties, bool fromDataAnnotation) => RemoveUnusedShadowProperties( properties, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs index 528f6ec5c25..f3862f260dd 100644 --- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs @@ -612,7 +612,7 @@ public virtual InternalPropertyBuilder Attach([NotNull] InternalEntityTypeBuilde { newPropertyBuilder = Metadata.GetIdentifyingMemberInfo() == null ? entityTypeBuilder.Property( - Metadata.ClrType, Metadata.Name, configurationSource, Metadata.GetTypeConfigurationSource()) + Metadata.ClrType, Metadata.Name, Metadata.GetTypeConfigurationSource(), configurationSource) : entityTypeBuilder.Property(Metadata.GetIdentifyingMemberInfo(), configurationSource); } diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index db904865064..8912e515153 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -153,7 +153,7 @@ public static string CannotLoadDetached([CanBeNull] object navigation, [CanBeNul navigation, entityType); /// - /// The entity type '{entityType}' requires a primary key to be defined. If you intended to use a keyless entity type call `HasNoKey()`. + /// The entity type '{entityType}' requires a primary key to be defined. If you intended to use a keyless entity type call 'HasNoKey()'. /// public static string EntityRequiresKey([CanBeNull] object entityType) => string.Format( @@ -1025,7 +1025,7 @@ public static string DataBindingWithIListSource => GetString("DataBindingWithIListSource"); /// - /// Data binding directly to `DbSet.Local` is not supported since it does not provide a stable ordering. For WPF bind to 'DbSet.Local.ToObservableCollection()'. For WinForms bind to 'DbSet.Local.ToBindingList()'. For ASP.NET WebForms bind to 'DbSet.ToList()' or use Model Binding. + /// Data binding directly to 'DbSet.Local' is not supported since it does not provide a stable ordering. For WPF bind to 'DbSet.Local.ToObservableCollection()'. For WinForms bind to 'DbSet.Local.ToBindingList()'. For ASP.NET WebForms bind to 'DbSet.ToList()' or use Model Binding. /// public static string DataBindingToLocalWithIListSource => GetString("DataBindingToLocalWithIListSource"); @@ -2100,6 +2100,22 @@ public static string NoNavigation([CanBeNull] object entityType, [CanBeNull] obj GetString("NoNavigation", nameof(entityType), nameof(foreignKey)), entityType, foreignKey); + /// + /// The property '{property}' cannot be added to type '{entityType}' because the name of the given CLR property or field '{clrName}' is different. + /// + public static string PropertyWrongName([CanBeNull] object property, [CanBeNull] object entityType, [CanBeNull] object clrName) + => string.Format( + GetString("PropertyWrongName", nameof(property), nameof(entityType), nameof(clrName)), + property, entityType, clrName); + + /// + /// The indexed property '{property}' cannot be added to type '{entityType}' because the CLR class contains a member with the same name. + /// + public static string PropertyClashingNonIndexer([CanBeNull] object property, [CanBeNull] object entityType) + => string.Format( + GetString("PropertyClashingNonIndexer", nameof(property), nameof(entityType)), + property, entityType); + /// /// This query would cause multiple evaluation of a subquery because entity '{entityType}' has a composite key. Rewrite your query avoiding the subquery. /// @@ -2878,7 +2894,6 @@ public static EventDefinition LogContextDisposed([NotNull] IDiagnosticsL return (EventDefinition)definition; } - /// /// Compiling query model: {newline}'{queryModel}' /// @@ -2893,7 +2908,6 @@ public static EventDefinition LogCompilingQueryModel([NotNull] I () => new EventDefinition( logger.Options, CoreEventId.QueryModelCompiling, -#pragma warning restore CS0612 // Type or member is obsolete LogLevel.Debug, "CoreEventId.QueryModelCompiling", level => LoggerMessage.Define( diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index cf27e4c9445..e3aea1e67fe 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -166,7 +166,7 @@ Navigation property '{navigation}' on entity of type '{entityType}' cannot be loaded because the entity is not being tracked. Navigation properties can only be loaded for tracked entities. - The entity type '{entityType}' requires a primary key to be defined. If you intended to use a keyless entity type call `HasNoKey()`. + The entity type '{entityType}' requires a primary key to be defined. If you intended to use a keyless entity type call 'HasNoKey()'. The specified key properties {key} are not declared on the entity type '{entityType}'. Ensure key properties are declared on the target entity type. @@ -507,15 +507,15 @@ Compiling query model: {newline}'{queryModel}' - Debug CoreEventId.QueryModelCompiling string string + Obsolete Debug CoreEventId.QueryModelCompiling string string Optimized query model: {newline}'{queryModel}' - Debug CoreEventId.QueryModelOptimized string string + Obsolete Debug CoreEventId.QueryModelOptimized string string Including navigation: '{navigation}' - Debug CoreEventId.NavigationIncluded string + Obsolete Debug CoreEventId.NavigationIncluded string {plan} @@ -642,7 +642,7 @@ Data binding directly to a store query is not supported. Instead populate a DbSet with data, for example by calling Load on the DbSet, and then bind to local data to avoid sending a query to the database each time the databound control iterates the data. For WPF bind to 'DbSet.Local.ToObservableCollection()'. For WinForms bind to 'DbSet.Local.ToBindingList()'. For ASP.NET WebForms bind to 'DbSet.ToList()' or use Model Binding. - Data binding directly to `DbSet.Local` is not supported since it does not provide a stable ordering. For WPF bind to 'DbSet.Local.ToObservableCollection()'. For WinForms bind to 'DbSet.Local.ToBindingList()'. For ASP.NET WebForms bind to 'DbSet.ToList()' or use Model Binding. + Data binding directly to 'DbSet.Local' is not supported since it does not provide a stable ordering. For WPF bind to 'DbSet.Local.ToObservableCollection()'. For WinForms bind to 'DbSet.Local.ToBindingList()'. For ASP.NET WebForms bind to 'DbSet.ToList()' or use Model Binding. The derived type '{derivedType}' cannot have KeyAttribute on property '{property}' since primary key can only be declared on the root type. @@ -764,7 +764,7 @@ The Include operation for navigation '{include}' is unnecessary and was ignored because the navigation is not reachable in the final query results. See https://go.microsoft.com/fwlink/?linkid=850303 for more information. - Warning CoreEventId.IncludeIgnoredWarning string + Obsolete Warning CoreEventId.IncludeIgnoredWarning string Cannot create a relationship between '{newPrincipalEntityType}.{newPrincipalNavigation}' and '{newDependentEntityType}.{newDependentNavigation}', because there already is a relationship between '{existingPrincipalEntityType}.{existingPrincipalNavigation}' and '{existingDependentEntityType}.{existingDependentNavigation}'. Navigation properties can only participate in a single relationship. @@ -810,7 +810,7 @@ Query: '{queryModel}' uses a row limiting operation (Skip/Take) without OrderBy which may lead to unpredictable results. - Warning CoreEventId.RowLimitingOperationWithoutOrderByWarning string + Obsolete Warning CoreEventId.RowLimitingOperationWithoutOrderByWarning string The property '{property}' cannot be removed from entity type '{entityType}' because it is being used in the foreign key {foreignKey} on '{foreignKeyType}'. All containing foreign keys must be removed or redefined before the property can be removed. @@ -820,7 +820,7 @@ Query: '{queryModel}' uses First/FirstOrDefault/Last/LastOrDefault operation without OrderBy and filter which may lead to unpredictable results. - Warning CoreEventId.FirstWithoutOrderByAndFilterWarning string + Obsolete Warning CoreEventId.FirstWithoutOrderByAndFilterWarning string The specified poolSize must be greater than 0. @@ -1168,6 +1168,12 @@ There is no navigation on entity type '{entityType}' associated with the foreign key {foreignKey}. + + The property '{property}' cannot be added to type '{entityType}' because the name of the given CLR property or field '{clrName}' is different. + + + The indexed property '{property}' cannot be added to type '{entityType}' because the CLR class contains a member with the same name. + This query would cause multiple evaluation of a subquery because entity '{entityType}' has a composite key. Rewrite your query avoiding the subquery. diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs index fd525f56e45..3aa4dc96c38 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs @@ -692,7 +692,7 @@ public virtual void Detects_non_notifying_entities(ChangeTrackingStrategy change { var model = CreateConventionlessModelBuilder().Model; var entityType = model.AddEntityType(typeof(NonNotifyingEntity)); - var id = entityType.AddProperty("Id", null); + var id = entityType.AddProperty("Id"); entityType.SetPrimaryKey(id); model.SetChangeTrackingStrategy(changeTrackingStrategy); @@ -709,7 +709,7 @@ public virtual void Detects_changed_only_notifying_entities(ChangeTrackingStrate { var model = CreateConventionlessModelBuilder().Model; var entityType = model.AddEntityType(typeof(ChangedOnlyEntity)); - var id = entityType.AddProperty("Id", null); + var id = entityType.AddProperty("Id"); entityType.SetPrimaryKey(id); model.SetChangeTrackingStrategy(changeTrackingStrategy); @@ -728,7 +728,7 @@ public virtual void Passes_for_fully_notifying_entities(ChangeTrackingStrategy c { var model = CreateConventionlessModelBuilder().Model; var entityType = model.AddEntityType(typeof(FullNotificationEntity)); - var id = entityType.AddProperty("Id", null); + var id = entityType.AddProperty("Id"); entityType.SetPrimaryKey(id); model.SetChangeTrackingStrategy(changeTrackingStrategy); @@ -744,7 +744,7 @@ public virtual void Passes_for_changed_only_entities_with_snapshot_or_changed_on { var model = CreateConventionlessModelBuilder().Model; var entityType = model.AddEntityType(typeof(ChangedOnlyEntity)); - var id = entityType.AddProperty("Id", null); + var id = entityType.AddProperty("Id"); entityType.SetPrimaryKey(id); model.SetChangeTrackingStrategy(changeTrackingStrategy); @@ -757,7 +757,7 @@ public virtual void Passes_for_non_notifying_entities_with_snapshot_tracking() { var model = CreateConventionlessModelBuilder().Model; var entityType = model.AddEntityType(typeof(NonNotifyingEntity)); - var id = entityType.AddProperty("Id", null); + var id = entityType.AddProperty("Id"); entityType.SetPrimaryKey(id); model.SetChangeTrackingStrategy(ChangeTrackingStrategy.Snapshot); diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeBaseTypeTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeBaseTypeTest.cs index 86eb45bbeca..2339e19977f 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeBaseTypeTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeBaseTypeTest.cs @@ -204,7 +204,7 @@ public void Adding_property_throws_when_parent_type_has_property_with_same_name( Assert.Equal( CoreStrings.ConflictingPropertyOrNavigation("G", typeof(B).Name, typeof(A).Name), - Assert.Throws(() => b.AddProperty("G", null)).Message); + Assert.Throws(() => b.AddProperty("G")).Message); } [Fact] @@ -223,7 +223,7 @@ public void Adding_property_throws_when_grandparent_type_has_property_with_same_ Assert.Equal( CoreStrings.ConflictingPropertyOrNavigation("G", typeof(D).Name, typeof(A).Name), - Assert.Throws(() => d.AddProperty("G", null)).Message); + Assert.Throws(() => d.AddProperty("G")).Message); } [Fact] @@ -428,10 +428,10 @@ public void Adding_keys_throws_when_there_is_a_parent_type() Assert.Equal( CoreStrings.DerivedEntityTypeKey(typeof(B).Name, typeof(A).Name), - Assert.Throws(() => b.SetPrimaryKey(b.AddProperty("G", null))).Message); + Assert.Throws(() => b.SetPrimaryKey(b.AddProperty("G"))).Message); Assert.Equal( CoreStrings.DerivedEntityTypeKey(typeof(B).Name, typeof(A).Name), - Assert.Throws(() => b.AddKey(b.AddProperty("E", null))).Message); + Assert.Throws(() => b.AddKey(b.AddProperty("E"))).Message); } [Fact] diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs index 32deac62b73..c794a3467a4 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs @@ -26,39 +26,6 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal { public partial class EntityTypeTest { - private readonly IMutableModel _model = BuildModel(); - - private class A - { - public static readonly PropertyInfo EProperty = typeof(A).GetProperty("E"); - public static readonly PropertyInfo GProperty = typeof(A).GetProperty("G"); - - public string E { get; set; } - public string G { get; set; } - } - - private class B : A - { - public static readonly PropertyInfo FProperty = typeof(B).GetProperty("F"); - public static readonly PropertyInfo HProperty = typeof(B).GetProperty("H"); - - public string F { get; set; } - public string H { get; set; } - } - - private class C : A - { - public static readonly PropertyInfo FProperty = typeof(C).GetProperty("F"); - public static readonly PropertyInfo HProperty = typeof(C).GetProperty("H"); - - public string F { get; set; } - public string H { get; set; } - } - - private class D : C - { - } - [Fact] public void Invalid_filter_expressions_throws() { @@ -381,7 +348,7 @@ public void Can_add_a_key_if_any_properties_are_part_of_derived_foreign_key() var idProperty = baseType.AddProperty(Customer.IdProperty); var fkProperty = baseType.AddProperty("fk", typeof(int)); var key = baseType.AddKey(new[] { idProperty }); - IMutableEntityType entityType = model.AddEntityType(typeof(Customer)); + var entityType = model.AddEntityType(typeof(Customer)); entityType.BaseType = baseType; entityType.AddForeignKey(new[] { fkProperty }, key, entityType); @@ -397,7 +364,7 @@ public void Adding_a_key_with_value_generation_throws_if_any_properties_are_part var fkProperty = baseType.AddProperty("fk", typeof(int)); fkProperty.ValueGenerated = ValueGenerated.OnAdd; var key = baseType.AddKey(new[] { idProperty }); - IMutableEntityType entityType = model.AddEntityType(typeof(Customer)); + var entityType = model.AddEntityType(typeof(Customer)); entityType.BaseType = baseType; entityType.AddForeignKey(new[] { fkProperty }, key, entityType); @@ -556,7 +523,7 @@ public void Can_add_a_foreign_key() } [Fact] - public void Can_add_a_foreign_key_targetting_different_key() + public void Can_add_a_foreign_key_targeting_different_key() { var model = CreateModel(); var customerType = model.AddEntityType(typeof(Customer)); @@ -581,7 +548,7 @@ public void Can_add_a_foreign_key_targetting_different_key() } [Fact] - public void Can_add_a_foreign_key_targetting_different_entity_type() + public void Can_add_a_foreign_key_targeting_different_entity_type() { var model = CreateModel(); var baseType = model.AddEntityType(typeof(BaseType)); @@ -685,7 +652,7 @@ public void Can_add_a_foreign_key_if_any_properties_are_part_of_inherited_key() var idProperty = baseType.AddProperty(Customer.IdProperty); var idProperty2 = baseType.AddProperty("id2", typeof(int)); var key = baseType.AddKey(new[] { idProperty, idProperty2 }); - IMutableEntityType entityType = model.AddEntityType(typeof(Customer)); + var entityType = model.AddEntityType(typeof(Customer)); entityType.BaseType = baseType; var fkProperty = entityType.AddProperty("fk", typeof(int)); @@ -701,7 +668,7 @@ public void Can_add_a_foreign_key_if_any_properties_are_part_of_inherited_key_wi idProperty.ValueGenerated = ValueGenerated.OnAdd; var idProperty2 = baseType.AddProperty("id2", typeof(int)); var key = baseType.AddKey(new[] { idProperty, idProperty2 }); - IMutableEntityType entityType = model.AddEntityType(typeof(Customer)); + var entityType = model.AddEntityType(typeof(Customer)); entityType.BaseType = baseType; var fkProperty = entityType.AddProperty("fk", typeof(int)); @@ -774,22 +741,6 @@ private static IMutableModel BuildModel() return model; } - private IMutableEntityType DependentType => _model.FindEntityType(typeof(DependentEntity)); - - private IMutableEntityType PrincipalType => _model.FindEntityType(typeof(PrincipalEntity)); - - private class PrincipalEntity - { - public int PeeKay { get; set; } - public IEnumerable AnotherNav { get; set; } - } - - private class DependentEntity - { - public PrincipalEntity Navigator { get; set; } - public PrincipalEntity AnotherNav { get; set; } - } - [Fact] public void Can_remove_foreign_keys() { @@ -969,7 +920,7 @@ public void Adding_a_new_navigation_with_a_name_that_conflicts_with_a_property_t var foreignKeyProperty = orderType.AddProperty(Order.CustomerIdProperty); var customerForeignKey = orderType.AddForeignKey(foreignKeyProperty, customerKey, customerType); - orderType.AddProperty("Customer", null); + orderType.AddProperty("Customer"); Assert.Equal( CoreStrings.ConflictingPropertyOrNavigation("Customer", typeof(Order).Name, typeof(Order).Name), @@ -1223,14 +1174,14 @@ public void Can_add_retrieve_and_remove_indexes() var index2 = entityType.AddIndex(new[] { property1, property2 }); - Assert.NotNull(((Index)index1).Builder); - Assert.NotNull(((Index)index2).Builder); + Assert.NotNull(((IConventionIndex)index1).Builder); + Assert.NotNull(((IConventionIndex)index2).Builder); Assert.Equal(2, index2.Properties.Count); Assert.Same(index2, entityType.FindIndex(new[] { property1, property2 })); Assert.Same(property1, index2.Properties[0]); Assert.Same(property2, index2.Properties[1]); Assert.True(property1.IsIndex()); - Assert.Equal(new IIndex[] { index1, index2 }, property1.GetContainingIndexes().ToArray()); + Assert.Equal(new [] { index1, index2 }, property1.GetContainingIndexes().ToArray()); Assert.Equal(2, entityType.GetIndexes().Count()); Assert.Same(index1, entityType.GetIndexes().First()); @@ -1334,7 +1285,7 @@ public void Can_add_new_properties_or_get_existing_properties_using_PropertyInfo Assert.Same(idProperty, entityType.FindProperty("Id")); Assert.False(idProperty.IsShadowProperty()); - var nameProperty = entityType.AddProperty("Name", null); + var nameProperty = entityType.AddProperty("Name"); Assert.False(nameProperty.IsShadowProperty()); Assert.Equal("Name", nameProperty.Name); @@ -1354,7 +1305,7 @@ public void Can_add_new_properties_using_name_of_property_in_base_class() var model = CreateModel(); var entityType = model.AddEntityType(typeof(HiddenField)); - var property = entityType.AddProperty("Raisin", null); + var property = entityType.AddProperty("Raisin"); Assert.False(property.IsShadowProperty()); Assert.Equal("Raisin", property.Name); @@ -1370,7 +1321,7 @@ public void Can_add_new_properties_using_name_of_field_in_base_class() var model = CreateModel(); var entityType = model.AddEntityType(typeof(HiddenField)); - var property = entityType.AddProperty("_date", null); + var property = entityType.AddProperty("_date"); Assert.False(property.IsShadowProperty()); Assert.Equal("_date", property.Name); @@ -1423,8 +1374,7 @@ public void AddProperty_throws_if_no_clr_property_or_field() Assert.Equal( CoreStrings.NoPropertyType("_foo", nameof(Customer)), Assert.Throws( - () => - entityType.AddProperty("_foo", null)).Message); + () => entityType.AddProperty("_foo")).Message); } [Fact] @@ -1440,6 +1390,19 @@ public void AddProperty_throws_if_clr_type_does_not_match() entityType.AddProperty(nameof(Customer.Name), typeof(int))).Message); } + [Fact] + public void AddProperty_throws_if_name_does_not_match() + { + var entityType = CreateModel().AddEntityType(typeof(Customer)); + + Assert.Equal( + CoreStrings.PropertyWrongName( + nameof(Customer.Id), nameof(Customer), nameof(Customer.Name)), + Assert.Throws( + () => + entityType.AddProperty(nameof(Customer.Id), typeof(int), Customer.NameProperty)).Message); + } + [Fact] public void AddProperty_ignores_clr_type_if_implicit() { @@ -1450,6 +1413,38 @@ public void AddProperty_ignores_clr_type_if_implicit() Assert.Equal(typeof(string), property.ClrType); } + [Fact] + public void AddIndexedProperty_adds_indexed_property() + { + var entityType = CreateModel().AddEntityType(typeof(Customer)); + + var property = entityType.AddIndexedProperty("A", typeof(int)); + + Assert.True(property.IsIndexedProperty()); + Assert.Equal("A", property.Name); + Assert.Equal(typeof(int), property.ClrType); + } + + [Fact] + public void AddIndexedProperty_throws_when_no_indexer() + { + var entityType = CreateModel().AddEntityType(typeof(Order)); + + Assert.Equal( + CoreStrings.NoIndexer(entityType.DisplayName()), + Assert.Throws(() => entityType.AddIndexedProperty("A", typeof(int))).Message); + } + + [Fact] + public void AddIndexedProperty_throws_when_clashing_with_other_property() + { + var entityType = CreateModel().AddEntityType(typeof(Customer)); + + Assert.Equal( + CoreStrings.PropertyClashingNonIndexer("Name", entityType.DisplayName()), + Assert.Throws(() => entityType.AddIndexedProperty("Name", typeof(int))).Message); + } + [Fact] public void Cannot_remove_property_when_used_by_primary_key() { @@ -1623,7 +1618,7 @@ public void Adding_a_new_property_with_a_name_that_already_exists_throws() Assert.Equal( CoreStrings.ConflictingPropertyOrNavigation("Id", typeof(Customer).Name, typeof(Customer).Name), - Assert.Throws(() => entityType.AddProperty("Id", null)).Message); + Assert.Throws(() => entityType.AddProperty("Id")).Message); } [Fact] @@ -1641,7 +1636,7 @@ public void Adding_a_new_property_with_a_name_that_conflicts_with_a_navigation_t Assert.Equal( CoreStrings.ConflictingPropertyOrNavigation("Customer", typeof(Order).Name, typeof(Order).Name), - Assert.Throws(() => orderType.AddProperty("Customer", null)).Message); + Assert.Throws(() => orderType.AddProperty("Customer")).Message); } [Fact] @@ -2085,28 +2080,6 @@ protected internal override void OnModelCreating(ModelBuilder modelBuilder) } } - private class Level1 - { - public int Id { get; set; } - public int Prop1 { get; set; } - public Level1 Level1Reference { get; set; } - public ICollection Level1Collection { get; set; } - } - - private class Level2 : Level1 - { - public int Prop2 { get; set; } - public Level2 Level2Reference { get; set; } - public ICollection Level2Collection { get; set; } - } - - private class Level3 : Level2 - { - public int Prop3 { get; set; } - public Level3 Level3Reference { get; set; } - public ICollection Level3Collection { get; set; } - } - [Fact] public void Indexes_are_ordered_by_property_count_then_property_names() { @@ -2127,7 +2100,7 @@ public void Indexes_are_ordered_by_property_count_then_property_names() [Fact] public void Adding_inheritance_to_weak_entity_types_throws() { - IMutableModel model = CreateModel(); + var model = CreateModel(); var customerType = model.AddEntityType(typeof(Customer)); var baseType = model.AddEntityType(typeof(BaseType), nameof(Customer.Orders), customerType); var orderType = model.AddEntityType(typeof(Order), nameof(Customer.Orders), customerType); @@ -2146,7 +2119,7 @@ public void Adding_inheritance_to_weak_entity_types_throws() [Fact] public void Adding_non_delegated_inheritance_to_delegated_identity_definition_entity_types_throws() { - IMutableModel model = CreateModel(); + var model = CreateModel(); var customerType = model.AddEntityType(typeof(Customer)); var baseType = model.AddEntityType(typeof(BaseType)); var orderType = model.AddEntityType(typeof(Order), nameof(Customer.Orders), customerType); @@ -2392,6 +2365,77 @@ public void Collections_dont_have_relationship_indexes_when_full_notifications_w Assert.Equal(3, entityType.RelationshipPropertyCount()); } + private readonly IMutableModel _model = BuildModel(); + + private IMutableEntityType DependentType => _model.FindEntityType(typeof(DependentEntity)); + + private IMutableEntityType PrincipalType => _model.FindEntityType(typeof(PrincipalEntity)); + + private class PrincipalEntity + { + public int PeeKay { get; set; } + public IEnumerable AnotherNav { get; set; } + } + + private class DependentEntity + { + public PrincipalEntity Navigator { get; set; } + public PrincipalEntity AnotherNav { get; set; } + } + + private class A + { + public static readonly PropertyInfo EProperty = typeof(A).GetProperty("E"); + public static readonly PropertyInfo GProperty = typeof(A).GetProperty("G"); + + public string E { get; set; } + public string G { get; set; } + } + + private class B : A + { + public static readonly PropertyInfo FProperty = typeof(B).GetProperty("F"); + public static readonly PropertyInfo HProperty = typeof(B).GetProperty("H"); + + public string F { get; set; } + public string H { get; set; } + } + + private class C : A + { + public static readonly PropertyInfo FProperty = typeof(C).GetProperty("F"); + public static readonly PropertyInfo HProperty = typeof(C).GetProperty("H"); + + public string F { get; set; } + public string H { get; set; } + } + + private class D : C + { + } + + private class Level1 + { + public int Id { get; set; } + public int Prop1 { get; set; } + public Level1 Level1Reference { get; set; } + public ICollection Level1Collection { get; set; } + } + + private class Level2 : Level1 + { + public int Prop2 { get; set; } + public Level2 Level2Reference { get; set; } + public ICollection Level2Collection { get; set; } + } + + private class Level3 : Level2 + { + public int Prop3 { get; set; } + public Level3 Level3Reference { get; set; } + public ICollection Level3Collection { get; set; } + } + private class BaseType { public int Id { get; set; } @@ -2409,6 +2453,8 @@ private class Customer : BaseType public Guid Unique { get; set; } public string Name { get; set; } public string Mane { get; set; } + public object this[string name] => null; + public ICollection Orders { get; set; } public ICollection MoreOrders { get; set; } diff --git a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs index e7cfee639c2..1b58834ff91 100644 --- a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs @@ -1403,12 +1403,11 @@ public void Property_returns_same_instance_if_type_matches() Assert.Same( propertyBuilder, entityBuilder.Property( - typeof(int), Order.IdProperty.Name, - ConfigurationSource.DataAnnotation, typeConfigurationSource: ConfigurationSource.DataAnnotation)); + typeof(int), Order.IdProperty.Name, typeConfigurationSource: ConfigurationSource.DataAnnotation, configurationSource: ConfigurationSource.DataAnnotation)); Assert.Same( propertyBuilder, - entityBuilder.Property(typeof(int), Order.IdProperty.Name, ConfigurationSource.Convention, typeConfigurationSource: null)); + entityBuilder.Property(typeof(int), Order.IdProperty.Name, typeConfigurationSource: null, configurationSource: ConfigurationSource.Convention)); Assert.Same( propertyBuilder, @@ -1417,8 +1416,7 @@ public void Property_returns_same_instance_if_type_matches() Assert.Null( entityBuilder.Property( - typeof(string), Order.IdProperty.Name, - ConfigurationSource.Convention, typeConfigurationSource: ConfigurationSource.Convention)); + typeof(string), Order.IdProperty.Name, typeConfigurationSource: ConfigurationSource.Convention, configurationSource: ConfigurationSource.Convention)); Assert.Equal(new[] { propertyBuilder.Metadata }, entityBuilder.GetActualProperties(new[] { propertyBuilder.Metadata }, null)); } diff --git a/test/EFCore.Tests/Metadata/Internal/PropertyAccessorsFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/PropertyAccessorsFactoryTest.cs index 052156df895..6053effdb04 100644 --- a/test/EFCore.Tests/Metadata/Internal/PropertyAccessorsFactoryTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/PropertyAccessorsFactoryTest.cs @@ -19,7 +19,7 @@ public void Can_use_PropertyAccessorsFactory_on_indexed_property() { IMutableModel model = new Model(); var entityType = model.AddEntityType(typeof(IndexedClass)); - var id = entityType.AddProperty("Id", typeof(int)); + entityType.AddProperty("Id", typeof(int)); var propertyA = entityType.AddIndexedProperty("PropertyA", typeof(string)); var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(model); @@ -36,7 +36,7 @@ public void Can_use_PropertyAccessorsFactory_on_indexed_property() Assert.Equal("ValueA", ((Func)propertyAccessors.RelationshipSnapshotGetter)(entry)); var valueBuffer = new ValueBuffer(new object[] { 1, "ValueA" }); - Assert.Equal("ValueA", ((Func)propertyAccessors.ValueBufferGetter)(valueBuffer)); + Assert.Equal("ValueA", propertyAccessors.ValueBufferGetter(valueBuffer)); } [Fact] diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilder.Other.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilder.Other.cs index 70d45c1c32f..d4de161dc6d 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilder.Other.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilder.Other.cs @@ -110,12 +110,11 @@ public virtual void HasForeignKey_infers_type_for_shadow_property_when_not_speci b.Entity().HasKey(c => c.Key); })) { - var model = (Model)context.Model; - Assert.Equal( - ConfigurationSource.Convention, - model.FindEntityType(typeof(ComplexCaseChild13108)) - .GetProperties().Where(p => p.Name == "ParentKey").Single() - .GetTypeConfigurationSource()); + var model = (IConventionModel)context.Model; + var property = model + .FindEntityType(typeof(ComplexCaseChild13108)).GetProperties().Single(p => p.Name == "ParentKey"); + Assert.Equal(typeof(int), property.ClrType); + Assert.Equal(ConfigurationSource.Explicit, property.GetTypeConfigurationSource()); } } diff --git a/tools/Resources.tt b/tools/Resources.tt index 7d2817f5ecd..6f32658f1f1 100644 --- a/tools/Resources.tt +++ b/tools/Resources.tt @@ -180,6 +180,12 @@ namespace <#= model.Namespace.EndsWith(".Internal") ? model.Namespace : (model.N else { var genericTypes = resource.Types.Any() ? ("<" + List(resource.Types) + ">") : ""; + if (resource.Obsolete) + { +#> + [Obsolete] +<# + } #> public static EventDefinition<#= genericTypes #> <#= resource.Name #>([NotNull] IDiagnosticsLogger logger) { @@ -312,15 +318,23 @@ namespace <#= model.Namespace.EndsWith(".Internal") ? model.Namespace : (model.N .ToList(); var eventInfo = node.Comment.Split(' '); - Level = eventInfo.FirstOrDefault() ?? "BadLevel"; - EventId = eventInfo.Skip(1).FirstOrDefault() ?? "BadEventId"; - Types = eventInfo.Skip(2).ToList(); + var argumentsRead = 0; + if (eventInfo.FirstOrDefault() == "Obsolete") + { + Obsolete = true; + argumentsRead++; + } + + Level = eventInfo.Skip(argumentsRead++).FirstOrDefault() ?? "BadLevel"; + EventId = eventInfo.Skip(argumentsRead++).FirstOrDefault() ?? "BadEventId"; + Types = eventInfo.Skip(argumentsRead++).ToList(); } public string Name { get; } public string Value { get; } public string EventId { get; } public string Level { get; } + public bool Obsolete { get; } public bool ForLogging => Name.StartsWith("Log"); public IEnumerable Parameters { get; } public IEnumerable Types { get; }