如何序列化/反序列化中从客户端应用程序发送的保留元数据的数据集.NET Framework 4.7.2中的gRPC服务



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

我正在进行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。反序列化:指定的强制转换无效

请注意,我需要传输模式信息和DataRow.RowState信息。当我尝试使用XML传输数据集时,如将数据表加载到XML并将XML返回到数据表的答案所示:

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

我发现后者似乎没有转移。使用DataContractSerializer序列化数据集时保留DataRowState证实了这一点。

我知道BinaryFormatter不是最好的选择,有什么更好的方法可以做到这一点,或者有办法让它发挥作用吗?

我能够在这里重现您的问题:fiddle#1。您需要序列化DataSetRowState信息,该信息似乎只通过BinaryFormatter进行序列化,但即使使用XmlWriteMode.WriteSchema进行写入和使用XmlReadMode.ReadSchema进行读取,也不会通过XML进行序列化。

现在,根据Dino Esposito在2004年10月编写的《前沿:数据集的二进制序列化》,BinaryFormatter实际序列化的是数据集的模式加上数据集的diffgram。由于DataSet有读取和写入其模式和diffgram的方法,因此应该可以在不丢失RowState信息的情况下将数据集转换为包含这两个属性的DTO或从包含这两种属性的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信息都被成功反序列化。在这里演示:小提琴#2。

注:

  • DTO可以使用您喜欢的任何序列化程序在客户端和服务器之间进行通信。但是,由于中解释的原因,不使用BinaryFormatter内置BinaryFormatter的不足之处是什么。网络序列化以及说明的文档备注

    BinaryFormatter不安全,无法使其安全。有关详细信息,请参阅BinaryFormatter安全指南。

    您当前正在使用。NET Core 3.1,但是,展望未来,BinaryFormatter在中被标记为过时。NET 5和CCD_ 13的序列化在默认情况下被禁止。NET应用程序。请参阅此处了解详细信息。

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

  • DataSet的引用源中,当加载架构时,将禁用外部引用的解析。

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

    但是,从XmlReader加载模式并拒绝解析的API是内部的。出于安全原因,您可能需要研究如何使用公开可用的API拒绝解析。请参阅XmlSchemaSet类:安全注意事项,了解从不受信任的源读取架构时可能出现的问题。

  • 此解决方案假定传入的数据集正是DataSet类型,而不是某个类型化的DataSet子类。如果使用类型化数据集,则需要增强DTO以包含类型信息。

相关内容

  • 没有找到相关文章

最新更新