views:

469

answers:

3

EDIT: It seems as if the DataSetProvider doesn't have the functionality I need for this project, so I'll be implementing a custom class for loading the data into the ClientDataSet.

I am trying to take data from a TMSQuery which is connected to my DB and populate a ClientDataSet with some of that data using a DataSetProvider.

My problem is that I will need to modify some of this data before it can go into my ClientDataSet. The ClientDataSet has persistent fields that will not match up with the raw DB data. I can't even get a string from the DB into a memo field in ClientDataSet.

The ClientDataSet is a part of my data tier so I will need to conform the data from the DB to the ClientDataSet field by field (well most will be able to go right through, but many will require routing and/or conversion).

Does anyone have experience with this?

A: 

If I need a ClientDataSet to have data that doesn't exactly match the database schema, I write a query for the TQuery component that returns the data in the format that I want. I then write my own, separate, Delete, Insert, Refresh, and Update queries for the TQuery component.

Alternatively, you could create a view on the database and use the view in your TQuery component.

If you want a custom ClientDataSet that is independent of the database, what you need is an in-memory dataset. If you don't have an in-memory dataset component, Google for "TClientDataSet as in-memory dataset". What you end up with though, is basically a glorified list view component. Of course, you can hook into the OnUpdateRecord of the in-memory dataset to know when to update your real dataset.

Marcus Adams
+1  A: 

You're looking for the TDataSetProvider.BeforeUpdateRecord event. Write an event handler for this event and you can take manual control of how the data is applied back to the data base.

Something like this

procedure TDataModule1.DataSetProvider1BeforeUpdateRecord(Sender: TObject; SourceDS: TDataSet; DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind; var Applied: Boolean);
begin
  { Set applied to tell DataSnap that you have applied this record yourself }
  Applied := True;

  case UpdateKind of
    ukModify:
      begin
        Table1.Edit;
        { set the values of the fields something like this }
        if not VarIsEmpty(DeltaDS.FieldByName('NewValue')) then
          Table1['SomeField'] := DeltaDS.FieldByName('SomeField').NewValue;
        Table1.Post;
    end;

    ukInsert:
      begin
        Table1.Insert;
        { set the values of the fields }
        Table1['SomeField'] := DeltaDS['SomeField']
        Table1.Post;
      end;

    ukDelete:
      if Table1.Locate('PrimaryKeyField', DeltaDS['PrimaryKeyField'], []) then
        Table1.Delete;
  end; // case
end;
LachlanG
+1 very similar to the kbmMemTable resolver just in reverse.
MarkRobinson
I will need to do this, but not until I am able to get the data into the ClientDataSet. I need to be able to make those same modifications to the data coming out of the DataSet connected to the database. Is it possible?
nomad311
It sounds like you want to combine the data from 2 or more tables into a single ClientDataSet and then apply the updates back to the source tables. If so that's definitely possible, I just want to check before I modify my answer.
LachlanG
I think I know what you want but I'll put it as a separate answer in case I'm on the right track with this answer.
LachlanG
A: 

You can modify the data going to the ClientDataSet by implementing the TDataSetProvider.OnGetData event.

procedure TDataModule1.DataSetProvider1GetData(Sender: TObject; DataSet: TCustomClientDataSet);
begin
  DataSet.First;
  while not DataSet.Eof do begin
    DataSet.Edit;
    DataSet['Surname'] := UpperCase(DataSet['Surname']);
    DataSet.Post;
    DataSet.Next;
  end; // while
end;

When applying updates from the ClientDataSet you can use the TDataSetProvider.OnUpdateData event. Like the OnGetData event you are operating on the whole dataset rather than a single record.

procedure TDataModule1.DataSetProvider1UpdateData(Sender: TObject; DataSet: TCustomClientDataSet);
begin
  DataSet.First;
  while not DataSet.Eof do begin
    DataSet.Edit;
    DataSet['Surname'] := LowerCase(DataSet['Surname']);
    DataSet.Post;
    DataSet.Next;
  end; // while
end;

This OnUpdateData event is called before the OnBeforeUpdateRecord event. Also the OnGetData and OnUpdateData events operate on the whole dataset while OnBeforeUpdateRecord is called once for each modified record.

LachlanG
I looked into the OnGetData event, but the data is already in the ClientDataSet at this point. The problem is that my data types don't match up so something as simple as a StringField in the TMSQuery can't load into a MemoField in the ClientDataSet, therefore I get an exception.
nomad311
Can you do the transformations you need in the SQL, casting strings to memos etc?
LachlanG
I could, but that would create another unwanted DB access point. The solution I'm implementing is a custom class for loading the data into the ClientDataSet. I would love to use this provider, but it doesn't have the functionality I need.
nomad311
You'd probably need to create your own TProvider descendent to do this. You could reuse a lot of the code from TDataSetProvider but it's probably more work than you want to do for this problem.
LachlanG