You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 21:21:25