Guid 在 json 中传递,被反序列化为 00000000-0000-0000-0000-000000000000

Bik*_*net 1 c# entity-framework-core

在进行 EF 数据迁移时,我们有一堆带有预先设置的 guid 的 json,如下所示:

  {
    "Id": "61dcc24e9b524f10b69a5c3f17be8603",
    "MakeName": "AUDI",
    "ExternalId": "61dcc24e9b524f10b69a5c3f17be8604",
    "CreatedBy": "System",
    "CreatedOn": "2022/01/05"
  },
  {
    "Id": "27a617d75b2e45bab513e2f336fcd921",
    "MakeName": "BMW",
    "ExternalId": "27a617d75b2e45bab513e2f336fcd927",
    "CreatedBy": "System",
    "CreatedOn": "2022/01/05"
  },
Run Code Online (Sandbox Code Playgroud)

制作类

 public class Make : AuditableEntity
    {
        public Make() { }

        Guid Id { get; }

        public String MakeName { get; set; }

        public String CreatedBy { get; set; } = null!;

        public DateTimeOffset CreatedOn { get; set; }

        Guid ExternalId{ get; set; }
    }
Run Code Online (Sandbox Code Playgroud)

然后我们使用通用种子函数来提取数据:

public static List<TEntity> SeedFromJson<TEntity>(string fileName)
        {
            string path = "../path/Seeds";
            string fullPath = Path.Combine(path, fileName);

            var result = new List<TEntity>();
            using (StreamReader reader = new StreamReader(fullPath))
            {
                string json = reader.ReadToEnd();
                result = JsonConvert.DeserializeObject<List<TEntity>>(json);
            }

            return result;
        }
Run Code Online (Sandbox Code Playgroud)

结果在自定义配置中使用,如下所示:

builder.HasData(LargeDataHelper.SeedFromJson<Make>("Makes.json"));
Run Code Online (Sandbox Code Playgroud)

问题是当我们运行迁移时。显示以下错误:

The seed entity for entity type 'Make' cannot be added because a default value was provided for the required property 'Id'. Please provide a value different from '00000000-0000-0000-0000-000000000000'.
Run Code Online (Sandbox Code Playgroud)

似乎在调试时result = JsonConvert.DeserializeObject用零替换了提供的 guid。

Chr*_*ler 5

零是 a 的默认状态,Guid不可为空,因此new Guid()or的值Guid.Empty

这意味着该属性的反序列化已失败或未尝试

造成这种情况的主要原因有3个:

  1. Id属性ExternalId私有的JsonConvert,因此默认情况下无法从进程访问

  2. Id属性是只读的,因此反序列化过程无法写入它。您应该通过添加 setter 使其可写。

  3. Guid值格式不正确,在 .Net 中, a 的序列化结构Guid应如下所示

{
    "Id": "9694e5ec-9818-4987-abd8-6848132d3e4c",
}
Run Code Online (Sandbox Code Playgroud)

可以使用简单的JsonConverter来解决值解析,如以下小提琴所示: https: //dotnetfiddle.net/KKYTUT

public class GuidConverter : JsonConverter<Guid>
{
    public GuidConverter()
    {
    }

    public override void WriteJson(JsonWriter writer, Guid value, JsonSerializer serializer)
    {
        writer.WriteValue(value.ToString("N"));
    }

    public override Guid ReadJson(JsonReader reader, Type objectType, Guid existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        string value = reader.Value.ToString();
        return Guid.Parse(value);
    }

    public override bool CanRead
    {
        get { return true; }
    }

}
Run Code Online (Sandbox Code Playgroud)

然后通过使属性公开且可写来更改模型以支持此转换器:

public class Make 
{
    public Make() {}
    [JsonConverter(typeof(GuidConverter))]
    public Guid Id { get; set; }
    public String MakeName { get; set; }
    public String CreatedBy { get; set; }
    public DateTimeOffset CreatedOn { get; set; }
    [JsonConverter(typeof(GuidConverter))]
    public Guid ExternalId{ get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这将适用于您当前的SeedFromJson方法,但也会影响此类型的序列化。

如果确实需要维护private值的访问修饰符和只读 状态Id,那么我们需要为此类型编写一个自定义类型转换器,或者可能需要添加其他结构更改来支持这一点。

执行此操作的一个简单方法可能是将OnDeserilized回调与ExtensionData 属性结合使用,这将捕获未能反序列化的 Json 令牌,我们可以在流程结束时引用它们。

这个小提琴显示了实现: https: //dotnetfiddle.net/sA7ZQl

此实现的额外好处是,它不需要此类之外的任何修改或依赖项,并且仅影响Deserilization和默认的 .Net序列化,因此来自控制器的响应将不受影响。

请注意,该Id属性已使用支持字段重新实现,这样我们就可以在构造函数之外设置该值。

public class Make
{
    public Make() { }
    private Guid Id { get { return _Id; } }
    private Guid _Id;
    public String MakeName { get; set; }
    public String CreatedBy { get; set; }
    public DateTimeOffset CreatedOn { get; set; }
    private Guid ExternalId { get; set; }

    [JsonExtensionData]
    private IDictionary<string, JToken> _additionalData;

    [System.Runtime.Serialization.OnDeserialized]
    private void OnDeserialized(System.Runtime.Serialization.StreamingContext context)
    {
        // Id and ExternalId are not public, so we capture the values into the Extension data to simplify psot processing
        if (_additionalData.TryGetValue("Id", out JToken @id))
            this._Id = Guid.Parse(id.ToString());
        if (_additionalData.TryGetValue("ExternalId", out JToken @ExternalId))
            this.ExternalId = Guid.Parse(@ExternalId.ToString());
    }
}
Run Code Online (Sandbox Code Playgroud)

另请注意,在OnDeserilized方法中,我们在使用属性之前检查属性是否存在,调用者不会提供它们,因此首先检查是合理的。