22using System . Collections . Concurrent ;
33using System . Collections . Generic ;
44using System . Linq ;
5+ using System . Runtime . CompilerServices ;
56using Elastic . Clients . Elasticsearch ;
67using Elastic . Clients . Elasticsearch . IndexManagement ;
78using Elastic . Clients . Elasticsearch . Mapping ;
@@ -18,6 +19,7 @@ public class ElasticMappingResolver
1819 private readonly TypeMapping _codeMapping ;
1920 private readonly Inferrer _inferrer ;
2021 private readonly ConcurrentDictionary < string , FieldMapping > _mappingCache = new ( ) ;
22+ private readonly ConditionalWeakTable < IProperty , ConcurrentDictionary < string , object > > _propertyMetadata = new ( ) ;
2123 private readonly ILogger _logger ;
2224
2325 public static ElasticMappingResolver NullInstance = new ( ( ) => null ) ;
@@ -85,15 +87,28 @@ public FieldMapping GetMapping(string field, bool followAlias = false)
8587 {
8688 string fieldPart = fieldParts [ depth ] ;
8789 IProperty fieldMapping = null ;
90+ PropertyName foundPropertyName = null ;
8891 if ( currentProperties == null || ! currentProperties . TryGetProperty ( fieldPart , out fieldMapping ) )
8992 {
90- // check to see if there is a name match
93+ // check to see if there is a name match by iterating through the dictionary keys
9194 if ( currentProperties != null )
92- fieldMapping = ( ( IDictionary < PropertyName , IProperty > ) currentProperties ) . Values . FirstOrDefault ( m =>
95+ {
96+ foreach ( var kvp in ( IDictionary < PropertyName , IProperty > ) currentProperties )
9397 {
94- string propertyName = _inferrer . PropertyName ( m ? . TryGetName ( ) ) ;
95- return propertyName != null && propertyName . Equals ( fieldPart , StringComparison . OrdinalIgnoreCase ) ;
96- } ) ;
98+ string propertyName = null ;
99+ if ( _inferrer != null && kvp . Key ? . Name != null )
100+ propertyName = _inferrer . PropertyName ( kvp . Key ) ;
101+ else if ( kvp . Key ? . Name != null )
102+ propertyName = kvp . Key . Name ;
103+
104+ if ( propertyName != null && propertyName . Equals ( fieldPart , StringComparison . OrdinalIgnoreCase ) )
105+ {
106+ fieldMapping = kvp . Value ;
107+ foundPropertyName = kvp . Key ;
108+ break ;
109+ }
110+ }
111+ }
97112
98113 // no mapping found, call GetServerMapping again in case it hasn't been called recently and there are possibly new mappings
99114 if ( fieldMapping == null && GetServerMapping ( ) )
@@ -122,17 +137,32 @@ public FieldMapping GetMapping(string field, bool followAlias = false)
122137 break ;
123138 }
124139 }
140+ else
141+ {
142+ // TryGetProperty succeeded, store the PropertyName used
143+ foreach ( var kvp in ( IDictionary < PropertyName , IProperty > ) currentProperties )
144+ {
145+ if ( kvp . Value == fieldMapping )
146+ {
147+ foundPropertyName = kvp . Key ;
148+ break ;
149+ }
150+ }
151+ }
125152
126- // coded properties sometimes have null Name properties
127- string name = fieldMapping . TryGetName ( ) ;
128- // TODO: ?
129- // if (name == null && fieldMapping is IPropertyWithClrOrigin clrOrigin && clrOrigin.ClrOrigin != null)
130- // name = new PropertyName(clrOrigin.ClrOrigin);
153+ // Determine the property name - use foundPropertyName if available, otherwise fall back to fieldPart
154+ string resolvedName ;
155+ if ( foundPropertyName != null && _inferrer != null && foundPropertyName . Name != null )
156+ resolvedName = _inferrer . PropertyName ( foundPropertyName ) ;
157+ else if ( foundPropertyName != null && foundPropertyName . Name != null )
158+ resolvedName = foundPropertyName . Name ;
159+ else
160+ resolvedName = fieldPart ;
131161
132162 if ( depth == 0 )
133- resolvedFieldName += _inferrer . PropertyName ( name ) ;
163+ resolvedFieldName += resolvedName ;
134164 else
135- resolvedFieldName += "." + _inferrer . PropertyName ( name ) ;
165+ resolvedFieldName += "." + resolvedName ;
136166
137167 if ( depth == fieldParts . Length - 1 )
138168 {
@@ -150,6 +180,10 @@ public FieldMapping GetMapping(string field, bool followAlias = false)
150180 {
151181 currentProperties = objectProperty . Properties ;
152182 }
183+ else if ( fieldMapping is NestedProperty nestedProperty )
184+ {
185+ currentProperties = nestedProperty . Properties ;
186+ }
153187 else
154188 {
155189 if ( fieldMapping is TextProperty textProperty )
@@ -415,12 +449,13 @@ private Properties MergeProperties(Properties codeProperties, Properties serverP
415449 if ( kvp . Value is not FieldAliasProperty aliasProperty )
416450 continue ;
417451
418- mergedCodeProperties [ kvp . Key ] = new FieldAliasProperty
452+ var newAliasProperty = new FieldAliasProperty
419453 {
420- //LocalMetadata = aliasProperty.LocalMetadata,
421454 Path = _inferrer ? . Field ( aliasProperty . Path ) ?? aliasProperty . Path ,
422455 // Name = aliasProperty.Name
423456 } ;
457+ CopyPropertyMetadata ( aliasProperty , newAliasProperty ) ;
458+ mergedCodeProperties [ kvp . Key ] = newAliasProperty ;
424459 }
425460 }
426461 }
@@ -433,11 +468,12 @@ private Properties MergeProperties(Properties codeProperties, Properties serverP
433468 foreach ( var serverProperty in serverProperties )
434469 {
435470 var merged = serverProperty . Value ;
436- // if (mergedCodeProperties.TryGetProperty(serverProperty.Key, out var codeProperty))
437- // merged.LocalMetadata = codeProperty.LocalMetadata;
438471
439472 if ( mergedCodeProperties . TryGetProperty ( serverProperty . Key , out var codeProperty ) )
440473 {
474+ // Copy local metadata from code property to merged property
475+ CopyPropertyMetadata ( codeProperty , merged ) ;
476+
441477 switch ( merged )
442478 {
443479 case ObjectProperty objectProperty :
@@ -491,7 +527,7 @@ private bool GetServerMapping()
491527 }
492528 }
493529
494- public static ElasticMappingResolver Create < T > ( Func < TypeMappingDescriptor < T > , TypeMapping > mappingBuilder , ElasticsearchClient client , ILogger logger = null ) where T : class
530+ public static ElasticMappingResolver Create < T > ( Action < TypeMappingDescriptor < T > > mappingBuilder , ElasticsearchClient client , ILogger logger = null ) where T : class
495531 {
496532 logger ??= NullLogger . Instance ;
497533
@@ -506,7 +542,7 @@ public static ElasticMappingResolver Create<T>(Func<TypeMappingDescriptor<T>, Ty
506542 } , logger ) ;
507543 }
508544
509- public static ElasticMappingResolver Create < T > ( Func < TypeMappingDescriptor < T > , TypeMapping > mappingBuilder , ElasticsearchClient client , string index , ILogger logger = null ) where T : class
545+ public static ElasticMappingResolver Create < T > ( Action < TypeMappingDescriptor < T > > mappingBuilder , ElasticsearchClient client , string index , ILogger logger = null ) where T : class
510546 {
511547 logger ??= NullLogger . Instance ;
512548
@@ -521,10 +557,11 @@ public static ElasticMappingResolver Create<T>(Func<TypeMappingDescriptor<T>, Ty
521557 } , logger ) ;
522558 }
523559
524- public static ElasticMappingResolver Create < T > ( Func < TypeMappingDescriptor < T > , TypeMapping > mappingBuilder , Inferrer inferrer , Func < TypeMapping > getMapping , ILogger logger = null ) where T : class
560+ public static ElasticMappingResolver Create < T > ( Action < TypeMappingDescriptor < T > > mappingBuilder , Inferrer inferrer , Func < TypeMapping > getMapping , ILogger logger = null ) where T : class
525561 {
526- var codeMapping = mappingBuilder ( new TypeMappingDescriptor < T > ( ) ) ;
527- return new ElasticMappingResolver ( codeMapping , inferrer , getMapping , logger : logger ) ;
562+ var descriptor = new TypeMappingDescriptor < T > ( ) ;
563+ mappingBuilder ( descriptor ) ;
564+ return new ElasticMappingResolver ( descriptor , inferrer , getMapping , logger : logger ) ;
528565 }
529566
530567public static ElasticMappingResolver Create < T > ( ElasticsearchClient client , ILogger logger = null )
@@ -562,6 +599,59 @@ public static ElasticMappingResolver Create(Func<TypeMapping> getMapping, Inferr
562599 {
563600 return new ElasticMappingResolver ( getMapping , inferrer , logger : logger ) ;
564601 }
602+
603+ #region Property Metadata
604+
605+ public IDictionary < string , object > GetPropertyMetadata ( IProperty property )
606+ {
607+ if ( property == null )
608+ return null ;
609+
610+ return _propertyMetadata . GetOrCreateValue ( property ) ;
611+ }
612+
613+ public T GetPropertyMetadataValue < T > ( IProperty property , string key , T defaultValue = default )
614+ {
615+ var metadata = GetPropertyMetadata ( property ) ;
616+ if ( metadata == null || ! metadata . TryGetValue ( key , out var value ) )
617+ return defaultValue ;
618+
619+ if ( value is T typedValue )
620+ return typedValue ;
621+
622+ try
623+ {
624+ return ( T ) Convert . ChangeType ( value , typeof ( T ) ) ;
625+ }
626+ catch
627+ {
628+ return defaultValue ;
629+ }
630+ }
631+
632+ public void SetPropertyMetadataValue ( IProperty property , string key , object value )
633+ {
634+ if ( property == null )
635+ return ;
636+
637+ var metadata = _propertyMetadata . GetOrCreateValue ( property ) ;
638+ metadata [ key ] = value ;
639+ }
640+
641+ public void CopyPropertyMetadata ( IProperty source , IProperty target )
642+ {
643+ if ( source == null || target == null )
644+ return ;
645+
646+ if ( ! _propertyMetadata . TryGetValue ( source , out var sourceMetadata ) )
647+ return ;
648+
649+ var targetMetadata = _propertyMetadata . GetOrCreateValue ( target ) ;
650+ foreach ( var kvp in sourceMetadata )
651+ targetMetadata [ kvp . Key ] = kvp . Value ;
652+ }
653+
654+ #endregion
565655}
566656
567657public class FieldMapping
0 commit comments