感谢大家的提示。在处理 JSON 和 .NET 数据表时,我必须克服许多不同的挑战。
- 返回 0 行的表不会序列化,因此您最终会得到一个包含 0 列的 Datatable。
- JSON 丢失列的数据类型
- JSON 丢失记录的 RowState。
- JSON 丢失了已修改行的 Original\Proposed 值,因此无法保存
- JSON 将 DataType Byte[] 转换为 Base64 字符串,但不会将其转换回来。
因此,我找到了一个似乎可以一次性克服所有这些问题的解决方案。总结一下解决方案..
- 使用 DataTable 的内置 XML 功能恢复架构
- 保存原始 RowState 以便将其恢复
- 保存修改记录的原始值以将其恢复。
既然我们都喜欢代码...
即将被序列化的类(还有更多属性,但这些是重点)
public System.Data.DataTable Data { get; set; }
[Newtonsoft.Json.JsonProperty]
private string _DataTableSchema { get; set; }
[Newtonsoft.Json.JsonProperty]
private List<DeconstructedDataRow> _DeconstructedDataRows { get; set; }
private class DeconstructedDataRow
{
public System.Data.DataRowState RowState { get; set; }
public List<object> RowOriginalValues { get; set; }
public DeconstructedDataRow()
{
RowState = System.Data.DataRowState.Unchanged;
RowOriginalValues = new List<object>();
}
}
从一方面来说,在序列化之前您调用的表 .Data_Deconstruct....
public void Data_Deconstruct()
{
//Couple of Pre-Checks
_DataTableSchema = String.Empty;
_DeconstructedDataRows.Clear();
if (this.Data == null || this.Data.Columns.Count == 0)
{
return;
}
//We need to mess around a bit so instead of tampering with their original table, we work with a copy
System.Data.DataTable myWorkingData = this.Data.Copy();
//In order to serialize to XML, the table MUST have a name
if (String.IsNullOrWhiteSpace(myWorkingData.TableName) == true)
{
myWorkingData.TableName = System.Guid.NewGuid().ToString();
}
//JSON doesn't carry over the Schema of the Table so we loose the specific
//DataTypes of each colum. So we use the Built-in method of the DataTable object to create
//a XML\String version of its schema
System.IO.StringWriter myStringWriter = new System.IO.StringWriter();
myWorkingData.WriteXmlSchema(myStringWriter, true);
_DataTableSchema = myStringWriter.ToString();
//JSON and the process of Serializing and Deserializing doesn't carry over
//the proper RowState for each Record. In addition, for those records that
//have been Modified, we lose the Original values, and for those records that
//have been Deleted, we can't serialize them. So this is a KLUDGE king that
//seems to sort all this out.
for (Int32 intRowIndex = 0; intRowIndex < myWorkingData.Rows.Count; intRowIndex++)
{
DeconstructedDataRow myDeconstructedDataRow = new DeconstructedDataRow();
//So start by saving off the current RowState
myDeconstructedDataRow.RowState = myWorkingData.Rows[intRowIndex].RowState;
//If the RowState is DELETED, then the ORGINAL Record will not serialize,
//so we need to reject the ORIGINAL state for now and we will restore it later
if (myDeconstructedDataRow.RowState == System.Data.DataRowState.Deleted)
{
this.Data.Rows[intRowIndex].RejectChanges();
}
//If the RowState is MODIFIED, then we have to restore the ORIGINAL values
//when we restore this record. Without the Original Values, the record won't
//update and even if we force it, it will error out because of 'concurrency' errors.
if (myDeconstructedDataRow.RowState == System.Data.DataRowState.Modified)
{
myWorkingData.Rows[intRowIndex].RejectChanges();
myDeconstructedDataRow.RowOriginalValues.AddRange(myWorkingData.Rows[intRowIndex].ItemArray);
}
//And don't forget to add it to our list
this._DeconstructedDataRows.Add(myDeconstructedDataRow);
}
//Clean up our Clone.
myWorkingData.Dispose();
myWorkingData = null;
}
另一方面,反序列化后你调用 .Data_Reconstruct....
public void Data_Reconstruct()
{
//Couple of Pre-Checks
if (this.Data == null || String.IsNullOrWhiteSpace(_DataTableSchema) == true)
{
return;
}
//So first we build a new DataTable with the correct Schema
System.Data.DataTable myWorkingData = new System.Data.DataTable();
System.IO.StringReader myStringReader = new System.IO.StringReader(_DataTableSchema);
myWorkingData.ReadXmlSchema(myStringReader);
//Now we transfer over all the data that was serialize 'as-is' from the existing to the new Table
foreach (System.Data.DataRow myRow in this.Data.Rows)
{
//myWorkingData.ImportRow(myRow); //Should have been this easy BUT ...
// JSON converts some data types to a different format, but then when it deserializes
// it doesn't convert them back (not sure why). So we have to account for that
// and at a performance cost
System.Data.DataRow myNewRecord = myWorkingData.NewRow(); //Create a New row from the table with the Proper Schema
foreach (System.Data.DataColumn myField in myWorkingData.Columns)
{
if (myField.DataType.Equals(typeof(System.Byte[])))
{
myNewRecord[myField.ColumnName] = Convert.FromBase64String(Convert.ToString(myRow[myField.ColumnName]));
}
else
{
myNewRecord[myField.ColumnName] = myRow[myField.ColumnName];
}
}
myWorkingData.Rows.Add(myNewRecord);
}
//We have to accept the changes because all rows are currently marked as "Added" (via JSON as well)
myWorkingData.AcceptChanges();
//Now restore their Row States
for (Int32 intRowIndex = 0; intRowIndex < myWorkingData.Rows.Count; intRowIndex++)
{
switch (_DeconstructedDataRows[intRowIndex].RowState)
{
case System.Data.DataRowState.Added:
myWorkingData.Rows[intRowIndex].SetAdded();
break;
case System.Data.DataRowState.Deleted:
myWorkingData.Rows[intRowIndex].Delete();
break;
case System.Data.DataRowState.Modified:
//For Modified, we have to do some kludge stuff or else the UPDATE will not trigger
//We start by saving off the Values that are in the Record NOW (aka the New values)
object[] objNewValues = myWorkingData.Rows[intRowIndex].ItemArray;
//Now we replace those values with the ORIGINAL values we saved off before transporting
for (Int32 intFieldIndex = 0; intFieldIndex < this._DeconstructedDataRows[intRowIndex].RowOriginalValues.Count; intFieldIndex++)
{
if (this._DeconstructedDataRows[intRowIndex].RowOriginalValues[intFieldIndex] == null)
{
this._DeconstructedDataRows[intRowIndex].RowOriginalValues[intFieldIndex] = DBNull.Value;
}
}
myWorkingData.Rows[intRowIndex].ItemArray = this._DeconstructedDataRows[intRowIndex].RowOriginalValues.ToArray();
myWorkingData.Rows[intRowIndex].AcceptChanges();
//and Last we replace those Original values with the New Values, which not only
//correctly sets the Original\Proposed values, but also changes the RowState to MODIFIED
myWorkingData.Rows[intRowIndex].ItemArray = objNewValues;
break;
default:
//These would be the Unchanged
break;
}
}
//And finally, we replace the existing Table with our fixed one.
this.Data = myWorkingData;
}
为了测试这一点,我在我的数据库中创建了 2 条记录
Rec1_Delete
Rec2_Modify
然后我使用上述方法加载表,将删除记录标记为删除,修改修改记录中的字段,并添加全新记录。
然后我将数据表发送回 API 和数据库中,删除了已删除的记录,修改了修改的记录,并添加了新记录。
作为附注,我公开了实际的 DataTable,因为如果您碰巧从 .NET 以外的其他东西(如 HTML 文件中的 Javascript)调用它。您仍然可以直接使用 DataTable 来读取和显示数据。整个 DeConstruct 和 ReConstruct 都是针对 .NET 客户端的,同时仍然允许其他客户端进行有限访问。