在进行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
的值
这意味着此属性的取消序列化失败或没有尝试
这主要有三个原因:
-
Id
和ExternalId
属性是私有,因此默认情况下无法从JsonConvert
进程访问 -
Id
属性是只读的,因此反序列化进程无法对其进行写入。您应该通过添加一个setter来使它可写。 -
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
方法中,我们在使用属性之前要检查属性的存在性,没有要求调用者提供这些属性,因此首先进行检查是合理的。