我正尝试使用C#和NewtonSoft.Json包将类型化的DataSet序列化和反序列化为JSON。除了包含外键约束的DataSet以及父表在子表之后添加到设计器之外,此操作都很好。在自动生成的Designer.cs中,父表在子表之后添加到基DataTableCollection。
将DataSet序列化为JSON时,首先序列化子表。然后,将JSON反序列化回类型化DataSet的示例时,将引发InvalidConstraintException("ForeignKeyConstraint要求子键值存在于父表中")。
要设置测试场景,我创建了一个非常简单的类型化数据集,名为ChildParentDataSet。它包含两个表。第一个表名为Child,有一个名为ParentId的列。第二个表名为Parent,有一个名为Id的列。在Parent.Id和Child.ParentId之间创建了一个外键。在设计器中,必须将Parent表添加到Child表之后。此代码在最后一行中导致InvalidConstraintException:
ChildParentDataSet ds = new ChildParentDataSet();
ds.Parent.Rows.Add(1);
ds.Child.Rows.Add(1);
string json = Newtonsoft.Json.JsonConvert.SerializeObject(ds);
ChildParentDataSet? ds2 = Newtonsoft.Json.JsonConvert.DeserializeObject<ChildParentDataSet>(json);
我的首选方法是在反序列化期间处理此问题,方法是实现我自己的CustomCreationConverter,该转换器在执行反序列化之前将DataSet. EnforceConstraints初始化为false,然后在反序列化完成时将EnforceConstraints设置回true。下面是我希望完成此操作的类:
public class DataSetDeserializer
{
public static T DeserializeDataSet<T>(string json)
{
var ds = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(json, new DataSetConverter<T>());
System.Data.DataSet? dataSet = (System.Data.DataSet?)(Object?)ds;
dataSet.EnforceConstraints = true;
return ds;
}
private class DataSetConverter<T> : Newtonsoft.Json.Converters.CustomCreationConverter<T>
{
public override T Create(Type objectType)
{
var ds = Activator.CreateInstance(objectType);
System.Data.DataSet? dataSet = (System.Data.DataSet?)ds;
dataSet.EnforceConstraints = false;
return (T)ds;
}
}
}
然后,我更改了测试代码的最后一行,使用DataSetDeserializer类而不是Newtonsoft进行反序列化:
ChildParentDataSet? ds2 = DataSetDeserializer.DeserializeDataSet<ChildParentDataSet>(json);
运行此代码时,调用DeserializeObject时抛出Newtonsoft.Json.JsonSerializationException。异常消息为"无法将JSON对象填充到类型'ChildParentDataSet'上。路径'Child',第1行,位置9'"。我还没弄清楚这一点。
我考虑的另一个选择是在序列化而不是反序列化期间处理这个问题。我实现了一个方法,该方法对数据集中的表进行排序,使最高级别的父表排在最前面,最低级别的子表排在最后。然后,我以正确的顺序一次序列化一个表,并连接所有JSON字符串。这样就可以了。但关键的是,它不能处理表通过自引用外键成为其自身的父表和子表的情况。我可能知道如何处理这种情况,但实际上会更加困难。另外,与用一次调用序列化整个数据集相比,我目前的实现效率相当低。
一个可行但我真的不想做的选择是使用XML代替JSON。这当然会更容易,因为DataSet有一个GetXml函数,但XML要冗长得多,我通过gRPC在网络上传递这些数据。另外,它需要对许多客户端和服务进行非常复杂的重新部署。
我当然愿意接受建议。我肯定还有更好的选择,我还没有考虑过。
编辑:@Serge建议我发布ChildParentDataSet
的代码。不幸的是,这是许多自动生成的代码,太大了,不能发布在这里,但他随后建议我在代码中创建一些等效的东西。我照做了,下面是:
internal class ChildParentDataSet2: DataSet
{
public DataTable Child { get; set; }
public DataTable Parent { get; set; }
public ChildParentDataSet2()
{
this.Child = this.Tables.Add("Child");
DataColumn childColumn = this.Child.Columns.Add("ParentId", typeof(int));
this.Parent = this.Tables.Add("Parent");
DataColumn parentColumn = this.Parent.Columns.Add("Id", typeof(int));
this.Parent.PrimaryKey = new DataColumn[] { parentColumn };
this.Child.ParentRelations.Add(parentColumn, childColumn);
}
}
奇怪的是,当使用这个类而不是自动生成的类时,我可以在使用DataSetDeserializer和CustomCreationConverter时成功地反序列化它。因此,问题显然在于类型化数据集太大,无法在这里发布。
编辑2:ChildParentDataSet(使用Visual Studio中的DataSet设计器创建)和ChildParentDataSet2(如上所示手动创建)都生成相同的序列化JSON:{"Child":[{"ParentId":1}],"Parent":[{"Id":1}]}
。现在我只需要弄清楚为什么我的DataSetDeserializer可以很好地与ChildParentDataSet2一起工作,而不能与ChildParentDataSet一起工作。感谢大家到目前为止的帮助!
编辑三:我想我知道为什么我的CustomCreationConverter
适用于我手动编写的ChildParentDataSet2
,而不适用于我通过设计器创建的ChildParentDataSet
了。使用CustomCreationConverter
,我错过了Newtonsoft在它自己的DataSetConverter
类中提供的所有功能。所以我目前的解决方案是在Github上创建Newtonsoft的DataSetConverter
类的一个分支。该分支只是在反序列化之前将EnforceConstraints设置为false,并在反序列化之后将其设置回true。我还创建了一个pull request,但可能存在性能影响或其他原因,作者不希望包括我的更改。
1条答案
按热度按时间dw1jzc5e1#
我建议修改你的数据集构造函数,使其适应json字符串,因为json字符串不包含任何关系,所以你每次都必须添加它们。
测试代码
如果愿意,可以创建自定义转换器
对于这个转换器,你将需要添加这个构造函数(或替换现有的)