如何使用自定义序列化程序对使用 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
}
目前使用这些版本:
- 弹性搜索 7.4.2
- 鸟巢 7.4.2
更新:
下面由 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
};
}
我正在使用 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
}
目前使用这些版本:
- 弹性搜索 7.4.2
- 鸟巢 7.4.2
更新:
下面由 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
};
}