如何 Serialize/Deserialize DataSets 保留其元数据,从 .NET Framework 4.7.2 中的客户端应用程序发送到 Core 3.1 中的 gRPC 服务

How to Serialize/Deserialize DataSets keeping its metadata, sent from a client application in .NET Framework 4.7.2 to a gRPC service in Core 3.1

要求是将使用数据集的 WCF 服务迁移到 gRPC 服务,在将数据从客户端发送到服务时保留元数据以识别行修改,以便我们可以相应地更新数据库。

我正在研究序列化 DataSet 的 PoC,如下所示:

using (MemoryStream ms = new MemoryStream()) {
      dataSet.RemotingFormat = SerializationFormat.Binary;
      BinaryFormatter fmt = new BinaryFormatter();
      fmt.Binder = DataSetSerializationBinder.Default;
      fmt.Serialize(ms, dataSet);
      ms.Flush();
      return ms.ToArray();
}

将字节数组从客户端发送到服务,然后在服务中使用以下反序列化:

using (MemoryStream ms = new MemoryStream(datasetBytes)) {
      ms.Position = 0;
      BinaryFormatter fmt = new BinaryFormatter();
      fmt.Binder = DataSetSerializationBinder.Default;                    
      var des = fmt.Deserialize(ms);
      DataSet ds = (DataSet)des;
      ms.Close();
}

像这样覆盖 SerializationBinder:

public override Type BindToType(string assemblyName, string typeName){
    if (assemblyName.Equals("DSSerializer"))
        return typeof(System.Data.DataSet);
    else
        return defaultBinder.BindToType(assemblyName, typeName);
}
public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
    assemblyName = "DSSerializer";
    typeName = serializedType.FullName;            
}

Serialize 和 Deserialize 方法位于从客户端和服务器引用的同一个库中, 但是在尝试反序列化时它抛出异常:未找到成员'XmlSchema'。 在添加 SerializationBinder 之前,异常是:BinaryFormatter.Deserialize: specified cast is not valid.

请注意,我需要传输架构信息和 DataRow.RowState information. When I tried to transfer my DataSet using XML as shown by this answer to Loading a datatable into a xml and xml back into a datatable:

dataSet.WriteXml(ms, XmlWriteMode.WriteSchema);

var dataSet = new DataSet();
dataSet.ReadXml(ms, XmlReadMode.ReadSchema);

我发现后者好像没有被转移。 Preserving DataRowState when serializing DataSet using DataContractSerializer.

证实了这一点

我知道 BinaryFormatter 不是最好的选择,什么是更好的方法,或者有什么方法可以让它工作?

我能够在这里重现你的问题:fiddle #1. You need to serialize the RowState 一个 DataSet 的信息,它似乎只被 BinaryFormatter 序列化,而不是由 XML 序列化,即使用 XmlWriteMode.WriteSchema 写,用 XmlReadMode.ReadSchema 读。

现在,根据Cutting Edge: Binary Serialization of DataSets written by Dino Esposito in October 2004, what BinaryFormatter actually serializes is the schema of the data set plus the diffgram的数据集。由于 DataSet 具有读取和写入其模式和 diffgram 的方法,因此应该可以在不丢失 RowState 信息的情况下将数据集与包含这两个属性的 DTO 相互转换。

以下 DTO 完成工作:

public class DataSetDTO
{
    public string XmlSchema { get; set; }
    public string XmlDiffGram { get; set; }
    
    public static DataSetDTO FromDataSet(DataSet dataSet, bool indent = false)
    {
        var diffGram = new StringBuilder();
        using (var xmlWriter = XmlWriter.Create(new StringWriter(diffGram), new XmlWriterSettings { Indent = indent }))
            dataSet.WriteXml(xmlWriter, XmlWriteMode.DiffGram);
        
        var schema = new StringBuilder();
        using (var xmlWriter = XmlWriter.Create(new StringWriter(schema), new XmlWriterSettings { Indent = indent }))
            dataSet.WriteXmlSchema(xmlWriter);
        
        return new DataSetDTO
        {
            XmlSchema = schema.ToString(),
            XmlDiffGram = diffGram.ToString(),
        };
    }
    
    public DataSet ToDataSet()
    {
        var dataSet = new DataSet();
        if (!string.IsNullOrEmpty(XmlSchema))
        {
            // TODO: determine whether and how to deny resolving external references, as is done in the reference source
            // https://referencesource.microsoft.com/#system.data/system/data/DataSet.cs,388
            dataSet.ReadXmlSchema(new StringReader(XmlSchema));
        }
        if (!string.IsNullOrEmpty(XmlDiffGram))
        {
            using var reader = XmlReader.Create(new StringReader(XmlDiffGram));
            dataSet.ReadXml(reader, XmlReadMode.DiffGram);
        }
        return dataSet;
    }
}

如果我使用以下方法往返 DataSet

static DataSet RoundTripViaDTO(DataSet set) => DataSetDTO.FromDataSet(set).ToDataSet();

架构、行数据和RowState信息都已成功反序列化。此处演示:fiddle #2.

备注:

  • DTO 可以使用您喜欢的任何序列化程序在您的客户端和服务器之间进行通信。但是,不要使用 BinaryFormatter,原因在 What are the deficiencies of the built-in BinaryFormatter based .Net serialization? as well as the documentation remarks 中有说明

    BinaryFormatter is insecure and can't be made secure. For more information, see the BinaryFormatter security guide.

    您当前使用的是 .NET Core 3.1,但展望未来,BinaryFormatter 在 .NET 5 中被标记为已过时并且 ASP.NET 应用默认禁止 BinaryFormatter 序列化.有关详细信息,请参阅 here

  • 由于 diffgram 数据可能很冗长,我在将其写入字符串时禁用了格式化。如果生成的字符串非常大,您可以考虑用某种流解决方案替换 DTO。

  • DataSetreference source 中,加载模式时禁用外部引用解析。

    this.ReadXmlSchema(new XmlTextReader(new StringReader(strSchema)), true);
    
    internal void ReadXmlSchema(XmlReader reader, bool denyResolving) { //...
    

    但是,API 从 XmlReader 加载模式并拒绝解析是内部的。出于安全原因,您可能想研究如何拒绝使用公开可用的 API 进行解析。请参阅 XmlSchemaSet Class: Security Considerations 了解从不受信任的来源读取模式可能引起的问题。

  • 此解决方案假定传入数据集完全属于 DataSet 类型,而不是某些 typed DataSet 子类。如果您正在使用类型化数据集,您将需要增强 DTO 以包含类型信息。