Skip to content

Commit

Permalink
Metadata: Add Getter/Setter support for indexed properties
Browse files Browse the repository at this point in the history
Part of #13610
  • Loading branch information
smitpatel committed Jan 1, 2020
1 parent 347be2d commit ddf496f
Show file tree
Hide file tree
Showing 18 changed files with 300 additions and 68 deletions.
31 changes: 30 additions & 1 deletion src/EFCore/Extensions/ConventionEntityTypeExtensions.cs
Expand Up @@ -7,6 +7,8 @@
using System.Linq.Expressions;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
Expand Down Expand Up @@ -433,7 +435,9 @@ public static IConventionProperty FindProperty([NotNull] this IConventionEntityT
Check.NotNull(entityType, nameof(entityType));
Check.NotNull(memberInfo, nameof(memberInfo));

return entityType.FindProperty(memberInfo.GetSimpleMemberName());
return (memberInfo as PropertyInfo)?.IsIndexerProperty() == true
? null
: entityType.FindProperty(memberInfo.GetSimpleMemberName());
}

/// <summary>
Expand Down Expand Up @@ -504,6 +508,31 @@ public static IConventionProperty FindDeclaredProperty([NotNull] this IConventio
bool setTypeConfigurationSource = true, bool fromDataAnnotation = false)
=> entityType.AddProperty(name, propertyType, null, setTypeConfigurationSource, fromDataAnnotation);

/// <summary>
/// Adds an indexed property to this entity type.
/// </summary>
/// <param name="entityType"> The entity type to add the property to. </param>
/// <param name="name"> The name of the property to add. </param>
/// <param name="propertyType"> The type of value the property will hold. </param>
/// <param name="setTypeConfigurationSource"> Indicates whether the type configuration source should be set. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> The newly created property. </returns>
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));

var indexerPropertyInfo = entityType.FindIndexerPropertyInfo();
if (indexerPropertyInfo == null)
{
throw new InvalidOperationException(
CoreStrings.NonIndexerEntityType(name, entityType.DisplayName(), typeof(string).ShortDisplayName()));
}

return entityType.AddProperty(name, propertyType, indexerPropertyInfo, setTypeConfigurationSource, fromDataAnnotation);
}

/// <summary>
/// Gets the index defined on the given property. Returns null if no index is defined.
/// </summary>
Expand Down
4 changes: 3 additions & 1 deletion src/EFCore/Extensions/EntityTypeExtensions.cs
Expand Up @@ -539,7 +539,9 @@ public static IProperty FindProperty([NotNull] this IEntityType entityType, [Not
Check.NotNull(entityType, nameof(entityType));
Check.NotNull(memberInfo, nameof(memberInfo));

return entityType.FindProperty(memberInfo.GetSimpleMemberName());
return (memberInfo as PropertyInfo)?.IsIndexerProperty() == true
? null
: entityType.FindProperty(memberInfo.GetSimpleMemberName());
}

/// <summary>
Expand Down
26 changes: 25 additions & 1 deletion src/EFCore/Extensions/MutableEntityTypeExtensions.cs
Expand Up @@ -6,6 +6,8 @@
using System.Linq.Expressions;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
Expand Down Expand Up @@ -402,7 +404,7 @@ public static IMutableProperty FindProperty([NotNull] this IMutableEntityType en
Check.NotNull(entityType, nameof(entityType));
Check.NotNull(propertyInfo, nameof(propertyInfo));

return entityType.FindProperty(propertyInfo.GetSimpleMemberName());
return propertyInfo.IsIndexerProperty() ? null : entityType.FindProperty(propertyInfo.GetSimpleMemberName());
}

/// <summary>
Expand Down Expand Up @@ -463,6 +465,28 @@ public static IMutableProperty FindDeclaredProperty([NotNull] this IMutableEntit
[NotNull] this IMutableEntityType entityType, [NotNull] string name, [NotNull] Type propertyType)
=> entityType.AddProperty(name, propertyType, null);

/// <summary>
/// Adds an indexed property to this entity type.
/// </summary>
/// <param name="entityType"> The entity type to add the property to. </param>
/// <param name="name"> The name of the property to add. </param>
/// <param name="propertyType"> The type of value the property will hold. </param>
/// <returns> The newly created property. </returns>
public static IMutableProperty AddIndexedProperty(
[NotNull] this IMutableEntityType entityType, [NotNull] string name, [NotNull] Type propertyType)
{
Check.NotNull(entityType, nameof(entityType));

var indexerPropertyInfo = entityType.FindIndexerPropertyInfo();
if (indexerPropertyInfo == null)
{
throw new InvalidOperationException(
CoreStrings.NonIndexerEntityType(name, entityType.DisplayName(), typeof(string).ShortDisplayName()));
}

return entityType.AddProperty(name, propertyType, indexerPropertyInfo);
}

/// <summary>
/// Gets the index defined on the given property. Returns null if no index is defined.
/// </summary>
Expand Down
12 changes: 12 additions & 0 deletions src/EFCore/Extensions/PropertyBaseExtensions.cs
Expand Up @@ -82,6 +82,18 @@ public static string GetFieldName([NotNull] this IPropertyBase propertyBase)
public static bool IsShadowProperty([NotNull] this IPropertyBase property)
=> Check.NotNull(property, nameof(property)).GetIdentifyingMemberInfo() == null;

/// <summary>
/// Gets a value indicating whether this is an indexer property. An indexer property is one that is accessed through
/// an indexer on the entity class.
/// </summary>
/// <param name="property"> The property to check. </param>
/// <returns>
/// <c>True</c> if the property is an indexer property, otherwise <c>false</c>.
/// </returns>
public static bool IsIndexerProperty([NotNull] this IPropertyBase property)
=> Check.NotNull(property, nameof(property)).GetIdentifyingMemberInfo() is PropertyInfo propertyInfo
&& propertyInfo == property.DeclaringType.FindIndexerPropertyInfo();

/// <summary>
/// <para>
/// Gets the <see cref="PropertyAccessMode" /> being used for this property.
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore/Infrastructure/ModelValidator.cs
Expand Up @@ -783,7 +783,7 @@ private bool Contains(IForeignKey inheritedFk, IForeignKey derivedFk)
.GetDeclaredProperties()
.Cast<IPropertyBase>()
.Concat(entityType.GetDeclaredNavigations())
.Where(p => !p.IsShadowProperty()));
.Where(p => !p.IsShadowProperty() && !p.IsIndexerProperty()));

var constructorBinding = (InstantiationBinding)entityType[CoreAnnotationNames.ConstructorBinding];

Expand Down
12 changes: 10 additions & 2 deletions src/EFCore/Metadata/Internal/ClrPropertyGetterFactory.cs
Expand Up @@ -40,7 +40,7 @@ public override IClrPropertyGetter Create(IPropertyBase property)
Expression readExpression;
if (memberInfo.DeclaringType.IsAssignableFrom(typeof(TEntity)))
{
readExpression = Expression.MakeMemberAccess(entityParameter, memberInfo);
readExpression = CreateMemberAccess(entityParameter);
}
else
{
Expand All @@ -57,7 +57,7 @@ public override IClrPropertyGetter Create(IPropertyBase property)
Expression.Condition(
Expression.ReferenceEqual(converted, Expression.Constant(null)),
Expression.Default(memberInfo.GetMemberType()),
Expression.MakeMemberAccess(converted, memberInfo))
CreateMemberAccess(converted))
});
}

Expand Down Expand Up @@ -101,6 +101,14 @@ public override IClrPropertyGetter Create(IPropertyBase property)
return new ClrPropertyGetter<TEntity, TValue>(
Expression.Lambda<Func<TEntity, TValue>>(readExpression, entityParameter).Compile(),
Expression.Lambda<Func<TEntity, bool>>(hasDefaultValueExpression, entityParameter).Compile());

Expression CreateMemberAccess(Expression parameter)
{
return propertyBase?.IsIndexerProperty() == true
? Expression.MakeIndex(
entityParameter, (PropertyInfo)memberInfo, new List<Expression>() { Expression.Constant(propertyBase.Name) })
: (Expression)Expression.MakeMemberAccess(parameter, memberInfo);
}
}
}
}
16 changes: 12 additions & 4 deletions src/EFCore/Metadata/Internal/ClrPropertySetterFactory.cs
Expand Up @@ -45,8 +45,7 @@ public override IClrPropertySetter Create(IPropertyBase property)
Expression writeExpression;
if (memberInfo.DeclaringType.IsAssignableFrom(typeof(TEntity)))
{
writeExpression = Expression.MakeMemberAccess(entityParameter, memberInfo)
.Assign(convertedParameter);
writeExpression = CreateMemberAssignment(entityParameter);
}
else
{
Expand All @@ -62,8 +61,7 @@ public override IClrPropertySetter Create(IPropertyBase property)
Expression.TypeAs(entityParameter, memberInfo.DeclaringType)),
Expression.IfThen(
Expression.ReferenceNotEqual(converted, Expression.Constant(null)),
Expression.MakeMemberAccess(converted, memberInfo)
.Assign(convertedParameter))
CreateMemberAssignment(converted))
});
}

Expand All @@ -78,6 +76,16 @@ public override IClrPropertySetter Create(IPropertyBase property)
&& propertyType.UnwrapNullableType().IsEnum
? new NullableEnumClrPropertySetter<TEntity, TValue, TNonNullableEnumValue>(setter)
: (IClrPropertySetter)new ClrPropertySetter<TEntity, TValue>(setter);

Expression CreateMemberAssignment(Expression parameter)
{
return propertyBase?.IsIndexerProperty() == true
? Expression.Assign(
Expression.MakeIndex(
entityParameter, (PropertyInfo)memberInfo, new List<Expression>() { Expression.Constant(propertyBase.Name) }),
convertedParameter)
: Expression.MakeMemberAccess(parameter, memberInfo).Assign(convertedParameter);
}
}
}
}
4 changes: 2 additions & 2 deletions src/EFCore/Metadata/Internal/EntityType.cs
Expand Up @@ -1599,7 +1599,7 @@ private Type ValidateClrMember(string name, MemberInfo memberInfo, bool throwOnN

if (name != memberInfo.GetSimpleMemberName())
{
if ((memberInfo as PropertyInfo)?.IsEFIndexerProperty() != true)
if (memberInfo != FindIndexerPropertyInfo())
{
if (throwOnNameMismatch)
{
Expand Down Expand Up @@ -2066,7 +2066,7 @@ public virtual Index RemoveIndex([NotNull] Index index)

if (memberInfo != null
&& propertyType != memberInfo.GetMemberType()
&& (memberInfo as PropertyInfo)?.IsEFIndexerProperty() != true)
&& memberInfo != FindIndexerPropertyInfo())
{
if (typeConfigurationSource != null)
{
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
Expand Up @@ -682,7 +682,7 @@ public virtual bool CanAddProperty([NotNull] Type propertyType, [NotNull] string
var memberInfo = Metadata.ClrType.GetMembersInHierarchy(name).FirstOrDefault();
if (memberInfo != null
&& propertyType != memberInfo.GetMemberType()
&& (memberInfo as PropertyInfo)?.IsEFIndexerProperty() != true
&& memberInfo != Metadata.FindIndexerPropertyInfo()
&& typeConfigurationSource != null)
{
return false;
Expand Down
6 changes: 4 additions & 2 deletions src/EFCore/Metadata/Internal/PropertyBase.cs
Expand Up @@ -80,8 +80,10 @@ public abstract class PropertyBase : ConventionAnnotatable, IMutablePropertyBase
/// </summary>
public virtual FieldInfo FieldInfo
{
[DebuggerStepThrough] get => _fieldInfo;
[DebuggerStepThrough] set => SetField(value, ConfigurationSource.Explicit);
[DebuggerStepThrough]
get => _fieldInfo;
[DebuggerStepThrough]
set => SetField(value, ConfigurationSource.Explicit);
}

/// <summary>
Expand Down
35 changes: 24 additions & 11 deletions src/EFCore/Metadata/Internal/TypeBase.cs
Expand Up @@ -25,6 +25,8 @@ public abstract class TypeBase : ConventionAnnotatable, IMutableTypeBase, IConve
private readonly Dictionary<string, ConfigurationSource> _ignoredMembers
= new Dictionary<string, ConfigurationSource>(StringComparer.Ordinal);

private bool _indexerPropertyInitialized;
private PropertyInfo _indexerPropertyInfo;
private Dictionary<string, PropertyInfo> _runtimeProperties;
private Dictionary<string, FieldInfo> _runtimeFields;

Expand Down Expand Up @@ -174,32 +176,43 @@ public virtual void UpdateConfigurationSource(ConfigurationSource configurationS
/// 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.
/// </summary>
public virtual void SetPropertyAccessMode(
PropertyAccessMode? propertyAccessMode, ConfigurationSource configurationSource)
=> this.SetOrRemoveAnnotation(CoreAnnotationNames.PropertyAccessMode, propertyAccessMode, configurationSource);
public virtual PropertyInfo FindIndexerPropertyInfo()
{
if (ClrType == null)
{
return null;
}

if (!_indexerPropertyInitialized)
{
var indexerPropertyInfo = GetRuntimeProperties().Values.FirstOrDefault(pi => pi.IsIndexerProperty());

Interlocked.CompareExchange(ref _indexerPropertyInfo, indexerPropertyInfo, null);
_indexerPropertyInitialized = true;
}

return _indexerPropertyInfo;
}

/// <summary>
/// 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.
/// </summary>
public virtual void SetNavigationAccessMode(
public virtual void SetPropertyAccessMode(
PropertyAccessMode? propertyAccessMode, ConfigurationSource configurationSource)
=> this.SetOrRemoveAnnotation(CoreAnnotationNames.NavigationAccessMode, propertyAccessMode, configurationSource);
=> this.SetOrRemoveAnnotation(CoreAnnotationNames.PropertyAccessMode, propertyAccessMode, configurationSource);

/// <summary>
/// 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.
/// </summary>
public virtual void ClearCaches()
{
_runtimeProperties = null;
_runtimeFields = null;
Thread.MemoryBarrier();
}
public virtual void SetNavigationAccessMode(
PropertyAccessMode? propertyAccessMode, ConfigurationSource configurationSource)
=> this.SetOrRemoveAnnotation(CoreAnnotationNames.NavigationAccessMode, propertyAccessMode, configurationSource);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
25 changes: 12 additions & 13 deletions src/EFCore/Metadata/Internal/TypeBaseExtensions.cs
Expand Up @@ -51,20 +51,19 @@ public static bool HasClrType([NotNull] this ITypeBase type)
/// 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.
/// </summary>
public static MemberInfo FindClrMember([NotNull] this TypeBase type, [NotNull] string name)
{
if (type.GetRuntimeProperties().TryGetValue(name, out var property))
{
return property;
}

if (type.GetRuntimeFields().TryGetValue(name, out var field))
{
return field;
}
public static PropertyInfo FindIndexerPropertyInfo([NotNull] this ITypeBase type)
=> (type as TypeBase).FindIndexerPropertyInfo();

return null;
}
/// <summary>
/// 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.
/// </summary>
public static MemberInfo FindClrMember([NotNull] this TypeBase type, [NotNull] string name)
=> type.GetRuntimeProperties().TryGetValue(name, out var property)
? property
: (MemberInfo)(type.GetRuntimeFields().TryGetValue(name, out var field) ? field : null);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
10 changes: 9 additions & 1 deletion src/EFCore/Properties/CoreStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit ddf496f

Please sign in to comment.