如何使用自定义序列化程序对使用 NEST 和 Elasticsearch 的子文档序列化类型信息

How to serialize type information using custom serializer also for sub documents using NEST and Elasticsearch

我正在使用 https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/custom-serialization.html#_serializing_type_information 上的示例来获取 elasticsearch 中文档的 $type 信息。

然而,正如页面上提到的,这只是 returns 外部文档的类型信息:

the type information is serialized for the outer MyDocument instance, but not for each MySubDocument instance in the SubDocuments collection.

所以我现在的问题是,是否有人知道如何同时获取子文档的类型信息?

我已经尝试使用与他们的示例中相同的 JsonSerializerSettings 与 Elasticsearch 分开(使用 LinqPad),并且我还获得了子文档的类型信息:

void Main()
{
    var temp = new ListBlock
    {
        Id = 1,
        Title = "Titel",
        Blocks = new List<BlockContent> {
            new InnerBlock {
                Id = 11,
                MyProperty ="Inner Property"
            },
            new InnerBlock2 {
                Id = 12,
                MyProperty2 = "Inner property 2"
            }
        }
    };

    var serializeOptions = new Newtonsoft.Json.JsonSerializerSettings
    {
        TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All,
        NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
        TypeNameAssemblyFormatHandling = Newtonsoft.Json.TypeNameAssemblyFormatHandling.Simple,
        Formatting = Newtonsoft.Json.Formatting.Indented
    };
    var serialized = Newtonsoft.Json.JsonConvert.SerializeObject(temp, serializeOptions);
    serialized.Dump();
}

public class BlockContent
{
    public int Id { get; set; }
}

public class ListBlock : BlockContent
{
    public string Title { get; set; }
    public List<BlockContent> Blocks { get; set; }
}

public class ListBlock2 : BlockContent
{
    public string Title2 { get; set; }
    public List<BlockContent> Blocks { get; set; }
}

public class InnerBlock : BlockContent
{
    public string MyProperty { get; set; }
}

public class InnerBlock2 : BlockContent
{
    public string MyProperty2 { get; set; }
}

此代码产生以下结果 json:

{
  "$type": "UserQuery+ListBlock, LINQPadQuery",
  "Title": "Titel",
  "Blocks": {
    "$type": "System.Collections.Generic.List`1[[UserQuery+BlockContent, LINQPadQuery]], System.Private.CoreLib",
    "$values": [
      {
        "$type": "UserQuery+InnerBlock, LINQPadQuery",
        "MyProperty": "Inner Property",
        "Id": 11
      },
      {
        "$type": "UserQuery+InnerBlock2, LINQPadQuery",
        "MyProperty2": "Inner property 2",
        "Id": 12
      }
    ]
  },
  "Id": 1
}

目前使用这些版本:

更新:

下面由 Russ Cam 提供的解决方案对于响应中包含的数据模型非常有效,但是我根据我们如何创建索引(使用自动映射)和批量索引初始列表在下面整理了一个示例文件。如果我们在模型中排除 Guids (CategoryIds) 列表,这会正常工作,但如果我们包含它,则会抛出以下异常:

{
    "took": 8,
    "errors": true,
    "items": [{
            "index": {
                "_index": "testindex",
                "_type": "_doc",
                "_id": "1",
                "status": 400,
                "error": {
                    "type": "mapper_parsing_exception",
                    "reason": "failed to parse field [categoryIds] of type [keyword] in document with id '1'. Preview of field's value: '{$values=[], $type=System.Collections.Generic.List`1[[System.Guid, System.Private.CoreLib]], System.Private.CoreLib}'",
                    "caused_by": {
                        "type": "illegal_state_exception",
                        "reason": "Can't get text on a START_OBJECT at 1:140"
                    }
                }
            }
        }, {
            "index": {
                "_index": "testindex",
                "_type": "_doc",
                "_id": "2",
                "status": 400,
                "error": {
                    "type": "mapper_parsing_exception",
                    "reason": "failed to parse field [categoryIds] of type [keyword] in document with id '2'. Preview of field's value: '{$values=[], $type=System.Collections.Generic.List`1[[System.Guid, System.Private.CoreLib]], System.Private.CoreLib}'",
                    "caused_by": {
                        "type": "illegal_state_exception",
                        "reason": "Can't get text on a START_OBJECT at 1:141"
                    }
                }
            }
        }
    ]
}

这是一个简单的 (.Net 5) 控制台应用程序,希望其他人也可以重现此行为:

using System;
using System.Collections.Generic;
using System.Linq;
using Elasticsearch.Net;
using Nest;
using Nest.JsonNetSerializer;
using Newtonsoft.Json;

namespace ElasticsearchTypeSerializer
{
    internal class Program
    {
        private const string IndexName = "testindex";

        private static void Main(string[] args)
        {
            var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
            var settings = new ConnectionSettings(pool,
                (builtin, settings) => new MySecondCustomJsonNetSerializer(builtin, settings));
            settings.DisableDirectStreaming();
            var client = new ElasticClient(settings);

            CreateIndex(client);
            IndexDocuments(client);
            var documents = GetDocuments(client);
        }

        private static void CreateIndex(IElasticClient client)
        {
            var createIndexResponse = client.Indices.Create(IndexName, x => x.Map<MyDocument>(m => m.AutoMap()));
        }

        private static void IndexDocuments(IElasticClient client)
        {
            var documents = new List<MyDocument>
            {
                new()
                {
                    Id = 1,
                    Name = "My first document",
                    OwnerId = 2,
                    SubDocuments = new List<SubDocument>
                    {
                        new MySubDocument {Id = 11, Name = "my first sub document"},
                        new MySubDocument2 {Id = 12, Description = "my second sub document"}
                    }
                },
                new()
                {
                    Id = 2,
                    Name = "My second document",
                    OwnerId = 3,
                    SubDocuments = new List<SubDocument>
                    {
                        new MySubDocument {Id = 21, Name = "My third sub document"}
                    }
                }
            };

            var bulkIndexResponse = client.Bulk(b => b.Index(IndexName).IndexMany(documents).Refresh(Refresh.True));
        }

        private static IEnumerable<MyDocument> GetDocuments(IElasticClient client)
        {
            var searchResponse = client.Search<MyDocument>(s => s.Index(IndexName).Query(q => q.MatchAll()));
            var documents = searchResponse.Documents.ToList();
            return documents;
        }
    }


    public class MyDocument
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string FilePath { get; set; }
        public int OwnerId { get; set; }
        public List<Guid> CategoryIds { get; set; } = new();
        public List<SubDocument> SubDocuments { get; set; }
    }

    public class SubDocument
    {
        public int Id { get; set; }
    }

    public class MySubDocument : SubDocument
    {
        public string Name { get; set; }
    }

    public class MySubDocument2 : SubDocument
    {
        public string Description { get; set; }
    }


    public class MySecondCustomJsonNetSerializer : ConnectionSettingsAwareSerializerBase
    {
        public MySecondCustomJsonNetSerializer(IElasticsearchSerializer builtinSerializer,
            IConnectionSettingsValues connectionSettings)
            : base(builtinSerializer, connectionSettings)
        {
        }

        protected override JsonSerializerSettings CreateJsonSerializerSettings()
        {
            return new()
            {
                TypeNameHandling = TypeNameHandling.All,
                NullValueHandling = NullValueHandling.Ignore,
                TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple
            };
        }
    }
}

非常感谢有关此问题的任何帮助!

如果您希望文档中的类型化集合包含类型信息,则可以省略派生契约解析器,这会抑制集合项类型的类型处理

private static void Main()
{
    var pool = new SingleNodeConnectionPool(new Uri($"http://localhost:9200"));
    var settings = new ConnectionSettings(pool, 
        (builtin, settings) => new MySecondCustomJsonNetSerializer(builtin, settings));
        
    var client = new ElasticClient(settings);

    var document = new MyDocument
    {
        Id = 1,
        Name = "My first document",
        OwnerId = 2,
        SubDocuments = new[]
        {
        new MySubDocument { Name = "my first sub document" },
        new MySubDocument { Name = "my second sub document" },
    }
    };

    var indexResponse = client.IndexDocument(document);

}

public class MyDocument
{
    public int Id { get; set; }

    public string Name { get; set; }

    public string FilePath { get; set; }

    public int OwnerId { get; set; }

    public IEnumerable<MySubDocument> SubDocuments { get; set; }
}

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

public class MySecondCustomJsonNetSerializer : ConnectionSettingsAwareSerializerBase
{
    public MySecondCustomJsonNetSerializer(IElasticsearchSerializer builtinSerializer, IConnectionSettingsValues connectionSettings)
        : base(builtinSerializer, connectionSettings) { }

    protected override JsonSerializerSettings CreateJsonSerializerSettings() =>
        new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.All,
            NullValueHandling = NullValueHandling.Ignore,
            TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple
        };
}