Getting DA Linq to Datatable from DataContext for Briefcase application

Hello,

I am using DA linq with a DataContext on my application. I want to integrate Briefcase in this setup but Briefcase is a DataTable technology. Essentially I am filtering based on each user so they only get their own data like:

IQueryable clientsQuery = from c in this.remoteDataAdapter.GetTable <tblCustomer()> where c.Salesperson == salesperson orderby c.Company select c;

I then bind this to a DataContext

this.Context.tblCustomer = clientsQuery.ToList();

How would I get this into a briefcase? Is there already a method or extension that does this? Is there any examples of using DA Linq with Briefcase you can direct me to?

Thanks,

Howie

Hello

Briefcase is aimed at DataSets, not DA LINQ. Even if it is possible to make it work with DA LINQ it would require way too much code to do that.

Here is t sample of another approach: BinSerializer.zip (115.9 KB)

In this sample the client app can perform a DA LINQ query and then save the results of that query into a file using .NET binary serializer:

    private void SaveData_Click(object sender, EventArgs e)
    {
        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.ToList();

        var dataStream = new MemoryStream();

        var formatter = new BinaryFormatter();
        formatter.Serialize(dataStream, data);

        File.WriteAllBytes("customers.dat", dataStream.ToArray());

        MessageBox.Show("Done");
    }

The serialized data can be loaded using a very simple code like

    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;
    }

Important notes:

  • The Customers class is marked as Serializable in the TableDefinitions_Extension.cs file. Using a separate file is a simple trick to adjust properties of Table Definition classes without modifying the auto-generated TableDefinitions code file (so these changes won’t be lost when this file is regenerated)
  • this._dataModule.DataAdapter.UseBindableClass = false; This code line is required to detach loaded data from and DA LINQ references. Otherwise BinaryFormatter won’t be able to serialize them.

Hope that helps

Thank you Anton,

I see how this can give me local data in a .dat file but how would I synchronize the CRUD deltas back to the server?

Thanks,

Howie

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

Hi Anton,

I think this will work as all of my (add new record )tables have GUID;s for PK and the ones that are auto inc will not add any records only update and never the pk. The adding for those records i.e. adding users will only happen at the main db. I will give this a try and let you know if it worked for me.

Thank you for the well explained example,

Howie