Skip to content

Commit

Permalink
Query: Add tests for indexer properties
Browse files Browse the repository at this point in the history
Resolves #19495
Resolves #19458

Changes
- Sort indexer properties along with shadow properties during model diff for column ordering in a table
- Updated documentation of IndexedProperty
- Add IndexedProperty fluent API to owned navigation builder
- Re-attach indexer property correctly after detach
  • Loading branch information
smitpatel committed Feb 6, 2020
1 parent dfd3192 commit b2c7058
Show file tree
Hide file tree
Showing 18 changed files with 1,724 additions and 554 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,8 @@ private static IEnumerable<IProperty> GetSortedProperties(IEntityType entityType
foreach (var property in entityType.GetDeclaredProperties())
{
var clrProperty = property.PropertyInfo;
if (clrProperty == null)
if (clrProperty == null
|| clrProperty.IsIndexerProperty())
{
if (property.IsPrimaryKey())
{
Expand Down
9 changes: 8 additions & 1 deletion src/EFCore/Metadata/Builders/EntityTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ public virtual PropertyBuilder Property([NotNull] Type propertyType, [NotNull] s
/// Returns an object that can be used to configure a property of the entity type.
/// If no property with the given name exists, then a new property will be added.
/// </para>
/// <para>
/// Indexed properties are stored in the entity using
/// <see href="https://docs.microsoft.com/dotnet/csharp/programming-guide/indexers/">an indexer</see>
/// supplying the provided property name.
/// </para>
/// </summary>
/// <typeparam name="TProperty"> The type of the property to be configured. </typeparam>
/// <param name="propertyName"> The name of the property to be configured. </param>
Expand All @@ -201,7 +206,9 @@ public virtual PropertyBuilder<TProperty> IndexedProperty<TProperty>([NotNull] s
/// If no property with the given name exists, then a new property will be added.
/// </para>
/// <para>
/// Indexed properties are stored in the entity using an indexer supplying the provided property name.
/// Indexed properties are stored in the entity using
/// <see href="https://docs.microsoft.com/dotnet/csharp/programming-guide/indexers/">an indexer</see>
/// supplying the provided property name.
/// </para>
/// </summary>
/// <param name="propertyType"> The type of the property to be configured. </param>
Expand Down
40 changes: 40 additions & 0 deletions src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,46 @@ public virtual PropertyBuilder Property([NotNull] Type propertyType, [NotNull] s
Check.NotNull(propertyType, nameof(propertyType)),
Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit).Metadata);

/// <summary>
/// <para>
/// Returns an object that can be used to configure a property of the entity type.
/// If no property with the given name exists, then a new property will be added.
/// </para>
/// <para>
/// Indexed properties are stored in the entity using
/// <see href="https://docs.microsoft.com/dotnet/csharp/programming-guide/indexers/">an indexer</see>
/// supplying the provided property name.
/// </para>
/// </summary>
/// <typeparam name="TProperty"> The type of the property to be configured. </typeparam>
/// <param name="propertyName"> The name of the property to be configured. </param>
/// <returns> An object that can be used to configure the property. </returns>
public virtual PropertyBuilder<TProperty> IndexedProperty<TProperty>([NotNull] string propertyName)
=> new PropertyBuilder<TProperty>(
DependentEntityType.Builder.IndexedProperty(
typeof(TProperty),
Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit).Metadata);

/// <summary>
/// <para>
/// Returns an object that can be used to configure a property of the entity type.
/// If no property with the given name exists, then a new property will be added.
/// </para>
/// <para>
/// Indexed properties are stored in the entity using
/// <see href="https://docs.microsoft.com/dotnet/csharp/programming-guide/indexers/">an indexer</see>
/// supplying the provided property name.
/// </para>
/// </summary>
/// <param name="propertyType"> The type of the property to be configured. </param>
/// <param name="propertyName"> The name of the property to be configured. </param>
/// <returns> An object that can be used to configure the property. </returns>
public virtual PropertyBuilder IndexedProperty([NotNull] Type propertyType, [NotNull] string propertyName)
=> new PropertyBuilder(
DependentEntityType.Builder.IndexedProperty(
Check.NotNull(propertyType, nameof(propertyType)),
Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit).Metadata);

/// <summary>
/// Excludes the given property from the entity type. This method is typically used to remove properties
/// or navigations from the owned entity type that were added by convention.
Expand Down
119 changes: 87 additions & 32 deletions src/EFCore/Metadata/Internal/EntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2607,45 +2607,93 @@ public virtual IEnumerable<IDictionary<string, object>> GetSeedData(bool provide
var data = new List<Dictionary<string, object>>();
var valueConverters = new Dictionary<string, ValueConverter>(StringComparer.Ordinal);
var properties = this.GetPropertiesAndNavigations().ToDictionary(p => p.Name);
var indexerPropertyInfo = FindIndexerPropertyInfo();
foreach (var rawSeed in _data)
{
var seed = new Dictionary<string, object>(StringComparer.Ordinal);
data.Add(seed);
var type = rawSeed.GetType();
foreach (var memberInfo in type.GetMembersInHierarchy())

if (type == ClrType)
{
if (!properties.TryGetValue(memberInfo.GetSimpleMemberName(), out var propertyBase))
// non-anonymous case
foreach (var propertyBase in properties.Values)
{
continue;
}
ValueConverter valueConverter = null;
if (providerValues
&& !valueConverters.TryGetValue(propertyBase.Name, out valueConverter))
{
if (propertyBase is IProperty property)
{
valueConverter = property.FindTypeMapping()?.Converter
?? property.GetValueConverter();
}

ValueConverter valueConverter = null;
if (providerValues
&& !valueConverters.TryGetValue(memberInfo.Name, out valueConverter))
{
if (propertyBase is IProperty property)
valueConverters[propertyBase.Name] = valueConverter;
}

object value = null;
switch (propertyBase.GetIdentifyingMemberInfo())
{
valueConverter = property.FindTypeMapping()?.Converter
?? property.GetValueConverter();
case PropertyInfo propertyInfo:
if (propertyInfo == indexerPropertyInfo)
{
// indexer property
try
{
value = propertyInfo.GetValue(rawSeed, new[] { propertyBase.Name });
}
catch (Exception)
{
// Swallow if the property value is not set on the seed data
}
}
else
{
value = propertyInfo.GetValue(rawSeed);
}

break;
case FieldInfo fieldInfo:
value = fieldInfo.GetValue(rawSeed);
break;
}

valueConverters[memberInfo.Name] = valueConverter;
seed[propertyBase.Name] = valueConverter == null
? value
: valueConverter.ConvertToProvider(value);
}

object value = null;
switch (memberInfo)
}
else
{
// anonymous case
foreach (var memberInfo in type.GetMembersInHierarchy())
{
case PropertyInfo propertyInfo:
value = propertyInfo.GetValue(rawSeed);
break;
case FieldInfo fieldInfo:
value = fieldInfo.GetValue(rawSeed);
break;
}
if (!properties.TryGetValue(memberInfo.GetSimpleMemberName(), out var propertyBase))
{
continue;
}

ValueConverter valueConverter = null;
if (providerValues
&& !valueConverters.TryGetValue(propertyBase.Name, out valueConverter))
{
if (propertyBase is IProperty property)
{
valueConverter = property.FindTypeMapping()?.Converter
?? property.GetValueConverter();
}

seed[memberInfo.Name] = valueConverter == null
? value
: valueConverter.ConvertToProvider(value);
valueConverters[propertyBase.Name] = valueConverter;
}

// All memberInfos are PropertyInfo in anonymous type
var value = ((PropertyInfo)memberInfo).GetValue(rawSeed);

seed[propertyBase.Name] = valueConverter == null
? value
: valueConverter.ConvertToProvider(value);
}
}
}

Expand Down Expand Up @@ -2865,7 +2913,8 @@ IConventionEntityTypeBuilder IConventionEntityType.Builder
/// </summary>
IModel ITypeBase.Model
{
[DebuggerStepThrough] get => Model;
[DebuggerStepThrough]
get => Model;
}

/// <summary>
Expand All @@ -2876,7 +2925,8 @@ IModel ITypeBase.Model
/// </summary>
IMutableModel IMutableTypeBase.Model
{
[DebuggerStepThrough] get => Model;
[DebuggerStepThrough]
get => Model;
}

/// <summary>
Expand All @@ -2887,7 +2937,8 @@ IMutableModel IMutableTypeBase.Model
/// </summary>
IMutableModel IMutableEntityType.Model
{
[DebuggerStepThrough] get => Model;
[DebuggerStepThrough]
get => Model;
}

/// <summary>
Expand All @@ -2910,7 +2961,8 @@ IConventionModel IConventionEntityType.Model
/// </summary>
IEntityType IEntityType.BaseType
{
[DebuggerStepThrough] get => _baseType;
[DebuggerStepThrough]
get => _baseType;
}

/// <summary>
Expand Down Expand Up @@ -2945,7 +2997,8 @@ IConventionEntityType IConventionEntityType.BaseType
/// </summary>
IEntityType IEntityType.DefiningEntityType
{
[DebuggerStepThrough] get => DefiningEntityType;
[DebuggerStepThrough]
get => DefiningEntityType;
}

/// <summary>
Expand All @@ -2956,7 +3009,8 @@ IEntityType IEntityType.DefiningEntityType
/// </summary>
IMutableEntityType IMutableEntityType.DefiningEntityType
{
[DebuggerStepThrough] get => DefiningEntityType;
[DebuggerStepThrough]
get => DefiningEntityType;
}

/// <summary>
Expand All @@ -2967,7 +3021,8 @@ IMutableEntityType IMutableEntityType.DefiningEntityType
/// </summary>
IConventionEntityType IConventionEntityType.DefiningEntityType
{
[DebuggerStepThrough] get => DefiningEntityType;
[DebuggerStepThrough]
get => DefiningEntityType;
}

/// <summary>
Expand Down
11 changes: 8 additions & 3 deletions src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -584,10 +584,14 @@ public virtual InternalPropertyBuilder Attach([NotNull] InternalEntityTypeBuilde
}
else
{
newPropertyBuilder = Metadata.GetIdentifyingMemberInfo() == null
var identifyingMemberInfo = Metadata.GetIdentifyingMemberInfo();

newPropertyBuilder = identifyingMemberInfo == null
? entityTypeBuilder.Property(
Metadata.ClrType, Metadata.Name, Metadata.GetTypeConfigurationSource(), configurationSource)
: entityTypeBuilder.Property(Metadata.GetIdentifyingMemberInfo(), configurationSource);
: (identifyingMemberInfo as PropertyInfo)?.IsIndexerProperty() == true
? entityTypeBuilder.IndexedProperty(Metadata.ClrType, Metadata.Name, configurationSource)
: entityTypeBuilder.Property(identifyingMemberInfo, configurationSource);
}

if (newProperty == Metadata)
Expand Down Expand Up @@ -658,7 +662,8 @@ public virtual InternalPropertyBuilder Attach([NotNull] InternalEntityTypeBuilde
/// </summary>
IConventionProperty IConventionPropertyBuilder.Metadata
{
[DebuggerStepThrough] get => Metadata;
[DebuggerStepThrough]
get => Metadata;
}

/// <summary>
Expand Down
Loading

0 comments on commit b2c7058

Please sign in to comment.