json中传递的Guid被反序列化为00000000-0000-0000-0000-000000000000000



在进行EF数据迁移时,我们有一堆json,其中包含这样的预先设置的guid:

{
"Id": "61dcc24e9b524f10b69a5c3f17be8603",
"MakeName": "AUDI",
"ExternalId": "61dcc24e9b524f10b69a5c3f17be8604",
"CreatedBy": "System",
"CreatedOn": "2022/01/05"
},
{
"Id": "27a617d75b2e45bab513e2f336fcd921",
"MakeName": "BMW",
"ExternalId": "27a617d75b2e45bab513e2f336fcd927",
"CreatedBy": "System",
"CreatedOn": "2022/01/05"
},

Make Class

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; }
}

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

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;
}

结果用于自定义配置,如下所示:

builder.HasData(LargeDataHelper.SeedFromJson<Make>("Makes.json"));

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

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'.

在调试result = JsonConvert.DeserializeObject时,它似乎会将提供的guid替换为零。

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

这意味着此属性的取消序列化失败或没有尝试

这主要有三个原因:

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

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

  3. Guid值的格式不正确,在.Net中,Guid的串行化结构应类似于以下

{
"Id": "9694e5ec-9818-4987-abd8-6848132d3e4c",
}

值解析可以用一个简单的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; }
}
}

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

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; }
}

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

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

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

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

此实现的额外好处是,它不需要此类之外的修改或依赖项,并且只影响默认的.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());
}
}

还要注意的是,在OnDeserilized方法中,我们在使用属性之前要检查属性的存在性,没有要求调用者提供这些属性,因此首先进行检查是合理的。

最新更新