RavenDB 5.3查询投影时自定义JsonConverter失效问题排查
RavenDB查询自定义JsonConverter属性返回null的问题
我有一个大型文档,仅需查询其中需通过自定义JsonConverter反序列化的SomeProp属性。在RavenDB文档及网络搜索中未找到相关解决方案。
实体类定义
public class Document { public DocId SomeProp { get; set; } public string Data { get; set; } // 更多属性... } public class DocId { public Guid Id { get; set; } }
问题现象
加载文档时SomeProp可正常反序列化,但执行以下查询时返回null:
var x = session.Query<Document>() .Select(i => i.SomeProp) .FirstOrDefault();
JsonReader的差异场景
- 查询路径:调用
IdConverter.ReadJson()时,JsonReader指向整个JSON对象的开头:
{ "SomeProp":"95704f13-fd64-4dcb-a64e-6aa42bc577d6", "@metadata":{ ... } }
- 加载路径:JsonReader指向SomeProp的值,对应JSON:
{ "SomeProp":"95704f13-fd64-4dcb-a64e-6aa42bc577d6", "Data":null ... }
我期望JsonReader每次都指向SomeProp的值,是操作有误?还是RavenDB要求转换器同时解析对象和字符串值?
复现代码
自定义转换器与契约解析器
public class IdConverter : JsonConverter { public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { writer.WriteValue(((DocId)value).Id.ToString()); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { return reader.TokenType != JsonToken.String ? null : new DocId { Id = Guid.Parse(reader.Value as string) }; } public override bool CanConvert(Type type) { return typeof(DocId).IsAssignableFrom(type); } } public class LocalResolver : DefaultRavenContractResolver { public LocalResolver(ISerializationConventions x) : base(x) { } public override JsonContract ResolveContract(Type type) { if (type.IsGenericType) { var p = string.Join(", ", type.GenericTypeArguments.Select(x => x.Name)); Debug.WriteLine($"Resolving converter for {type.Name}<{p}>"); } else { Debug.WriteLine($"Resolving converter for {type.Name}"); } if (type.IsAssignableFrom(typeof(DocId))) return new JsonObjectContract(type) { Converter = new IdConverter() }; return base.ResolveContract(type); } }
单元测试代码
[TestMethod] public void MyTestMethod() { var conventions = new NewtonsoftJsonSerializationConventions(); var resolver = new LocalResolver(conventions); conventions.JsonContractResolver = resolver; var store = new DocumentStore() { Urls = new[] { <your URL here> }, Database = DatabaseConfiguration.Name, Conventions = new DocumentConventions() { Serialization = conventions } }; const string g = "95704f13-fd64-4dcb-a64e-6aa42bc577d6"; var id = $"Doc/{g}"; store.Initialize(); using (var session = store.OpenSession()) { var doc = new Document { SomeProp = new DocId { Id = Guid.Parse(g) } }; Debug.WriteLine("Storing document"); session.Store(doc, id); session.SaveChanges(); } using (var session = store.OpenSession()) { Debug.WriteLine("Loading document"); var x = session.Load<Document>(id); Assert.AreEqual(g, x.SomeProp.Id.ToString()); } using (var session = store.OpenSession()) { Debug.WriteLine("Querying document"); var x = session.Query<Document>() .Select(i => i.SomeProp) .FirstOrDefault(); Assert.IsNotNull(x); Assert.AreEqual(g, x.Id.ToString()); } }
解决方案
问题根源在于RavenDB执行投影查询(Select)时,会将整个文档对象传递给转换器,而非单独的属性值。因此需要修改转换器,同时处理对象场景和字符串场景:
修改后的IdConverter
public class IdConverter : JsonConverter { public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { writer.WriteValue(((DocId)value).Id.ToString()); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // 处理查询时的对象场景:遍历找到SomeProp属性的值 if (reader.TokenType == JsonToken.StartObject) { while (reader.Read()) { if (reader.TokenType == JsonToken.PropertyName && reader.Value.ToString() == "SomeProp") { reader.Read(); // 移动到属性值位置 if (reader.TokenType == JsonToken.String) { return new DocId { Id = Guid.Parse(reader.Value as string) }; } break; } if (reader.TokenType == JsonToken.EndObject) break; } return null; } // 处理加载文档时的字符串场景 return reader.TokenType == JsonToken.String ? new DocId { Id = Guid.Parse(reader.Value as string) } : null; } public override bool CanConvert(Type type) { return typeof(DocId).IsAssignableFrom(type); } }
修正契约解析器的类型判断
原代码中type.IsAssignableFrom(typeof(DocId))的判断逻辑错误,应改为:
// 原代码 if (type.IsAssignableFrom(typeof(DocId))) // 修改为 if (typeof(DocId).IsAssignableFrom(type))
内容的提问来源于stack exchange,提问作者mrfelis
相关产品推荐
相关产品推荐

