From ad3f4866d17236f27f2073778e9517a58ff2cbe9 Mon Sep 17 00:00:00 2001 From: rainbird Date: Sun, 24 Nov 2024 02:29:25 +0100 Subject: [PATCH] Added support for DataSet/DataTable DiffGram (to transfer RowStates and row versions) serialization with BSON serializer --- CoreRemoting.Tests/BsonSerializationTests.cs | 37 ++ .../DataSetSerializationTests.cs | 152 +++++++ CoreRemoting.Tests/TestDataSet.cs | 397 ++++++++++++++++++ CoreRemoting.Tests/TestDataSet.xsd | 17 + CoreRemoting.sln.DotSettings.user | 4 + .../Bson/BsonSerializerAdapter.cs | 18 +- .../DataSetDiffGramJsonConverter.cs | 92 ++++ .../SerializedDiffGram.cs | 62 +++ CoreRemoting/Serialization/Bson/Envelope.cs | 8 + 9 files changed, 786 insertions(+), 1 deletion(-) create mode 100644 CoreRemoting.Tests/DataSetSerializationTests.cs create mode 100644 CoreRemoting.Tests/TestDataSet.cs create mode 100644 CoreRemoting.Tests/TestDataSet.xsd create mode 100644 CoreRemoting/Serialization/Bson/DataSetDiffGramSupport/DataSetDiffGramJsonConverter.cs create mode 100644 CoreRemoting/Serialization/Bson/DataSetDiffGramSupport/SerializedDiffGram.cs diff --git a/CoreRemoting.Tests/BsonSerializationTests.cs b/CoreRemoting.Tests/BsonSerializationTests.cs index 00a1713..d46cbd3 100644 --- a/CoreRemoting.Tests/BsonSerializationTests.cs +++ b/CoreRemoting.Tests/BsonSerializationTests.cs @@ -1,4 +1,5 @@ using System; +using System.Data; using CoreRemoting.RpcMessaging; using CoreRemoting.Serialization.Bson; using CoreRemoting.Tests.Tools; @@ -192,5 +193,41 @@ public void BsonSerializerAdapter_should_deserialize_Int32_value_in_envelope_cor Assert.Equal(envelope.Value, deserializedValue.Value); Assert.IsType(envelope.Value); } + + [Fact] + public void BsonSerializerAdapter_should_serialize_DataSet_as_Diffgram() + { + var originalTable = new DataTable("TestTable"); + originalTable.Columns.Add("UserName", typeof(string)); + originalTable.Columns.Add("Age", typeof(short)); + var originalDataSet = new DataSet("TestDataSet"); + originalDataSet.Tables.Add(originalTable); + + var originalRow = originalTable.NewRow(); + originalRow["UserName"] = "Tester"; + originalRow["Age"] = 44; + originalTable.Rows.Add(originalRow); + + originalTable.AcceptChanges(); + + originalRow["Age"] = 43; + + var envelope = new Envelope(originalDataSet); + + var serializer = new BsonSerializerAdapter(); + var raw = serializer.Serialize(envelope); + var deserializedEnvelope = serializer.Deserialize(raw); + + var deserializedDataSet = (DataSet)deserializedEnvelope.Value; + var deserializedTable = deserializedDataSet.Tables["TestTable"]; + var deserializedRow = deserializedTable!.Rows[0]; + + Assert.Equal(originalDataSet.DataSetName, deserializedDataSet.DataSetName); + Assert.Equal(originalTable.TableName, deserializedTable.TableName); + Assert.Equal(originalRow.RowState, deserializedRow.RowState); + Assert.Equal(originalRow["Age", DataRowVersion.Original], deserializedRow["Age", DataRowVersion.Original]); + Assert.Equal(originalRow["Age", DataRowVersion.Current], deserializedRow["Age", DataRowVersion.Current]); + Assert.Equal(originalRow["UserName", DataRowVersion.Current], deserializedRow["UserName", DataRowVersion.Current]); + } } } \ No newline at end of file diff --git a/CoreRemoting.Tests/DataSetSerializationTests.cs b/CoreRemoting.Tests/DataSetSerializationTests.cs new file mode 100644 index 0000000..10c2b71 --- /dev/null +++ b/CoreRemoting.Tests/DataSetSerializationTests.cs @@ -0,0 +1,152 @@ +using System.Data; +using System.IO; +using System.Reflection; +using CoreRemoting.Serialization.Bson.DataSetDiffGramSupport; +using Newtonsoft.Json; +using Xunit; + +namespace CoreRemoting.Tests; + +public class DataSetSerializationTests +{ + [Fact] + public void DataSetDiffGramJsonConverter_should_serialize_typed_DataSet_to_DiffGram() + { + var originalDataSet = new TestDataSet(); + var originalRow = originalDataSet.TestTable.NewTestTableRow(); + originalRow.UserName = "TestUser"; + originalRow.Age = 44; + originalDataSet.TestTable.AddTestTableRow(originalRow); + originalDataSet.AcceptChanges(); + + originalRow.Age = 43; + + var json = + JsonConvert.SerializeObject( + value: originalDataSet, + formatting: Formatting.Indented, + converters: new DataSetDiffGramJsonConverter()); + + var deserializedDataSet = + JsonConvert.DeserializeObject( + value: json, + converters: new DataSetDiffGramJsonConverter()); + + var deserializedRow = deserializedDataSet.TestTable[0]; + + Assert.Equal(originalDataSet.DataSetName, deserializedDataSet.DataSetName); + Assert.Equal(originalDataSet.Tables.Count, deserializedDataSet.Tables.Count); + Assert.Equal(originalRow.RowState, deserializedRow.RowState); + Assert.Equal(originalRow["Age", DataRowVersion.Original], deserializedRow["Age", DataRowVersion.Original]); + Assert.Equal(originalRow["Age", DataRowVersion.Current], deserializedRow["Age", DataRowVersion.Current]); + Assert.Equal(originalRow["UserName", DataRowVersion.Current], deserializedRow["UserName", DataRowVersion.Current]); + } + + [Fact] + public void DataSetDiffGramJsonConverter_should_serialize_typed_DataTable_to_DiffGram() + { + var originalTable = new TestDataSet.TestTableDataTable(); + var originalRow = originalTable.NewTestTableRow(); + originalRow.UserName = "TestUser"; + originalRow.Age = 44; + originalTable.AddTestTableRow(originalRow); + originalTable.AcceptChanges(); + + originalRow.Age = 43; + + var json = + JsonConvert.SerializeObject( + value: originalTable, + formatting: Formatting.Indented, + converters: new DataSetDiffGramJsonConverter()); + + var deserializedTable = + JsonConvert.DeserializeObject( + value: json, + converters: new DataSetDiffGramJsonConverter()); + + var deserializedRow = deserializedTable[0]; + + Assert.Equal(originalRow.RowState, deserializedRow.RowState); + Assert.Equal(originalRow["Age", DataRowVersion.Original], deserializedRow["Age", DataRowVersion.Original]); + Assert.Equal(originalRow["Age", DataRowVersion.Current], deserializedRow["Age", DataRowVersion.Current]); + Assert.Equal(originalRow["UserName", DataRowVersion.Current], deserializedRow["UserName", DataRowVersion.Current]); + } + + [Fact] + public void DataSetDiffGramJsonConverter_should_serialize_untyped_DataSet_to_DiffGram() + { + var originalTable = new DataTable("TestTable"); + originalTable.Columns.Add("UserName", typeof(string)); + originalTable.Columns.Add("Age", typeof(short)); + var originalDataSet = new DataSet("TestDataSet"); + originalDataSet.Tables.Add(originalTable); + + var originalRow = originalTable.NewRow(); + originalRow["UserName"] = "Tester"; + originalRow["Age"] = 44; + originalTable.Rows.Add(originalRow); + + originalTable.AcceptChanges(); + + originalRow["Age"] = 43; + + var json = + JsonConvert.SerializeObject( + value: originalDataSet, + formatting: Formatting.Indented, + converters: new DataSetDiffGramJsonConverter()); + + var deserializedDataSet = + JsonConvert.DeserializeObject( + value: json, + converters: new DataSetDiffGramJsonConverter()); + + var deserializedTable = deserializedDataSet.Tables["TestTable"]; + var deserializedRow = deserializedTable!.Rows[0]; + + Assert.Equal(originalDataSet.DataSetName, deserializedDataSet.DataSetName); + Assert.Equal(originalTable.TableName, deserializedTable.TableName); + Assert.Equal(originalRow.RowState, deserializedRow.RowState); + Assert.Equal(originalRow["Age", DataRowVersion.Original], deserializedRow["Age", DataRowVersion.Original]); + Assert.Equal(originalRow["Age", DataRowVersion.Current], deserializedRow["Age", DataRowVersion.Current]); + Assert.Equal(originalRow["UserName", DataRowVersion.Current], deserializedRow["UserName", DataRowVersion.Current]); + } + + [Fact] + public void DataSetDiffGramJsonConverter_should_serialize_untyped_DataTable_to_DiffGram() + { + var originalTable = new DataTable("TestTable"); + originalTable.Columns.Add("UserName", typeof(string)); + originalTable.Columns.Add("Age", typeof(short)); + + var originalRow = originalTable.NewRow(); + originalRow["UserName"] = "Tester"; + originalRow["Age"] = 44; + originalTable.Rows.Add(originalRow); + + originalTable.AcceptChanges(); + + originalRow["Age"] = 43; + + var json = + JsonConvert.SerializeObject( + value: originalTable, + formatting: Formatting.Indented, + converters: new DataSetDiffGramJsonConverter()); + + var deserializedDataSet = + JsonConvert.DeserializeObject( + value: json, + converters: new DataSetDiffGramJsonConverter()); + + var deserializedTable = deserializedDataSet.Tables["TestTable"]; + var deserializedRow = deserializedTable!.Rows[0]; + + Assert.Equal(originalTable.TableName, deserializedTable.TableName); + Assert.Equal(originalRow.RowState, deserializedRow.RowState); + Assert.Equal(originalRow["Age", DataRowVersion.Original], deserializedRow["Age", DataRowVersion.Original]); + Assert.Equal(originalRow["Age", DataRowVersion.Current], deserializedRow["Age", DataRowVersion.Current]); + Assert.Equal(originalRow["UserName", DataRowVersion.Current], deserializedRow["UserName", DataRowVersion.Current]); + } +} \ No newline at end of file diff --git a/CoreRemoting.Tests/TestDataSet.cs b/CoreRemoting.Tests/TestDataSet.cs new file mode 100644 index 0000000..6dbd5dd --- /dev/null +++ b/CoreRemoting.Tests/TestDataSet.cs @@ -0,0 +1,397 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +// +//This source code was auto-generated by MonoXSD +// +// Command line to generate: xsd TestDataSet.xsd /dataset /language:CS +// +namespace CoreRemoting.Tests; + +[System.Serializable()] +[System.ComponentModel.DesignerCategoryAttribute("code")] +[System.Diagnostics.DebuggerStepThrough()] +[System.ComponentModel.ToolboxItem(true)] +[System.Xml.Serialization.XmlSchemaProviderAttribute("GetTypedDataSetSchema")] +[System.Xml.Serialization.XmlRootAttribute("TestDataSet")] +public class TestDataSet : System.Data.DataSet { + + private TestTableDataTable tableTestTable; + + public TestDataSet() { + this.BeginInit(); + this.InitClass(); + System.ComponentModel.CollectionChangeEventHandler schemaChangedHandler = new System.ComponentModel.CollectionChangeEventHandler(this.SchemaChanged); + this.Tables.CollectionChanged += schemaChangedHandler; + this.Relations.CollectionChanged += schemaChangedHandler; + this.EndInit(); + } + + protected TestDataSet(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : + base(info, context) { + if ((this.IsBinarySerialized(info, context) == true)) { + this.InitVars(false); + System.ComponentModel.CollectionChangeEventHandler schemaChangedHandler1 = new System.ComponentModel.CollectionChangeEventHandler(this.SchemaChanged); + this.Tables.CollectionChanged += schemaChangedHandler1; + this.Relations.CollectionChanged += schemaChangedHandler1; + return; + } + string strSchema = ((string)(info.GetValue("XmlSchema", typeof(string)))); + if ((strSchema != null)) { + System.Data.DataSet ds = new System.Data.DataSet(); + ds.ReadXmlSchema(new System.Xml.XmlTextReader(new System.IO.StringReader(strSchema))); + if ((ds.Tables["TestTable"] != null)) { + this.Tables.Add(new TestTableDataTable(ds.Tables["TestTable"])); + } + this.DataSetName = ds.DataSetName; + this.Prefix = ds.Prefix; + this.Namespace = ds.Namespace; + this.Locale = ds.Locale; + this.CaseSensitive = ds.CaseSensitive; + this.EnforceConstraints = ds.EnforceConstraints; + this.Merge(ds, false, System.Data.MissingSchemaAction.Add); + this.InitVars(); + } + else { + this.BeginInit(); + this.InitClass(); + this.EndInit(); + } + this.GetSerializationData(info, context); + System.ComponentModel.CollectionChangeEventHandler schemaChangedHandler = new System.ComponentModel.CollectionChangeEventHandler(this.SchemaChanged); + this.Tables.CollectionChanged += schemaChangedHandler; + this.Relations.CollectionChanged += schemaChangedHandler; + } + + [System.ComponentModel.Browsable(false)] + [System.ComponentModel.DesignerSerializationVisibilityAttribute(System.ComponentModel.DesignerSerializationVisibility.Content)] + public TestTableDataTable TestTable { + get { + return this.tableTestTable; + } + } + + public override System.Data.DataSet Clone() { + TestDataSet cln = ((TestDataSet)(base.Clone())); + cln.InitVars(); + return cln; + } + + internal void InitVars() { + this.InitVars(true); + } + + protected override bool ShouldSerializeTables() { + return false; + } + + protected override bool ShouldSerializeRelations() { + return false; + } + + public static System.Xml.Schema.XmlSchemaComplexType GetTypedDataSetSchema(System.Xml.Schema.XmlSchemaSet xs) { + TestDataSet ds = new TestDataSet(); + xs.Add(ds.GetSchemaSerializable()); + System.Xml.Schema.XmlSchemaComplexType type = new System.Xml.Schema.XmlSchemaComplexType(); + System.Xml.Schema.XmlSchemaSequence sequence = new System.Xml.Schema.XmlSchemaSequence(); + System.Xml.Schema.XmlSchemaAny any = new System.Xml.Schema.XmlSchemaAny(); + any.Namespace = ds.Namespace; + sequence.Items.Add(any); + type.Particle = sequence; + return type; + } + + protected override void ReadXmlSerializable(System.Xml.XmlReader reader) { + this.Reset(); + System.Data.DataSet ds = new System.Data.DataSet(); + ds.ReadXml(reader); + if ((ds.Tables["TestTable"] != null)) { + this.Tables.Add(new TestTableDataTable(ds.Tables["TestTable"])); + } + this.DataSetName = ds.DataSetName; + this.Prefix = ds.Prefix; + this.Namespace = ds.Namespace; + this.Locale = ds.Locale; + this.CaseSensitive = ds.CaseSensitive; + this.EnforceConstraints = ds.EnforceConstraints; + this.Merge(ds, false, System.Data.MissingSchemaAction.Add); + this.InitVars(); + } + + protected override System.Xml.Schema.XmlSchema GetSchemaSerializable() { + System.IO.MemoryStream stream = new System.IO.MemoryStream(); + this.WriteXmlSchema(new System.Xml.XmlTextWriter(stream, null)); + stream.Position = 0; + return System.Xml.Schema.XmlSchema.Read(new System.Xml.XmlTextReader(stream), null); + } + + internal void InitVars(bool initTable) { + this.tableTestTable = ((TestTableDataTable)(this.Tables["TestTable"])); + if ((initTable == true)) { + if ((this.tableTestTable != null)) { + this.tableTestTable.InitVars(); + } + } + } + + private void InitClass() { + this.DataSetName = "TestDataSet"; + this.Prefix = ""; + this.Namespace = ""; + this.Locale = new System.Globalization.CultureInfo("de-DE"); + this.CaseSensitive = false; + this.EnforceConstraints = true; + this.tableTestTable = new TestTableDataTable(); + this.Tables.Add(this.tableTestTable); + } + + private bool ShouldSerializeTestTable() { + return false; + } + + private void SchemaChanged(object sender, System.ComponentModel.CollectionChangeEventArgs e) { + if ((e.Action == System.ComponentModel.CollectionChangeAction.Remove)) { + this.InitVars(); + } + } + + public delegate void TestTableRowChangeEventHandler(object sender, TestTableRowChangeEvent e); + + [System.Serializable()] + [System.Diagnostics.DebuggerStepThrough()] + public class TestTableDataTable : System.Data.DataTable, System.Collections.IEnumerable { + + private System.Data.DataColumn columnUserName; + + private System.Data.DataColumn columnAge; + + public TestTableDataTable() : + base("TestTable") { + this.InitClass(); + } + + protected TestTableDataTable(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : + base(info, context) { + this.InitVars(); + } + + internal TestTableDataTable(System.Data.DataTable table) : + base(table.TableName) { + if ((table.CaseSensitive != table.DataSet.CaseSensitive)) { + this.CaseSensitive = table.CaseSensitive; + } + if ((table.Locale.ToString() != table.DataSet.Locale.ToString())) { + this.Locale = table.Locale; + } + if ((table.Namespace != table.DataSet.Namespace)) { + this.Namespace = table.Namespace; + } + this.Prefix = table.Prefix; + this.MinimumCapacity = table.MinimumCapacity; + this.DisplayExpression = table.DisplayExpression; + } + + [System.ComponentModel.Browsable(false)] + public int Count { + get { + return this.Rows.Count; + } + } + + public System.Data.DataColumn UserNameColumn { + get { + return this.columnUserName; + } + } + + public System.Data.DataColumn AgeColumn { + get { + return this.columnAge; + } + } + + public TestTableRow this[int index] { + get { + return ((TestTableRow)(this.Rows[index])); + } + } + + public event TestTableRowChangeEventHandler TestTableRowChanged; + + public event TestTableRowChangeEventHandler TestTableRowChanging; + + public event TestTableRowChangeEventHandler TestTableRowDeleted; + + public event TestTableRowChangeEventHandler TestTableRowDeleting; + + public void AddTestTableRow(TestTableRow row) { + this.Rows.Add(row); + } + + public TestTableRow AddTestTableRow(string UserName, short Age) { + TestTableRow rowTestTableRow = ((TestTableRow)(this.NewRow())); + rowTestTableRow.ItemArray = new object[] { + UserName, + Age}; + this.Rows.Add(rowTestTableRow); + return rowTestTableRow; + } + + public System.Collections.IEnumerator GetEnumerator() { + return this.Rows.GetEnumerator(); + } + + public override System.Data.DataTable Clone() { + TestTableDataTable cln = ((TestTableDataTable)(base.Clone())); + cln.InitVars(); + return cln; + } + + protected override System.Data.DataTable CreateInstance() { + return new TestTableDataTable(); + } + + internal void InitVars() { + this.columnUserName = this.Columns["UserName"]; + this.columnAge = this.Columns["Age"]; + } + + private void InitClass() { + this.columnUserName = new System.Data.DataColumn("UserName", typeof(string), null, System.Data.MappingType.Element); + this.Columns.Add(this.columnUserName); + this.columnAge = new System.Data.DataColumn("Age", typeof(short), null, System.Data.MappingType.Element); + this.Columns.Add(this.columnAge); + } + + public TestTableRow NewTestTableRow() { + return ((TestTableRow)(this.NewRow())); + } + + protected override System.Data.DataRow NewRowFromBuilder(System.Data.DataRowBuilder builder) { + return new TestTableRow(builder); + } + + protected override System.Type GetRowType() { + return typeof(TestTableRow); + } + + protected override void OnRowChanged(System.Data.DataRowChangeEventArgs e) { + base.OnRowChanged(e); + if ((this.TestTableRowChanged != null)) { + this.TestTableRowChanged(this, new TestTableRowChangeEvent(((TestTableRow)(e.Row)), e.Action)); + } + } + + protected override void OnRowChanging(System.Data.DataRowChangeEventArgs e) { + base.OnRowChanging(e); + if ((this.TestTableRowChanging != null)) { + this.TestTableRowChanging(this, new TestTableRowChangeEvent(((TestTableRow)(e.Row)), e.Action)); + } + } + + protected override void OnRowDeleted(System.Data.DataRowChangeEventArgs e) { + base.OnRowDeleted(e); + if ((this.TestTableRowDeleted != null)) { + this.TestTableRowDeleted(this, new TestTableRowChangeEvent(((TestTableRow)(e.Row)), e.Action)); + } + } + + protected override void OnRowDeleting(System.Data.DataRowChangeEventArgs e) { + base.OnRowDeleting(e); + if ((this.TestTableRowDeleting != null)) { + this.TestTableRowDeleting(this, new TestTableRowChangeEvent(((TestTableRow)(e.Row)), e.Action)); + } + } + + public void RemoveTestTableRow(TestTableRow row) { + this.Rows.Remove(row); + } + } + + [System.Diagnostics.DebuggerStepThrough()] + public class TestTableRow : System.Data.DataRow { + + private TestTableDataTable tableTestTable; + + internal TestTableRow(System.Data.DataRowBuilder rb) : + base(rb) { + this.tableTestTable = ((TestTableDataTable)(this.Table)); + } + + public string UserName { + get { + try { + return ((string)(this[this.tableTestTable.UserNameColumn])); + } + catch (System.InvalidCastException e) { + throw new System.Data.StrongTypingException("StrongTyping_CananotAccessDBNull", e); + } + } + set { + this[this.tableTestTable.UserNameColumn] = value; + } + } + + public short Age { + get { + try { + return ((short)(this[this.tableTestTable.AgeColumn])); + } + catch (System.InvalidCastException e) { + throw new System.Data.StrongTypingException("StrongTyping_CananotAccessDBNull", e); + } + } + set { + this[this.tableTestTable.AgeColumn] = value; + } + } + + public bool IsUserNameNull() { + return this.IsNull(this.tableTestTable.UserNameColumn); + } + + public void SetUserNameNull() { + this[this.tableTestTable.UserNameColumn] = System.Convert.DBNull; + } + + public bool IsAgeNull() { + return this.IsNull(this.tableTestTable.AgeColumn); + } + + public void SetAgeNull() { + this[this.tableTestTable.AgeColumn] = System.Convert.DBNull; + } + } + + [System.Diagnostics.DebuggerStepThrough()] + public class TestTableRowChangeEvent : System.EventArgs { + + private TestTableRow eventRow; + + private System.Data.DataRowAction eventAction; + + public TestTableRowChangeEvent(TestTableRow row, System.Data.DataRowAction action) { + this.eventRow = row; + this.eventAction = action; + } + + public TestTableRow Row { + get { + return this.eventRow; + } + } + + public System.Data.DataRowAction Action { + get { + return this.eventAction; + } + } + } +} diff --git a/CoreRemoting.Tests/TestDataSet.xsd b/CoreRemoting.Tests/TestDataSet.xsd new file mode 100644 index 0000000..e936670 --- /dev/null +++ b/CoreRemoting.Tests/TestDataSet.xsd @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CoreRemoting.sln.DotSettings.user b/CoreRemoting.sln.DotSettings.user index ec02f7f..dd336b1 100644 --- a/CoreRemoting.sln.DotSettings.user +++ b/CoreRemoting.sln.DotSettings.user @@ -1,5 +1,9 @@  True + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded ShowAndRun <AssemblyExplorer> <Assembly Path="/home/rainbird/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/3.1.7/lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll" /> diff --git a/CoreRemoting/Serialization/Bson/BsonSerializerAdapter.cs b/CoreRemoting/Serialization/Bson/BsonSerializerAdapter.cs index 636e2b0..43a676d 100644 --- a/CoreRemoting/Serialization/Bson/BsonSerializerAdapter.cs +++ b/CoreRemoting/Serialization/Bson/BsonSerializerAdapter.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; +using CoreRemoting.Serialization.Bson.DataSetDiffGramSupport; using Newtonsoft.Json; using Newtonsoft.Json.Bson; @@ -35,8 +38,21 @@ public BsonSerializerAdapter(BsonSerializerConfig config = null) FloatParseHandling = FloatParseHandling.Double }; + // Add support for DataSet DiffGram serialization + var converters = + new List + { + new DataSetDiffGramJsonConverter() + }; + if (config != null) - settings.Converters = config.JsonConverters; + { + converters.AddRange( + config.JsonConverters.Where(converter => + converter.GetType() != typeof(DataSetDiffGramJsonConverter))); // Ensure DataSetDiffGramJsonConverter not added twice + } + + settings.Converters = converters; _serializer = JsonSerializer.Create(settings); } diff --git a/CoreRemoting/Serialization/Bson/DataSetDiffGramSupport/DataSetDiffGramJsonConverter.cs b/CoreRemoting/Serialization/Bson/DataSetDiffGramSupport/DataSetDiffGramJsonConverter.cs new file mode 100644 index 0000000..889615f --- /dev/null +++ b/CoreRemoting/Serialization/Bson/DataSetDiffGramSupport/DataSetDiffGramJsonConverter.cs @@ -0,0 +1,92 @@ +using System; +using System.Buffers.Text; +using System.Data; +using System.IO; +using System.Text; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace CoreRemoting.Serialization.Bson.DataSetDiffGramSupport; + +/// +/// Converter to serialize and deserialize typed and untyped DataSets / DataTables. +/// +public class DataSetDiffGramJsonConverter : JsonConverter +{ + /// + /// Returns true if this converter can convert the specified object type. + /// + /// Object type + /// True if type can be converted, otherwise false + public override bool CanConvert(Type objectType) + { + return + typeof(DataTable).IsAssignableFrom(objectType) || + typeof(DataSet).IsAssignableFrom(objectType); + } + + /// + /// Writes JSON for a DataSet/DataTable instance. + /// + /// JSON writer + /// DataSet/DataTable + /// JSON serializer + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + var type = value.GetType(); + + using var diffGramWriter = new StringWriter(); + using var schemaWriter = new StringWriter(); + + if (typeof(DataTable).IsAssignableFrom(type)) + { + var table = (DataTable)value; + table.WriteXml(diffGramWriter, XmlWriteMode.DiffGram); + table.WriteXmlSchema(schemaWriter); + } + else if (typeof(DataSet).IsAssignableFrom(type)) + { + var dataSet = (DataSet)value; + dataSet.WriteXml(diffGramWriter, XmlWriteMode.DiffGram); + dataSet.WriteXmlSchema(schemaWriter); + } + else + throw new ArgumentException("Value is not a DataSet or DataTable."); + + var xmlSchema = + Convert.ToBase64String( + Encoding.UTF8.GetBytes( + schemaWriter.ToString())); + + var diffGram = + Convert.ToBase64String( + Encoding.UTF8.GetBytes(diffGramWriter.ToString())); + + var wrapper = new SerializedDiffGram() + { + TypeName = type!.FullName!, + XmlSchema = xmlSchema, + DiffGram = diffGram + }; + + serializer.Serialize(writer, wrapper); + } + + /// + /// Reads a DataSet/DataTable instance from JSON. + /// + /// JSON reader + /// Object type to be read + /// Existing value + /// JSON serializer + /// DataSet/DataTable instance + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var wrapper = serializer.Deserialize(reader); + + return wrapper.ToDataObject(objectType); + } +} \ No newline at end of file diff --git a/CoreRemoting/Serialization/Bson/DataSetDiffGramSupport/SerializedDiffGram.cs b/CoreRemoting/Serialization/Bson/DataSetDiffGramSupport/SerializedDiffGram.cs new file mode 100644 index 0000000..e82fa43 --- /dev/null +++ b/CoreRemoting/Serialization/Bson/DataSetDiffGramSupport/SerializedDiffGram.cs @@ -0,0 +1,62 @@ +using System; +using System.Data; +using System.IO; +using System.Text; + +namespace CoreRemoting.Serialization.Bson.DataSetDiffGramSupport; + +public class SerializedDiffGram +{ + public string TypeName { get; set; } + + public string XmlSchema { get; set; } + + public string DiffGram { get; set; } + + public object ToDataObject(Type objectType) + { + var xmlSchema = + Encoding.UTF8.GetString( + Convert.FromBase64String( + XmlSchema)); + + var diffGram = + Encoding.UTF8.GetString( + Convert.FromBase64String( + DiffGram)); + + using var schemaReader = new StringReader(xmlSchema); + using var diffGramReader = new StringReader(diffGram); + + if (typeof(DataSet).IsAssignableFrom(objectType)) + { + DataSet dataSet; + + if (objectType == typeof(DataSet)) + dataSet = new DataSet(); + else + dataSet = (DataSet)Activator.CreateInstance(objectType); + + dataSet.ReadXmlSchema(schemaReader); + dataSet.ReadXml(diffGramReader, XmlReadMode.DiffGram); + + return dataSet; + } + else if (typeof(DataTable).IsAssignableFrom(objectType)) + { + DataTable table; + + if (objectType == typeof(DataTable)) + table = new DataTable(); + else + table = (DataTable)Activator.CreateInstance(objectType); + + table.ReadXmlSchema(schemaReader); + table.ReadXml(diffGramReader); + + return table; + } + else + return null; + } +} \ No newline at end of file diff --git a/CoreRemoting/Serialization/Bson/Envelope.cs b/CoreRemoting/Serialization/Bson/Envelope.cs index 6c7383a..e6184ef 100644 --- a/CoreRemoting/Serialization/Bson/Envelope.cs +++ b/CoreRemoting/Serialization/Bson/Envelope.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using CoreRemoting.Serialization.Bson.DataSetDiffGramSupport; using Newtonsoft.Json; namespace CoreRemoting.Serialization.Bson @@ -60,6 +61,13 @@ public object Value // Special handling for enum values, because BSON serializes every integer as Int64! if (_type.IsEnum && valueType != _type) return Enum.ToObject(_type, _value); + + // Special handling for serializes DiffGrams + if (_value.GetType() == typeof(SerializedDiffGram)) + { + var serializedDiffGram = (SerializedDiffGram)_value; + return serializedDiffGram.ToDataObject(_type); + } return Convert.ChangeType(_value, _type); }