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

使用Cosmos SDK和Newtonsoft时static readonly record被篡改问题

问题原因分析

你遇到的问题核心是Newtonsoft.Json(Azure Cosmos SDK v3.x默认序列化器)的反序列化行为,结合C# record的特性与static readonly实例的复用导致的。

触发场景的代码回顾

首先看核心定义:
Author是一个record类型,包含全局static readonly实例:

public record Author(
    [Required] string Id,
    [Required] string DisplayName,
    [Required] string MailAddress)
{
    public static readonly Author Unknown = Create(nameof(Unknown));

    public static Author Create(string name)
    {
        return new Author(
            name.ToLowerInvariant(),
            name,
            $"{name}@test.internal");
    }
}

DocumentBase的默认构造直接复用这个全局实例:

public class DocumentBase
{
    [Newtonsoft.Json.JsonProperty("id")]
    public Guid Id { get; set; } = default!;
    public string PartitionKey { get; set; } = "PartitionKey";

    public Author Modified { get; set; } = Author.Unknown;
}

写入并反序列化的逻辑:

public static async Task Main()
{
    var container = await CreateContainer();

    var defaultValue = new DocumentBase
    {
        Id = Guid.NewGuid(),
        Modified = Author.Create("Modified"),
    };

    var copyOfUnknown = Author.Unknown with { };
    Author.Unknown.ShouldBe(copyOfUnknown);

    await container.CreateItemAsync(defaultValue, new PartitionKey(defaultValue.PartitionKey));

    // 此处对比失败,Unknown已被篡改
    Author.Unknown.ShouldBe(copyOfUnknown);
}

具体触发过程

  1. 全局实例的引用复用:DocumentBase默认构造中,Modified直接指向Author.Unknown的内存引用——所有默认构造的DocumentBase对象,这个字段都共享同一个全局实例。
  2. 反序列化的实例修改:调用CreateItemAsync后,Cosmos返回写入的文档,SDK自动反序列化为DocumentBase对象:
    • 反序列化器先调用DocumentBase无参构造,Modified被设置为Author.Unknown的引用。
    • 接着处理Modified的JSON数据时,Newtonsoft.Json为了性能,会直接通过反射修改已有实例的属性值——即使record的属性是init修饰的,反射可以绕过这个不可变限制。
  3. 全局实例被永久篡改:因为Author.Unknown是static readonly的全局实例,它的属性被修改后,所有引用它的地方都会看到篡改后的值。

规避方案的原理

  • 改用属性getter返回新实例:每次获取Unknown都生成新的record实例,DocumentBase的Modified指向局部实例,反序列化修改的是局部实例,不影响全局。
  • 不在默认构造设置Modified:反序列化时会直接创建新的Author实例赋值,不会复用全局的Unknown。
  • 用with {}创建副本:Author.Unknown with {}会生成新的record实例(值复制),Modified指向副本,反序列化修改副本,原全局实例不受影响。

额外建议

如果使用.NET 6+,可以将Cosmos SDK的序列化器切换为System.Text.Json,它对record的init属性处理更严格,默认会创建新实例而非修改已有实例,能从根源避免这类问题。

内容的提问来源于stack exchange,提问作者Oliver

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 11:47:38