Restructure a TClientdataset - delphi

Can a TClientDataSet XML file be restructured without losing data? Are there any demo applications or source code that shows how to do such a restructure?

yes and no, the xml doc is transformed using XLST so it needs to conform to that template to be able to be read by the TClientDataSet.
However that does also mean that you can also transform the doc to any format you like yourself into a separate doc, you just can't load the transformed doc directly into the TClientDataSet.
EDIT:
Oops, forgot to post an example.
This project on code central shows a transform from clientdataset to an ADO recordset.

In order to make changes to CDS structure on disk I used a sub-class outlined below. We write our data in binary format to a stream (before compression/encryption) but it should work much the same for XML format.
If you need to add/remove any fields from your saved dataset or change the field definitions then you just increment the dataset table version. When the dataset is opened each time it compares the saved version number with the current. If the saved table is old it will be copied into the new structure, so if you need to make changes, you will take one performance hit the first time you reload the table, but after that it should load from disk as usual.
So, if you save the CDS back to disk after doing the merge - voila - your XML structure is updated, in a CDS friendly format.
TCDS = class(TCustomClientDataset)
private
fTableVersion: integer;
/// <summary> Copies records from source with potentially different table
/// structure/field defs from self, providing defaults for missing fields</summary>
procedure CopyFromDataset(const ASource: TCustomClientDataset);
/// <summary>Provide a default value, if necessary, for any new fields</summary>
function GetDefaultValue(const AFieldName: string): variant;
public
procedure LoadFromStream(AStream: TStream);
procedure SaveToStream(AStream: TStream);
end;
procedure TCDS.LoadFromStream(AStream: TStream);
var
ATemp: TCDS;
APersistedVersion: integer;
begin
AStream.ReadData(APersistedVersion);
if APersistedVersion = fTableVersion then
begin
Close;
ReadDataPacket(AStream, True);
Open;
end
else if APersistedVersion < fTableVersion then
begin
// It's an old table structure:
// - Load old structure into temp CDS
// - Merge temp CDS records into new structure
ATemp := TCDS.Create;
try
ATemp.Close;
ATemp.ReadDataPacket(AStream, True);
ATemp.Open;
CopyFromDataset(ATemp);
finally
FreeAndNil(ATemp);
end;
end;
end;
procedure TCDS.SaveToStream(AStream: TStream);
begin
AStream.WriteData(fVersionNumber);
WriteDataPacket(AStream, True);
end;
procedure TCDS.CopyFromDataset(const ASource: TCustomClientDataset);
var
ACurrentFieldNames: TStrings;
i: integer;
begin
// Assuming we don't want to keep any records already in dataset
EmptyDataSet;
ACurrentFieldNames := TStringList.Create;
try
Fields.GetFieldNames(ACurrentFieldNames);
for i := 0 to ACurrentFieldNames.Count-1 do
ACurrentFieldNames.Objects[i] := ASource.Fields.FindField(ACurrentFieldNames[i]);
ASource.First;
while not ASource.Eof do
begin
Append;
for i := 0 to Fields.Count-1 do
begin
if Assigned(ACurrentFieldNames.Objects[i]) then
Fields[i].Value := TField(ACurrentFieldNames.Objects[i]).Value
else if Fields[i].Required then
Fields[i].Value := GetDefaultValue(ACurrentFieldNames[i]);
end;
Post;
ASource.Next;
end;
finally
FreeAndNil(ACurrentFieldNames);
end;
end;

Related

Indy download selected file from the listbox

I never used Indy and am struggling to learn the basic. Took me some time to figure out how to populate the listbox. Now that I have done that how can I download the selected file in the listbox ?
I tried :
procedure TFTP.Button2Click(Sender: TObject);
var
i:integer;
begin
for i := 0 to ListBox1.Items.Count - 1 do begin
if ListBox1.Selected[i] then begin
IdFTP1.Get(listbox1.Selected[i]);
end;
end;
end;
But I am getting :
[dcc32 Error] FTP_Form.pas(75): E2250 There is no overloaded version
of 'Get' that can be called with these arguments
Or do I need to use a savedialog too? Please help me with this. :)
ListBox1.Selected[i] is a Boolean. Note that in the previous line you wrote:
if ListBox1.Selected[i] then begin
Now, look at the TIdFTP.Get() method. It has two overloads:
procedure Get(const ASourceFile: string; ADest: TStream;
AResume: Boolean = false); overload;
procedure Get(const ASourceFile, ADestFile: string; const ACanOverwrite: boolean = false;
AResume: Boolean = false); overload;
You need to provide:
the source filename of the remote file you want to download.
a destination filename or stream to receive the content of the remote file.
I don't know where you intend to obtain these. Presumably the filename comes from the ListBox, which would therefore be ListBox1.Items[i].
What do you want to do with the content you download? Keep it in memory? Save it to a file? Something else? What destination you supply depends on your answers to those questions.
My advice to you is to put the ListBox to one side for the moment, and write a simpler program, one without any UI, that simply downloads a single file from the FTP server. Use a local filename or a TFileStream to save the downloaded content to your local disk. Check that the contents are what you expect. Once you can download one file, you can download any number of files, to other kinds of destinations.
Once you have mastered that, move on to the user interface. Spend some time learning how the ListBox control works, how you populate it, how you read back strings from it, how you test for selection, and so on.
Only when you have a good understanding of all parts involved, then you should you try to fit them together.
One way ....
procedure TFTP.Button2Click(Sender: TObject);
Var
Name{, Line}: String;
begin
Name := IdFTP1.DirectoryListing.Items[ListBox1.ItemIndex].FileName;
SaveDialog1.FileName := Name;
if SaveDialog1.Execute then begin
IdFTP1.Get(Name, SaveDialog1.FileName, true);
end;
end;
Assuming the ListBox contains the remote filenames to download (such as from the TIdFTP.DirectoryListing property after a call to TIdFTP.List()):
procedure TFTP.Button2Click(Sender: TObject);
var
i:integer;
begin
for i := 0 to ListBox1.Items.Count - 1 do
begin
if ListBox1.Selected[i] then begin
IdFTP1.Get(ListBox1.Items[i], 'C:\Some Local Path\' + ListBox1.Items[i]);
end;
end;
end;

Storing extra data in developer express component cxTreeView

I have a cxTreeView on a form displaying descriptions of some files. I would like to be able to have the option of clicking one of the descriptions and have the filename returned.
In order to do this I need to store some extra data in the treeview and that is my problem.
How do I do this:
Store both filename and description, display just the description but get the filename by doubleclicking?
I think you can use a TcxTreeList for this task (if possible), if there is no urgent need to use a TcxTreeView. In a TcxTreeList you can add a new column and store the filename in it. Then make this column invisible.
If you must rely on a TcxTreeView, there is no option to directly store data, like strings, in the TTreeNodes. You can store a pointer to any object into the property "Data" of the TTreeNode.
In both answers here is mentioned that TcxTreeView uses standard TTreeNode where, as Warren pointed in his answer as first, you can use the TTreeNode.Data property to store your data. I'll just complete these thoughts with an example.
Update:
The previous version of this post would work only for constant strings for whose the space is being allocated automatically. If you need to pass a variable into the TTreeNode.Data you have to allocate and release the memory by your own. Here is the example with using simple helper functions.
The AddFileNode helper function adds the node to the Items into the ParentNode with the name of the FileDesc and allocates the space and copy the passed FileName for the TTreeNode.Data by using the StrNew function.
When you need to modify the data, you should release (or better, but little bit complicated would be to reallocate) the allocated memory and allocate the space and copy the new value. For this you can use the ChangeFileName which disposes the string memory allocated before and allocates the space and copy the passed FileName value.
As I mentioned before you need to take care of the memory disposal by your own and for this there is the OnDeletion event the best place. So write the handler for this event where you'll release the memory allocated when adding the items.
function AddFileNode(Items: TTreeNodes; ParentNode: TTreeNode;
const FileName, FileDesc: string): TTreeNode;
begin
Result := Items.AddChildObject(ParentNode, FileDesc, StrNew(PChar(FileName)));
end;
function ChangeFileName(TreeNode: TTreeNode; const FileName: string): Boolean;
begin
Result := False;
if Assigned(TreeNode.Data) then
begin
Result := True;
StrDispose(PChar(TreeNode.Data));
TreeNode.Data := StrNew(PChar(FileName));
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
S: string;
begin
S := 'C:\FileName 1.xxx';
AddFileNode(cxTreeView1.Items, nil, S, 'File 1');
S := 'C:\FileName 2.xxx';
AddFileNode(cxTreeView1.Items, nil, S, 'File 2');
end;
procedure TForm1.cxTreeView1Deletion(Sender: TObject; Node: TTreeNode);
begin
if Assigned(Node.Data) then
StrDispose(PChar(Node.Data));
end;
procedure TForm1.cxTreeView1DblClick(Sender: TObject);
var
CurrentPos: TPoint;
CurrentNode: TTreeNode;
begin
CurrentPos := cxTreeView1.ScreenToClient(Mouse.CursorPos);
if (htOnItem in cxTreeView1.GetHitTestInfoAt(CurrentPos.X, CurrentPos.Y)) then
begin
CurrentNode := cxTreeView1.GetNodeAt(CurrentPos.X, CurrentPos.Y);
if Assigned(CurrentNode) and Assigned(CurrentNode.Data) then
ShowMessage(PChar(CurrentNode.Data));
end;
end;
If the nodes are regular TTreeNode, you can use the Data:Pointer value in the Node object.
If the nodes in your tree are of type TcxTreeListNode, they contain the ability to store any data values you want. WHen I look at the sources, I see these properties in the node-level:
property ValueCount: Integer read GetValueCount;
property Values[Index: Integer]: Variant read GetValue write SetValue;

Fastreports custom preview problem

I encounter problem on FastReports, it will not print correctly on pages which contain Korean character. It hapens only on Printer HP K5300 jet, T test it using rave and having no problem. I think it a bug for fast reports. I already convert all my reports from rave to FastReports and dont have plan to moved back.
I am planning to get the generated pages as images without saving it to hard drive and then generate a new preports. this time, the generated images will be used and print. I know this solution is not good. this is worable for now While waiting for their responds.
anybody has any idea how to get the images form generated pages?
If you just want to avoid saving a lot of files, you can create a new export class to print the file just after it is created and delete it instantly.
You can create a whole new export class which print the bitmap from memory (for example, using the TPrinter class and drawing the bitmap directly in the printer canvas)... you will learn how checking the source file of the TfrxBMPExport class.
Take this untested code as an example which will guide you how to create a new class to save/print/delete:
type
TBMPPrintExport = class(TfrxBMPExport)
private
FCurrentPage: Integer;
FFileSuffix: string;
protected
function Start: Boolean; override;
procedure StartPage(Page: TfrxReportPage; Index: Integer); override;
procedure Save; override;
end;
{ TBMPPrintExport }
procedure TBMPPrintExport.Save;
var
SavedFileName: string;
begin
inherited;
if SeparateFiles then
FFileSuffix := '.' + IntToStr(FCurrentPage)
else
FFileSuffix := '';
SavedFileName := ChangeFileExt(FileName, FFileSuffix + '.bmp');
//call your actual printing routine here. Be sure your the control returns here when the bitmap file is not needed anymore.
PrintBitmapFile(SavedFileName);
try
DeleteFile(SavedFileName);
except
//handle exceptions here if you want to continue if the file is not deleted
//or let the exception fly to stop the printing process.
//you may want to add the file to a queue for later deletion
end;
end;
function TBMPPrintExport.Start: Boolean;
begin
inherited;
FCurrentPage := 0;
end;
procedure TBMPPrintExport.StartPage(Page: TfrxReportPage; Index: Integer);
begin
inherited;
Inc(FCurrentPage);
end;
In production code you will want to override another methods to initialize and finalize the printer job, cleaning up, etc.
Code is based on FastReport v4.0 implementation of TfrxCustomImageExport, specially for page numbering and file naming. It may require adjustments for other FastReport versions.
You can use the TfrxBMPExport (frxExportImage unit) component to save the report as BMP.
For example, this code will export the report:
procedure ExportToBMP(AReport: TfrxReport; AFileName: String = '');
var
BMPExport: TfrxBMPExport;
begin
BMPExport := TfrxBMPExport.Create(nil);
try
BMPExport.ShowProgress := True;
if AFileName <> '' then
begin
BMPExport.ShowDialog := False;
BMPExport.FileName := AFileName;
BMPExport.SeparateFiles := True;
end;
AReport.PrepareReport(True);
AReport.Export(BMPExport);
finally
BMPExport.Free;
end;
end;
The Export component, in this case, uses a different file name for each page. If you pass 'c:\path\report.bmp' as the filename, the export component will generate c:\path\report.1.bmp, c:\path\report.2.bmp and such.
As usual, you can drop and manually configure the component on any form/data module if you prefer that way.

Delphi 2010: How to save a whole record to a file?

I have defined a record which has lots of fields with different types (integer, real , string, ... plus dynamic arrays in terms of "array of ...").
I want to save it as a whole to a file and then be able to load it back to my program. I don't want to go through saving each field's value individually.
The file type (binary or ascii or ...) is not important as long Delphi could read it back to a record.
Do you have any suggestions?
You can load and save the memory of a record directly to and from a stream, as long as you don't use dynamic arrays. So if you use strings, you need to make them fixed:
type TTestRecord = record
FMyString : string[20];
end;
var
rTestRecord: TTestRecord;
strm : TMemoryStream;
strm.Write(rTestRecord, Sizeof(TTestRecord) );
You can even load or save an array of record at once!
type TRecordArray = array of TTestRecord;
var ra : TRecordArray;
strm.Write(ra[0], SizeOf(TTestRecord) * Length(ra));
In case you want to write dynamic content:
iCount := Length(aArray);
strm.Write(iCount, Sizeof(iCount) ); //first write our length
strm.Write(aArray[0], SizeOf * iCount); //then write content
After that, you can read it back:
strm.Read(iCount, Sizeof(iCount) ); //first read the length
SetLength(aArray, iCount); //then alloc mem
strm.Read(aArray[0], SizeOf * iCount); //then read content
As promised here it is: https://github.com/KrystianBigaj/kblib
When you defined for example record as:
TTestRecord = record
I: Integer;
D: Double;
U: UnicodeString;
W: WideString;
A: AnsiString;
Options: TKBDynamicOptions;
IA: array[0..2] of Integer;
AI: TIntegerDynArray;
AD: TDoubleDynArray;
AU: array of UnicodeString;
AW: TWideStringDynArray;
AA: array of AnsiString;
R: array of TTestRecord; // record contain dynamic array of itself (D2009+)
end;
You can save whole dynamic record to stream (as binary data) by :
TKBDynamic.WriteTo(lStream, lTestRecord, TypeInfo(TTestRecord));
To load it back:
TKBDynamic.ReadFrom(lStream, lTestRecord, TypeInfo(TTestRecord));
It not need to be a record, you can do same for any dynamic type like:
TKBDynamic.WriteTo(lStream, lStr, TypeInfo(UnicodeString));
TKBDynamic.WriteTo(lStream, lInts, TypeInfo(TIntegerDynArray));
TKBDynamic.WriteTo(lStream, lArrayOfTestRecord, TypeInfo(TArrayOfTestRecord)); // TArrayOfTestRecord = array of TTestRecord;
Tested on Delphi 2006/2009/XE. License: MPL 1.1/GPL 2.0/LGPL 3.0
See readme for information.
Another option which works very well for records (Delphi 2010+) is to use the SuperObject library. For example:
type
TData = record
str: string;
int: Integer;
bool: Boolean;
flt: Double;
end;
var
ctx: TSuperRttiContext;
data: TData;
obj: ISuperObject;
sValue : string;
begin
ctx := TSuperRttiContext.Create;
try
sValue := '{str: "foo", int: 123, bool: true, flt: 1.23}';
data := ctx.AsType<TData>(SO(sValue));
obj := ctx.AsJson<TData>(data);
sValue := Obj.AsJson;
finally
ctx.Free;
end;
end;
I also tested this briefly with a simple TArray<Integer> dynamic array and it did not have a problem storing and loading the array elements.
In addition to the answers that indicate how you do this, please also be aware of these:
You must be aware that writing records out to a file will be Delphi version specific (usually: specific to a series of Delphi versions that share the same memory layout for the underlying data types).
You can only do that if your record does not contain fields of a managed type. Which means that fields cannot be of these managed types: strings, dynamic arrays, variants, and reference types (like pointers, procedural types, method references, interfaces or classes) and file types, or types that contain those manages types. Which basically limits to to these unmanaged types:
A: Simple types (including bytes, integers, floats, enumerations, chars and such)
B: Short strings
C: Sets
D: Static arrays of A, B, C, D and E
E: Records of A, B, C, D and E
In stead of writing out records to/from a file, it might be better to go with class instances and convert them to/from JSON, and them write the JSON string equivalent to a file and read it back in.
You can use this unit to do the JSON conversion for you (should work with Delphi 2010 and up; works for sure with Delphi XE and up) from this location this location.
unit BaseObject;
interface
uses DBXJSON, DBXJSONReflect;
type
TBaseObject = class
public
{ public declarations }
class function ObjectToJSON<T : class>(myObject: T): TJSONValue;
class function JSONToObject<T : class>(json: TJSONValue): T;
end;
implementation
{ TBaseObject }
class function TBaseObject.JSONToObject<T>(json: TJSONValue): T;
var
unm: TJSONUnMarshal;
begin
if json is TJSONNull then
exit(nil);
unm := TJSONUnMarshal.Create;
try
exit(T(unm.Unmarshal(json)))
finally
unm.Free;
end;
end;
class function TBaseObject.ObjectToJSON<T>(myObject: T): TJSONValue;
var
m: TJSONMarshal;
begin
if Assigned(myObject) then
begin
m := TJSONMarshal.Create(TJSONConverter.Create);
try
exit(m.Marshal(myObject));
finally
m.Free;
end;
end
else
exit(TJSONNull.Create);
end;
end.
I hope this helps you getting an overview of things.
--jeroen
You could also define an object instead of a record, so you can use RTTI to save your object to XML or whatever. If you have D2010 or XE, you can use DeHL to serialize it:
Delphi 2010 DeHL Serialization XML and custom attribute : how it work?
But if you "google" you can find other libs with RTTI and serialization (with D2007 etc)
Another solution, working from Delphi 5 up to XE, is available as an OpenSource unit.
In fact, it implements:
some low-level RTTI functions for handling record types: RecordEquals, RecordSave, RecordSaveLength, RecordLoad;
a dedicated TDynArray object, which is a wrapper around any dynamic array, able to expose TList-like methods around any dynamic array, even containing records, strings, or other dynamic arrays. It's able to serialize any dynamic array.
Serialization uses an optimized binary format, and is able to save and load any record or dynamic array as RawByteString.
We use this in our ORM, to store high-level types like dynamic array properties into a database back-end. First step to a DB-Sharding architecture.
If you have dynamic strings or array you can't write the record "as a whole". Instead of using old style-25 characters max strings, I would add methods to the record to be able to "stream" itself to a stream, or better using a TFiler descendant:
TMyRec = record
A: string;
B: Integer;
procedure Read(AReader: TReader);
procedure Writer(AWriter: TWriter);
end;
procedure TMyrec.Read(AReader: TReader);
begin
A := AReader.ReadString;
B := AReader.ReadInteger;
end;
Codes from delphibasics :
type
TCustomer = Record
name : string[20];
age : Integer;
male : Boolean;
end;
var
myFile : File of TCustomer; // A file of customer records
customer : TCustomer; // A customer record variable
begin
// Try to open the Test.cus binary file for writing to
AssignFile(myFile, 'Test.cus');
ReWrite(myFile);
// Write a couple of customer records to the file
customer.name := 'Fred Bloggs';
customer.age := 21;
customer.male := true;
Write(myFile, customer);
customer.name := 'Jane Turner';
customer.age := 45;
customer.male := false;
Write(myFile, customer);
// Close the file
CloseFile(myFile);
// Reopen the file in read only mode
FileMode := fmOpenRead;
Reset(myFile);
// Display the file contents
while not Eof(myFile) do
begin
Read(myFile, customer);
if customer.male
then ShowMessage('Man with name '+customer.name+
' is '+IntToStr(customer.age))
else ShowMessage('Lady with name '+customer.name+
' is '+IntToStr(customer.age));
end;
// Close the file for the last time
CloseFile(myFile);
end;
The problem with saving a record containing dynamic array or real strings (or other "managed" types for that matter) is, it's not an big blob of memory containing everything, it's more like a tree. Someone or something needs to go over everything and save it to storage, somehow. Other languages (Python for example) include all sorts of facilities to transform most objects to text (serialize it), save it to disk and reload it (deserialize it).
Even though a Embarcadero-provided solution doesn't exist for Delphi, one can be implemented using the extended RTTI available in Delphi 2010. A ready-made implementation is available in the DeHL library (here's a blog post about it) - but I can't say much about the implementation, I never used DeHL.
An other option is the one you want to avoid: manually serialize the record to an TStream; It's actually not half difficult. Here's the kind of code I usually use to read/write objects to a file stream:
procedure SaveToFile(FileName:string);
var F:TFileStream;
W:TWriter;
i:Integer;
begin
F := TFileStream.Create(FileName, fmCreate);
try
W := TWriter.Create(F, 128);
try
// For every field that needs saving:
W.WriteString(SomeStr);
W.WriteInteger(TheNumber);
// Dynamic arrays? Save the length first, then save
// every item. The length is needed when reading.
W.WriteInteger(Length(DArray));
for i:=0 to High(DArray) do
W.WriteString(DArray[i]);
finally W.Free;
end;
finally F.Free;
end;
end;
procedure ReadFromFile(FileName:string);
var F:TFileStream;
R:TReader;
i,n:Integer;
begin
F := TFileStream.Create(FileName, fmOpenRead);
try
R := TReader.Create(F, 128);
try
SomeStr := R.ReadString;
TheNumber := R.ReadInteger;
// Reading the dynamic-array. We first get the length:
n := R.ReadInteger;
SetLength(DArray, n);
// And item-by-item
for i:=0 to n-1 do
DArray[i] := R.ReadString;
finally R.Free;
end;
finally F.Free;
end;
end;

How to achieve independent cloned TADODataSet?

The scenarios is like this:
We have some SQL table. We are performing an SQL query on this table and we have results in TADOQuery object.
var
qryOryginal, qryClone: TADOQuery;
begin
//setup all the things here
qryOryginal.Active := True;
qryClone.Clone(qryOryginal, ltBatchOptimistic);
qryOryginal.Delete; //delete in qryOryginal casues that qryClone deletes its record too!
end;
So, after cloning the DataSet my qryClone should hold and independent data(at least I thought so). However, performing Delete on qryOryginal causes the same operation on the qryClone. I don't want that.
Any ideas?
I know I could store the data elsewhere, in TClientDataSet perhaps but I would like to try the above solution first.
Thanks in advance for your time.
You can use the recordset of a TADODataSet to clone a TADODataSet.
ds1.Recordset := CloneRecordset(ds2.Recordset);
This version works from Delphi XE. ADOInt is updated with the type library definitions for MDAC 2.8
uses ADOInt, Variants;
function CloneRecordset(const Data: _Recordset): _Recordset;
implementation
function CloneRecordset(const Data: _Recordset): _Recordset;
var
newRec: _Recordset;
stm: Stream;
begin
newRec := CoRecordset.Create as _Recordset;
stm := CoStream.Create;
Data.Save(stm, adPersistADTG);
newRec.Open(stm, EmptyParam, CursorTypeEnum(adOpenUnspecified),
LockTypeEnum(adLockUnspecified), 0);
Result := newRec;
end;
This version must be used for versions of Delphi prior to Delphi XE. ADOR_TLB is generated from msado28.tlb.
uses ADOInt, ADOR_TLB, Variants;
function CloneRecordset(const Data: ADOInt._Recordset): ADOInt._Recordset;
implementation
function CloneRecordset(const Data: ADOInt._Recordset): ADOInt._Recordset;
var
newRec: ADOR_TLB._Recordset;
stm: Stream;
begin
newRec := ADOR_TLB.CoRecordset.Create as ADOR_TLB._Recordset;
stm := CoStream.Create;
(Data as ADOR_TLB._Recordset).Save(stm, adPersistADTG);
newRec.Open(stm, EmptyParam, CursorTypeEnum(adOpenUnspecified),
LockTypeEnum(adLockUnspecified), 0);
Result := newRec as ADOInt._Recordset;
end;
I more like realization with TClientDataSet, because we can freely edit it as we need after copying.
var
MyADOStoredProc: TADOStoredProc;
DataSetProvider: TDataSetProvider;
ClientDataSet: TClientDataSet;
DataSource: TDataSource;
...
// here now we have an opened ADOStoredProc object MyADOStoredProc
// let's copy data from it
DataSetProvider := TDataSetProvider.Create(Self);
DataSetProvider.Name := 'DataSetProvider' + FormatDateTime('_yyyy_mm_dd_hh_nn_ss', Now);
DataSetProvider.DataSet := MyADOStoredProc;
ClientDataSet := TClientDataSet.Create(Self);
ClientDataSet.ProviderName := DataSetProvider.Name;
DataSource := TDataSource.Create(Self);
DataSource.DataSet := ClientDataSet;
ClientDataSet.Open;
MyADOStoredProc.Close;
ClientDataSet.First;
// here we can modify our ClientDataSet as we need, besides MyADOStoredProc is closed
if not ClientDataSet.Eof then
ClientDataSet.Delete;
...
Cloning just clones the cursor on dataset, not duplicating the data kept in the dataset.
If you need to have two independent data, then you have to copy the data from the original dataset to the second one.
If you want to read or modify a single dataset without changing the current cursor on the dataset, then you can use Clone method.
Or second way, use Devexpress dxMemData. Very useful and easy-to-use component.
var
MD: TdxMemData;
SP: TADOStoredProc;
...
...
// and after opening stored procedure:
MD.Close;
MD.Open;
MD.LoadFromDataset(SP);

Resources