Fluent Mapping Dictionary<string, MyDto>

Hi

I have a dictionary which I want to map using fluent mapping, and for some properties of MyDto class, I need to add normalizer.
// ...
// Dictionary<string, MyDto> SomeCollection {get;set;}
// ...
// class MyDto {
// Name {get;set;}
// }

How can I map it?

I answered your same question on stackoverflow; will include here for completeness.

It's not possible to add this as an explicit mapping, but it is through a dynamic template.

Let's look at why it's not possible through explicit mapping. Consider how Dictionary<string, MyDto> SomeProperty will serialize to JSON. For example

client.IndexDocument(new MyEntity 
{
    SomeProperty = new Dictionary<string, UserQuery.MyDto>
    {
        { "field_1", new MyDto { Name = "foo" } },
        { "field_2", new MyDto { Name = "bar" } }
    }
});

by default will serialize as

{
  "someProperty": {
    "field_1": {
      "name": "foo"
    },
    "field_2": {
      "name": "bar"
    }
  }
}

If we wanted to apply an explicit mapping to MyDto.Name, we would need to know at the point of mapping, all of the dictionary keys that will be used.

You can however, configure a dynamic template that will map any MyDto.Name as a keyword type, using path_match

private static void Main()
{
    var defaultIndex = "my_index";
    var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));

    var settings = new ConnectionSettings(pool)
        .DefaultIndex(defaultIndex);

    var client = new ElasticClient(settings);
    
	if (client.IndexExists(defaultIndex).Exists)
		client.DeleteIndex(defaultIndex);
	
    var createIndexResponse = client.CreateIndex(defaultIndex, c => c
		.Settings(s => s
			.NumberOfShards(1)
			.NumberOfReplicas(0)
		)
		.Mappings(m => m
			.Map<MyEntity>(mm => mm
                .AutoMap()
                .DynamicTemplates(dt => dt
                    .DynamicTemplate("MyDto", dtd => dtd
                        .PathMatch("someProperty.*.name")
                        .Mapping(dm => dm
                            .Keyword(k => k)
                        )
                    )
                )
                .Properties(p => p
                    .Object<Dictionary<string, MyDto>>(o => o
                        .Name(n => n.SomeProperty)
                    )
                )
            )
		)
	);
    
    var indexResponse = client.Index(new MyEntity 
    {
        SomeProperty = new Dictionary<string, UserQuery.MyDto>
        {
            { "field_1", new MyDto { Name = "foo" } },
            { "field_2", new MyDto { Name = "bar" } }
        }
    }, i => i.Refresh(Refresh.WaitFor));
    
    var mappingResponse = client.GetMapping<MyEntity>();
}

public class MyEntity
{
    public Dictionary<string, MyDto> SomeProperty { get; set; }
}

public class MyDto
{
    public string Name { get; set; }
}

The mapping response confirms that someProperty.field_1.name and someProperty.field_2.name are mapped as keyword

{
  "my_index" : {
    "mappings" : {
      "myentity" : {
        "dynamic_templates" : [
          {
            "MyDto" : {
              "path_match" : "someProperty.*.name",
              "mapping" : {
                "type" : "keyword"
              }
            }
          }
        ],
        "properties" : {
          "someProperty" : {
            "properties" : {
              "field_1" : {
                "properties" : {
                  "name" : {
                    "type" : "keyword"
                  }
                }
              },
              "field_2" : {
                "properties" : {
                  "name" : {
                    "type" : "keyword"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

An aside on mapping explosion

You may want to consider adding a property to MyDto to hold the dictionary key, and use a List<MyDto> or similar collection mapped as a nested datatype, rather than Dictionary<string, MyDto>, if users can add any arbitrary key names they wish. With a high cardinality of dictionary keys, you run the risk of a mapping explosion and hitting the maximum number of fields soft limit and a lot of sparse fields, which can affect performance. With a List<MyDto> property type, you would not have this issue and can still query on the key field, at the expense of the trade-off that a List<MyDto> may be less optimal for your application code than a Dictionary<string, MyDto>. Something to consider :slight_smile:

1 Like

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.