Hello
At first, a brief explanation of some internal DA LINQ concepts and why they make the Delta serialization a not-so-straightforward task.
When an insert or update operation is performed over a DA LINQ object entity the data adapter not only caches corresponding Delta operation to apply it later to the database. Also data adapter stores a Delta --> Object link. This link is used to update the source object after the changes are actually applied to the server. For example if the object Id corresponds to an AutoInc field in the server Schema then the data adapter first creates this object with an Id like -1
, -2
etc. Then when the changes are applied on the server and an actual data row is created in the database the server returns “real” autoinc value and the data adapter updates the source object.
So a proper solution would be really non-trivial.
The solution below works only when there are no server-calculated fields like AutoInc fields.
Also the code below is a concept. F.e. a real-world application should not write files directly from the button click even handler.
At first the following method should be added to the DataModule code:
public void ApplyChanges(Delta delta)
{
var serviceName = this.DataAdapter.RemoteService.ServiceName;
var serviceProxy = new DataAbstractService_Proxy(this.message, this.clientChannel, serviceName);
var binary = new Binary();
this.dataStreamer.InitializeStreamer(binary, StreamerInitialization.Write);
try
{
this.dataStreamer.WriteDelta(delta, false);
}
finally
{
this.dataStreamer.FinalizeStreamer();
}
var response = serviceProxy.UpdateData(binary);
this.dataStreamer.InitializeStreamer(response, StreamerInitialization.Read);
try
{
this.dataStreamer.ReadDelta(delta);
}
finally
{
this.dataStreamer.FinalizeStreamer();
}
foreach (var deltaChange in delta)
{
if (deltaChange.Status == ChangeStatus.Failed)
{
throw new Exception(deltaChange.Message); // Actually instead of immediately raising an exception all error messages should be gathered
}
}
}
This method takes a delta and sends it to the server. Note that it doesn’t check the server response for updated field values.
Now 2 serializable classes should be added. They will be used by the serializer:
[Serializable]
public class OfflineDelta
{
public OfflineDeltaChange[] Changes { get; set; }
}
[Serializable]
public class OfflineDeltaChange
{
public ChangeType Type { get; set; }
public Object[] OldValues { get; set; }
public Object[] NewValues { get; set; }
}
And the form code itself (this is and update to the previous sample in this thread):
private void SaveData_Click(object sender, EventArgs e)
{
this._dataModule.DataAdapter.GetTable<Customers>().Changes.Clear();
this._dataModule.DataAdapter.UseBindableClass = false;
var query = from c in this._dataModule.DataAdapter.GetTable<Customers>()
where c.Name.StartsWith("C")
select c;
var data = query.ToArray();
// Add a sample data change (not applied to the server!)
// Set a random value to a field
data[0].Remarks = Guid.NewGuid().ToString();
this._dataModule.DataAdapter.UpdateRow(data[0]);
this.Serialize(data);
// This should be a loop over known data entities
this.SerializeChanges<Customers>();
MessageBox.Show("Done");
}
private void Serialize(Customers[] data)
{
var dataList = new List<Customers>();
dataList.AddRange(data);
var dataStream = new MemoryStream();
var formatter = new BinaryFormatter();
formatter.Serialize(dataStream, dataList);
File.WriteAllBytes("customers.dat", dataStream.ToArray());
}
private void SerializeChanges<T>() where T : class, new()
{
// 1. Fill data into a buffer structure
var delta = this._dataModule.DataAdapter.GetTable<T>().Changes;
var fieldCount = delta.Schema.Fields.DeltaFieldCount;
// We cannot serialize delta object AS IS so a wrapper structure is used
var changes = new List<OfflineDeltaChange>();
foreach (var deltaChange in delta)
{
var offlineChange = new OfflineDeltaChange();
offlineChange.Type = deltaChange.Type;
offlineChange.OldValues = new Object[fieldCount];
offlineChange.NewValues = new Object[fieldCount];
for (int i = 0; i < fieldCount; i++)
{
offlineChange.OldValues[i] = deltaChange.OldValues[i];
offlineChange.NewValues[i] = deltaChange.NewValues[i];
}
changes.Add(offlineChange);
}
var offlineDelta = new OfflineDelta();
offlineDelta.Changes = changes.ToArray();
// 2. Write down that structure
var dataStream = new MemoryStream();
var formatter = new BinaryFormatter();
formatter.Serialize(dataStream, offlineDelta);
File.WriteAllBytes("delta.dat", dataStream.ToArray());
}
private Delta DeserializeChanges<T>() where T : class, new()
{
var delta = this._dataModule.DataAdapter.GetTable<T>().Changes;
var fieldCount = delta.Schema.Fields.DeltaFieldCount;
var dataBuffer = File.ReadAllBytes("delta.dat");
var dataStream = new MemoryStream(dataBuffer);
var formatter = new BinaryFormatter();
var data = (OfflineDelta)formatter.Deserialize(dataStream);
foreach (var offlineChange in data.Changes)
{
var deltaChange = new DeltaChange(delta);
delta.Add(deltaChange);
deltaChange.Type = offlineChange.Type;
for (int i = 0; i < fieldCount; i++)
{
deltaChange.OldValues[i] = offlineChange.OldValues[i];
deltaChange.NewValues[i] = offlineChange.NewValues[i];
}
}
return delta;
}
private void LoadOfflineData_Click(object sender, EventArgs e)
{
var dataBuffer = File.ReadAllBytes("customers.dat");
var dataStream = new MemoryStream(dataBuffer);
var formatter = new BinaryFormatter();
var data = (IList<Customers>)formatter.Deserialize(dataStream);
this.dataGridView.DataSource = data;
var delta = this.DeserializeChanges<Customers>();
this._dataModule.ApplyChanges(delta);
delta.Clear();
MessageBox.Show("Done");
}
Note the SerializeChanges and DeserializeChanges methods. First one takes cached Delta changes and writes them down to the file. Second one reads that file and applies these changes to the server.
Regards