Skip to content

Commit a9ed884

Browse files
committed
AutoMap() is now aware of overridden properties in derived types (#2632)
* AutoMap() is now aware of overridden properties in derived types Previously, `AutoMap()` would not take into account properties that have been overridden in derived classes: ``` public class A { public int Foo { get; set; } } public class B : A { public new string Foo { get; set; } } ``` So in this instance, mapping class `B` would result in `Foo` being mapped as an `int`, even though it has been overridden as a `string` using the `new` keyword. With this commit, `AutoMap()` is now aware of overridden properties and in this example, will map `Foo` as a `string`. Closes #2566 * fix formatting
1 parent 5a9dc67 commit a9ed884

File tree

3 files changed

+126
-29
lines changed

3 files changed

+126
-29
lines changed

src/Nest/CommonAbstractions/Extensions/TypeExtensions.cs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ internal static class TypeExtensions
2929

3030
//this contract is only used to resolve properties in class WE OWN.
3131
//these are not subject to change depending on what the user passes as connectionsettings
32-
private static readonly ElasticContractResolver JsonContract = new ElasticContractResolver(new ConnectionSettings(), null);
32+
private static readonly ElasticContractResolver JsonContract =
33+
new ElasticContractResolver(new ConnectionSettings(), null);
3334

3435
public delegate T ObjectActivator<out T>(params object[] args);
3536

@@ -51,7 +52,7 @@ internal static object CreateGenericInstance(this Type t, Type[] closeOver, para
5152
return closedType.CreateInstance(args);
5253
}
5354

54-
internal static T CreateInstance<T>(this Type t, params object[] args) => (T)t.CreateInstance(args);
55+
internal static T CreateInstance<T>(this Type t, params object[] args) => (T) t.CreateInstance(args);
5556

5657
internal static object CreateInstance(this Type t, params object[] args)
5758
{
@@ -71,7 +72,7 @@ internal static object CreateInstance(this Type t, params object[] args)
7172
var ctor = constructors.FirstOrDefault();
7273
if (ctor == null)
7374
throw new Exception($"Cannot create an instance of {t.FullName} because it has no constructor taking {args.Length} arguments");
74-
activator = (ObjectActivator<object>)generic.Invoke(null, new[] { ctor });
75+
activator = (ObjectActivator<object>) generic.Invoke(null, new[] {ctor});
7576
CachedActivators.TryAdd(key, activator);
7677
return activator(args);
7778
}
@@ -115,11 +116,12 @@ private static ObjectActivator<T> GetActivator<T>(ConstructorInfo ctor)
115116
Expression.Lambda(typeof(ObjectActivator<T>), newExp, param);
116117

117118
//compile it
118-
ObjectActivator<T> compiled = (ObjectActivator<T>)lambda.Compile();
119+
ObjectActivator<T> compiled = (ObjectActivator<T>) lambda.Compile();
119120
return compiled;
120121
}
121122

122-
internal static IList<JsonProperty> GetCachedObjectProperties(this Type t, MemberSerialization memberSerialization = MemberSerialization.OptIn)
123+
internal static IList<JsonProperty> GetCachedObjectProperties(this Type t,
124+
MemberSerialization memberSerialization = MemberSerialization.OptIn)
123125
{
124126
IList<JsonProperty> propertyDictionary;
125127
if (CachedTypeProperties.TryGetValue(t, out propertyDictionary))
@@ -139,21 +141,53 @@ internal static IList<PropertyInfo> AllPropertiesCached(this Type t)
139141
return propertyInfos;
140142
}
141143

142-
/// <summary> Returns inherited properties with reflectedType set to base type</summary>
144+
/// <summary>
145+
/// Returns inherited properties with reflectedType set to base type
146+
/// </summary>
143147
private static IEnumerable<PropertyInfo> AllPropertiesNotCached(this Type type)
144148
{
149+
var propertiesByName = new Dictionary<string, PropertyInfo>();
145150
do
146151
{
147152
foreach (var propertyInfo in type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
148-
yield return propertyInfo;
153+
{
154+
if (propertiesByName.ContainsKey(propertyInfo.Name))
155+
{
156+
if (IsHidingMember(propertyInfo))
157+
{
158+
propertiesByName[propertyInfo.Name] = propertyInfo;
159+
}
160+
}
161+
else
162+
{
163+
propertiesByName.Add(propertyInfo.Name, propertyInfo);
164+
}
165+
}
149166
#if DOTNETCORE
150167
type = type.GetTypeInfo()?.BaseType;
151168
} while (type?.GetTypeInfo()?.BaseType != null);
152169
#else
153170
type = type.BaseType;
154171
} while (type?.BaseType != null);
155172
#endif
173+
return propertiesByName.Values;
174+
}
156175

176+
/// <summary>
177+
/// Determines if a property is overriding an inherited property of its base class
178+
/// </summary>
179+
private static bool IsHidingMember(PropertyInfo propertyInfo)
180+
{
181+
#if DOTNETCORE
182+
var baseType = propertyInfo.DeclaringType?.GetTypeInfo()?.BaseType;
183+
#else
184+
var baseType = propertyInfo.DeclaringType?.BaseType;
185+
#endif
186+
var baseProperty = baseType?.GetProperty(propertyInfo.Name);
187+
if (baseProperty == null) return false;
188+
var derivedGetMethod = propertyInfo.GetGetMethod().GetBaseDefinition();
189+
return derivedGetMethod?.ReturnType != propertyInfo.PropertyType;
157190
}
158191
}
159192
}
193+

src/Nest/Mapping/Types/PropertyBase.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ protected PropertyBase(TypeName typeName)
3636
{
3737
Type = typeName;
3838
}
39+
3940
#pragma warning disable 618
4041
protected PropertyBase(FieldType type) : this(type.GetStringValue()) { }
4142
#pragma warning restore 618
4243

43-
protected string DebugDisplay => $"Type: {Type.DebugDisplay}, Name: {Name.DebugDisplay} ";
44+
protected string DebugDisplay => $"Type: {Type?.DebugDisplay}, Name: {Name?.DebugDisplay} ";
4445

4546
public PropertyName Name { get; set; }
4647
public virtual TypeName Type { get; set; }

src/Tests/ClientConcepts/HighLevel/Mapping/AutoMap.doc.cs

Lines changed: 83 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ public void MappingManually()
5555
.Map<Company>(m => m
5656
.Properties(ps => ps
5757
.Text(s => s
58-
.Name(c => c.Name) //<1> map `Name` as a `string` type
58+
.Name(c => c.Name) //<1> map `Name` as a `string` type
5959
)
60-
.Object<Employee>(o => o //<2> map `Employees` as an `object` type, mapping each of the properties of `Employee`
60+
.Object<Employee>(o => o //<2> map `Employees` as an `object` type, mapping each of the properties of `Employee`
6161
.Name(c => c.Employees)
6262
.Properties(eps => eps
6363
.Text(s => s
@@ -117,7 +117,7 @@ public void MappingManually()
117117
}
118118
};
119119

120-
Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor);
120+
Expect(expected).WhenSerializing((ICreateIndexRequest) descriptor);
121121
}
122122

123123
[U]
@@ -273,7 +273,7 @@ public void UsingAutoMap()
273273
};
274274

275275

276-
Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor);
276+
Expect(expected).WhenSerializing((ICreateIndexRequest) descriptor);
277277
}
278278
/**[IMPORTANT]
279279
* ====
@@ -353,7 +353,7 @@ public void OverridingAutoMappedProperties()
353353
}
354354
};
355355

356-
Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor);
356+
Expect(expected).WhenSerializing((ICreateIndexRequest) descriptor);
357357

358358
/**
359359
* `.AutoMap()` is idempotent; calling it before or after manually
@@ -371,7 +371,7 @@ public void OverridingAutoMappedProperties()
371371
)
372372
);
373373

374-
Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor);
374+
Expect(expected).WhenSerializing((ICreateIndexRequest) descriptor);
375375
}
376376

377377
/**[[attribute-mapping]]
@@ -758,7 +758,58 @@ public void OverridingAutoMappedAttributes()
758758
}
759759
};
760760

761-
Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor);
761+
Expect(expected).WhenSerializing((ICreateIndexRequest) descriptor);
762+
}
763+
764+
public class ParentWithStringId : Parent
765+
{
766+
public new string Id { get; set; }
767+
}
768+
769+
[U]
770+
public void OverridingInheritedProperties()
771+
{
772+
var descriptor = new CreateIndexDescriptor("myindex")
773+
.Mappings(ms => ms
774+
.Map<ParentWithStringId>(m => m
775+
.AutoMap()
776+
)
777+
);
778+
779+
var expected = new
780+
{
781+
mappings = new
782+
{
783+
parent = new
784+
{
785+
properties = new
786+
{
787+
id = new
788+
{
789+
type = "text",
790+
fields = new
791+
{
792+
keyword = new
793+
{
794+
ignore_above = 256,
795+
type = "keyword"
796+
}
797+
}
798+
}
799+
}
800+
}
801+
}
802+
};
803+
804+
var settings = WithConnectionSettings(s => s
805+
.InferMappingFor<ParentWithStringId>(m => m
806+
.TypeName("parent")
807+
.Ignore(p => p.Description)
808+
.Ignore(p => p.IgnoreMe)
809+
)
810+
);
811+
812+
settings.Expect(expected).WhenSerializing((ICreateIndexRequest) descriptor);
762813
}
763814

764815
/**[float]
@@ -830,7 +881,7 @@ public void IgnoringProperties()
830881
)
831882
);
832883

833-
settings.Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor);
884+
settings.Expect(expected).WhenSerializing((ICreateIndexRequest) descriptor);
834885
}
835886

836887
public class Parent
@@ -840,7 +891,10 @@ public class Parent
840891
public string IgnoreMe { get; set; }
841892
}
842893

843-
public class Child : Parent { }
894+
public class Child : Parent
895+
{
896+
}
897+
844898
[U]
845899
public void IgnoringInheritedProperties()
846900
{
@@ -860,16 +914,20 @@ public void IgnoringInheritedProperties()
860914
{
861915
properties = new
862916
{
863-
desc = new {
864-
fields = new {
865-
keyword = new {
917+
desc = new
918+
{
919+
fields = new
920+
{
921+
keyword = new
922+
{
866923
ignore_above = 256,
867924
type = "keyword"
868925
}
869926
},
870927
type = "text"
871928
},
872-
id = new {
929+
id = new
930+
{
873931
type = "integer"
874932
}
875933
}
@@ -884,8 +942,9 @@ public void IgnoringInheritedProperties()
884942
)
885943
);
886944

887-
settings.Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor);
945+
settings.Expect(expected).WhenSerializing((ICreateIndexRequest) descriptor);
888946
}
947+
889948
/**[float]
890949
* == Mapping Recursion
891950
* If you notice in our previous `Company` and `Employee` examples, the `Employee` type is recursive
@@ -933,7 +992,7 @@ public void ControllingRecursionDepth()
933992
}
934993
};
935994

936-
Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor);
995+
Expect(expected).WhenSerializing((ICreateIndexRequest) descriptor);
937996

938997
/** Now let's specify a maxRecursion of 3 */
939998
var withMaxRecursionDescriptor = new CreateIndexDescriptor("myindex")
@@ -981,7 +1040,7 @@ public void ControllingRecursionDepth()
9811040
}
9821041
};
9831042

984-
Expect(expectedWithMaxRecursion).WhenSerializing((ICreateIndexRequest)withMaxRecursionDescriptor);
1043+
Expect(expectedWithMaxRecursion).WhenSerializing((ICreateIndexRequest) withMaxRecursionDescriptor);
9851044
}
9861045

9871046
[U]
@@ -1002,7 +1061,7 @@ public void PutMappingAlsoAdheresToMaxRecursion()
10021061
}
10031062
};
10041063

1005-
Expect(expected).WhenSerializing((IPutMappingRequest)descriptor);
1064+
Expect(expected).WhenSerializing((IPutMappingRequest) descriptor);
10061065

10071066
var withMaxRecursionDescriptor = new PutMappingDescriptor<A>().AutoMap(3);
10081067

@@ -1039,7 +1098,7 @@ public void PutMappingAlsoAdheresToMaxRecursion()
10391098
}
10401099
};
10411100

1042-
Expect(expectedWithMaxRecursion).WhenSerializing((IPutMappingRequest)withMaxRecursionDescriptor);
1101+
Expect(expectedWithMaxRecursion).WhenSerializing((IPutMappingRequest) withMaxRecursionDescriptor);
10431102
}
10441103
//endhide
10451104

@@ -1058,15 +1117,17 @@ public class DisableDocValuesPropertyVisitor : NoopPropertyVisitor
10581117
public override void Visit(
10591118
INumberProperty type,
10601119
PropertyInfo propertyInfo,
1061-
ElasticsearchPropertyAttributeBase attribute) //<1> Override the `Visit` method on `INumberProperty` and set `DocValues = false`
1120+
ElasticsearchPropertyAttributeBase
1121+
attribute) //<1> Override the `Visit` method on `INumberProperty` and set `DocValues = false`
10621122
{
10631123
type.DocValues = false;
10641124
}
10651125

10661126
public override void Visit(
10671127
IBooleanProperty type,
10681128
PropertyInfo propertyInfo,
1069-
ElasticsearchPropertyAttributeBase attribute) //<2> Similarily, override the `Visit` method on `IBooleanProperty` and set `DocValues = false`
1129+
ElasticsearchPropertyAttributeBase
1130+
attribute) //<2> Similarily, override the `Visit` method on `IBooleanProperty` and set `DocValues = false`
10701131
{
10711132
type.DocValues = false;
10721133
}
@@ -1133,7 +1194,8 @@ public void UsingACustomPropertyVisitor()
11331194
*/
11341195
public class EverythingIsAStringPropertyVisitor : NoopPropertyVisitor
11351196
{
1136-
public override IProperty Visit(PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) => new TextProperty();
1197+
public override IProperty Visit(PropertyInfo propertyInfo,
1198+
ElasticsearchPropertyAttributeBase attribute) => new TextProperty();
11371199
}
11381200

11391201
[U]

0 commit comments

Comments
 (0)