具有外键约束的类型化数据集的C# JSON反序列化

rnmwe5a2  于 2023-02-17  发布在  C#
关注(0)|答案(1)|浏览(146)

我正尝试使用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,但可能存在性能影响或其他原因,作者不希望包括我的更改。

dw1jzc5e

dw1jzc5e1#

我建议修改你的数据集构造函数,使其适应json字符串,因为json字符串不包含任何关系,所以你每次都必须添加它们。

public class ChildParentDataSet2 : DataSet
{
    public DataTable Child { get; set; }

    public DataTable Parent { get; set; }

    private void CreateDataSet(DataTable parent, DataTable child)
    {
        Tables.Add(child);
        this.Child = Tables[0];
        Tables.Add(parent);
        this.Parent = Tables[1];

        if (this.Parent.Columns.Count == 0)
        {
            this.Parent.Columns.Add("Id", typeof(int));
            this.Child.Columns.Add("ParentId", typeof(int));
        }

        var parentColumn = this.Parent.Columns["Id"];
        var childColumn = this.Child.Columns["ParentId"];

        this.Parent.PrimaryKey = new DataColumn[] { parentColumn };
        this.Child.ParentRelations.Add(parentColumn, childColumn);

        this.Parent.TableName = "Parent";
        this.Child.TableName = "Child";
    }

    public ChildParentDataSet2(string json)
    {      
            var jObj=JObject.Parse(json);
            CreateDataSet(jObj["Parent"].ToObject<DataTable>(), jObj["Child"].ToObject<DataTable>());
    }

    public ChildParentDataSet2(JObject jObj)
    {
        CreateDataSet(jObj["Parent"].ToObject<DataTable>(), jObj["Child"].ToObject<DataTable>());
    }
    public ChildParentDataSet2()
    {
        CreateDataSet(new DataTable(), new DataTable());
    }
}

测试代码

var ds = new ChildParentDataSet2();
    ds.Parent.Rows.Add(1);
    ds.Child.Rows.Add(1);

    string json = Newtonsoft.Json.JsonConvert.SerializeObject(ds);

    var ds2 = new ChildParentDataSet2(json);
    
     ds2.Child.Rows.Add(3); // causes error key violation

如果愿意,可以创建自定义转换器

var ds2 = JsonConvert.DeserializeObject<ChildParentDataSet2>(json, new DataSetConverter());

public class DataSetConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(DataSet) || objectType == typeof(ChildParentDataSet2));
    }

    public override ChildParentDataSet2 ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var jObj = JObject.Load(reader);

        return new ChildParentDataSet2(jObj["Parent"].ToObject<DataTable>(), jObj["Child"].ToObject<DataTable>());
    }
    public override bool CanWrite
    {
        get { return false; }
    }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

对于这个转换器,你将需要添加这个构造函数(或替换现有的)

public ChildParentDataSet2(DataTable parent, DataTable child)
    {
        CreateDataSet(parent, child);
    }

相关问题