How to configure mapping of nested properties using .NET client?

I am trying to map a graph using the .NET Client API from NuGet "Elastic.Clients.Elasticsearch" 8.13.8. See code below.

The graph nodes are each represented as a document in ES. On each document there is supposed to be an array of a nested "relations" object field, each of which holds the relation's target ID and relationship type.

I am able to map the node name as a keyword as shown below. But when I try to "dot" my way through the mappings API, I am unable to unlock access to the type of the relation class.

How can I map the property Type in KnowledgeGraphRelation as a keyword, or in general, how do I map anything inside an array of nested objects inside the main document type?

namespace ElasticTest
{
  public class KnowledgeGraphNode
  {
    public Guid Id { get; private set; }
    public string Name { get; private set; }
    public List<KnowledgeGraphRelation> Relations { get; private set; }

    public KnowledgeGraphNode(string name)
    {
      Id = Guid.NewGuid();
      Name = name;
      Relations = new List<KnowledgeGraphRelation>();
    }
  }


  public class KnowledgeGraphRelation
  {
    public Guid Target { get; private set; }
    public string Type { get; private set; }

    public KnowledgeGraphRelation(Guid target, string type)
    {
      Target = target;
      Type = type;
    }
  }


  public class GraphRepository
  {
    public async Task TestMapping()
    {
      ElasticsearchClient Client = new ElasticsearchClient();
      IndexName indexName = "MyGraph";

      await Client.Indices.CreateAsync(indexName, ConfigureIndex);
    }

    void ConfigureIndex(CreateIndexRequestDescriptor descriptor)
    {
      descriptor.Mappings(md =>
        md.Properties<KnowledgeGraphNode>(pd =>
        {
          pd.Keyword(pn => pn.Name)
            .Nested(pn => pn.Relations,
              npd => npd.);
        }));
    }

    async Task Search()
    {
      ElasticsearchClient Client = new ElasticsearchClient();
      IndexName indexName = "MyGraph";

      var response = await Client.SearchAsync<KnowledgeGraphNode>(indexName, d => d.Query(
        q => q.Nested(
          nq => nq.Path(n => n.Relations).Query(
            rq => rq.Term(rt => rt.Field())))));

        // ----^ rt.Field has type "node" instead of "relation".
    }
  }
}

I have the same issue when trying to query the nested fields.

Hi @JornWildt, could you please open an issue on GitHub for this case?

This looks like a rather complicated problem to solve as it involves special handling in the code generator.

As a workaround, you could use the "non-lambda" overloads. For example, you are currently using:

public PropertiesDescriptor<TDocument> Nested(Expression<Func<TDocument, object>> propertyName, Action<Elastic.Clients.Elasticsearch.Mapping.NestedPropertyDescriptor<TDocument>> configure)

If you use this overload instead, you are not bound to the current TDocument type:

public PropertiesDescriptor<TDocument> Nested(Elastic.Clients.Elasticsearch.PropertyName propertyName, NestedProperty nestedProperty)

Like something similar to this:

descriptor.Mappings(md =>
	md.Properties<KnowledgeGraphNode>(pd =>
	{
		pd.Keyword(pn => pn.Name)
			.Nested(nameof(KnowledgeGraphNode.Relations),
				new NestedProperty
				{
					Properties = new Properties(new Dictionary<PropertyName, IProperty>
					{
						{
							nameof(KnowledgeGraphRelation.Type),
							new KeywordProperty()
						}
					})
				});
	}));

This is definitely not ideal, but should hopefully unblock you until we implement a proper fix.

Thanks, I'll look into it.

After much searching I found some older examples of the .NET Client where the Linq method "First" was used to access nested fields. It turned out to work in the new .NET Client too:

    void ConfigureIndex(CreateIndexRequestDescriptor descriptor)
    {
      descriptor.Mappings(md =>
        md.Properties<KnowledgeGraphNode>(pd =>
        {
          pd.Keyword(pn => pn.Name)
            .Nested(pn => pn.Relations,
              npd => npd.Properties(
                np => np.Keyword(k => k.Relations.First().Target)
                  .Keyword(k => k.Relations.First().Type)));
        }));
    }


    async Task Search()
    {
      ElasticsearchClient Client = new ElasticsearchClient();
      IndexName indexName = "MyGraph";

      var response = await Client.SearchAsync<KnowledgeGraphNode>(indexName, d => d.Query(
        q => q.Nested(
          nq => nq.Path(n => n.Relations).Query(
            rq => rq.Match(ma => ma.Field(f => f.Relations.First().Target).Query("xxx"))))));

    }

The above code has been tested and seems to work as expected.

I never got around trying the sugested solution.

May I suggest improving the new .NET Client documentation? It is really sparse as it is now.

1 Like

Hi @JornWildt,

thank you for providing the correct solution!

Your point regarding the documentation is absolutely valid. The fact that not even me as the new maintainer knew about this quirk, is certainly a proof for that...

1 Like