xtool/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/AntonE/ORMCDS/ORMCDS.pas

786 lines
31 KiB
ObjectPascal

unit ORMCDS;
interface
uses Data.DB,DBClient,mORMot,SynCommons,RTTI,TypInfo,Classes;
(*2014/12/01 - Version 1
TODO:
Ordered/(multi?)-Indexed or Ordered Arrays with TIDs
TADTFields for SETs?
*)
(*This is Info record that will be attached to root CDS .Tag and to each nested dataset (array or sub-TSQLRecord list) to
BOTH the TDatasetField.Tag and it's detail TClientdataset.Tag.
*)
type TORMCDSinfo = class(TObject)
public
CDS : TClientdataset; (*Nested TClientdataset*)
SQLRecordClass : TSQLRecordClass;(*IF it's SubList (not array), contain type*)
RecordTypeInfo : PTypeInfo; (*IF it';s array (not sublist, contain DynArray PTypeInfo*)
DatasetField : TDatasetField; (*Master CDS.TDatasetField*)
LinkField : RawUTF8; (*If it's SubList (not array), contain link field name that will be =MasterSQLRecord.ID*)
{ArrayKeyFields : RawUTF8;
ArrayOrdered : Boolean;}
(*Implement array comparing routines to handle key-fields in arrays and ordering
for finer-grained 'not touching' array elements when applying updates.
At the moment, when SAVEing, all array items are directly compared and overwritten if different from CDS*)
function IsArrayLink:Boolean;
end;
(*Attempt to create TField descentdants and nested Fields (for arrays) in the CDS*)
procedure ORM_CreateCDSFields(CDS:TClientdataset;AName:RawUTF8;ATypeInfo:PTypeInfo);
(*Add a TSQLRecord sub-class that is linked via DetailSQLrecord.ALinkField=MasterSQLRecord.ID*)
procedure ORM_AddSubField (CDS:TClientdataset;AFieldName,ALinkField:RawUTF8;AClass:TSQLRecordClass);overload;
(*Add a Dynamic Arrray nested field*)
procedure ORM_AddSubField (CDS:TClientdataset;AFieldName:RawUTF8;ADynArrayType:PTypeInfo);overload;
procedure ORM_LoadCDSFields (DB:TSQLRest;CDS:TClientdataset;AName:RawUTF8;AValue:TValue);
function ORM_SaveCDSFields (DB:TSQLRest;CDS:TClientdataset;AName:RawUTF8;var AValue:TValue;AForceUpdate:Boolean):Integer;
(*Link existing TClientdataset by creating TORMCDSinfo record.*)
procedure ORM_LinkCDS(ASourceCDS:TClientDataset;ATypeInfo:PTypeInfo;ALinkField:RawUTF8);
(*Free TORMCDSinfo record info, alternatively free dynamically created TClientdatasets*)
procedure ORM_FreeCDSInfo(CDS:TClientdataset;AFreeCDS:Boolean);
procedure ORM_LoadData(CDS:TClientdataset;aClient: TSQLRest;FormatSQLWhere: PUTF8Char;const BoundsSQLWhere: array of const;const aCustomFieldsCSV: RawUTF8='');
procedure ClearCDS(CDS:TClientDataset);
implementation
uses SysUtils,Datasnap.Provider;
procedure ClearCDS(CDS:TClientDataset);
begin;
if CDS.Active
then begin
CDS.EmptyDataSet;
CDS.Close;
end;
CDS.DataSetField:=nil;
CDS.Fields.Clear;
CDS.FieldDefs.Clear;
CDS.IndexDefs.Clear;
CDS.Params.Clear;
CDS.Aggregates.Clear;
CDS.IndexName := '';
CDS.IndexFieldNames := '';
end;
procedure ORM_LinkCDS(ASourceCDS:TClientDataset;ATypeInfo:PTypeInfo;ALinkField:RawUTF8);
var OInfo : TORMCDSinfo;
Field : TField;
I : Integer;
Cln : TClientdataset;
begin
OInfo:=TORMCDSinfo.Create;
OInfo.CDS:=ASourceCDS;
case ATypeInfo.Kind of
tkClass : begin
{$IFDEF DEBUG}
if not GetTypeData(ATypeInfo).ClassType.InheritsFrom(TSQLRecord)
then raise Exception.Create('ErrorMessage22');
{$ENDIF DEBUG}
OInfo.SQLRecordClass:=TSQLRecordClass(GetTypeData(ATypeInfo).ClassType);
OInfo.DatasetField :=ASourceCDS.DataSetField;
if ASourceCDS.DataSetField<>nil
then OInfo.LinkField:=ALinkField;
end;
tkDynArray:begin
if (ATypeInfo^.Kind<>tkDynArray)
then raise Exception.Create('ErrorMessage65');
OInfo.RecordTypeInfo:=ATypeInfo;
end
end;
ASourceCDS.Tag:=Integer(OInfo);
if ASourceCDS.DataSetField<>nil
then begin
ASourceCDS.DataSetField.Tag:=Integer(OInfo);
end;
end;
procedure ORM_FreeCDSInfo(CDS:TClientdataset;AFreeCDS:Boolean);
var Field : TField;
OInfo : TORMCDSinfo;
begin
if (TObject(CDS.Tag) is TORMCDSinfo)
then (TObject(CDS.Tag) as TORMCDSinfo).Free;
CDS.Tag:=0;
for Field in CDS.Fields do
begin
if (Field is TDatasetField)
then begin
OInfo:=TORMCDSinfo(Field.Tag);
if Assigned(OInfo)
then ORM_FreeCDSInfo(OInfo.CDS,AFreeCDS);
Field.Tag:=0;
end;
end;
if AFreeCDS
then CDS.Free;
end;
procedure ORM_LoadData(CDS:TClientdataset;aClient: TSQLRest;
FormatSQLWhere: PUTF8Char;
const BoundsSQLWhere: array of const;
const aCustomFieldsCSV: RawUTF8='');
var qRec : TSQLRecord;
OInfo:TORMCDSinfo;
OldDisabled : Boolean;
begin
OldDisabled:=CDS.ControlsDisabled;
if not OldDisabled
then CDS.DisableControls;
CDS.LogChanges:=False;
{$IFDEF DEBUG}
if not (TObject(CDS.Tag) is TORMCDSinfo)
then raise Exception.Create('ErrorMessage118');
{$ENDIF DEBUG}
OInfo:=TORMCDSinfo(CDS.Tag);
qRec:=OInfo.SQLRecordClass.CreateAndFillPrepare(aClient,FormatSQLWhere,BoundsSQLWhere,aCustomFieldsCSV);
while qRec.FillOne do
begin
ORM_LoadCDSFields(aClient,CDS,'root',qRec);
end;
qRec.Free;
CDS.MergeChangeLog;
CDS.LogChanges:=True;
if not OldDisabled
then CDS.EnableControls;
end;
procedure ORM_CreateCDSFields(CDS:TClientdataset;AName:RawUTF8;ATypeInfo:PTypeInfo);
var Ctx : TRttiContext;
Typ : TRttiType;
Fld : TField;
Cln : TClientDataset;
I : Integer;
S : String;
RField:TRttiField;
RProp :TRttiProperty;
OInfo :TORMCDSinfo;
begin
Ctx:=TRttiContext.Create;
Typ:=Ctx.GetType(ATypeInfo);
Fld:=CDS.FindField(AName);
case Typ.TypeKind of
tkString,
tkUString,
tkLString : begin
if Fld<>nil
then exit;
Fld:=TWideStringField.Create(CDS);
Fld.Name :=CDS.Name+AName;
Fld.FieldName:=AName;
Fld.DataSet:=CDS;
end;
tkInteger : begin
if Fld<>nil
then exit;
Fld:=TIntegerField.Create(CDS);
Fld.Name :=CDS.Name+AName;
Fld.FieldName:=AName;
Fld.DataSet:=CDS;
end;
tkEnumeration: begin
{???}{Maybe create a lookupfield?}
if Fld<>nil
then exit;
Fld:=TIntegerField.Create(CDS);
Fld.Name :=CDS.Name+AName;
Fld.FieldName:=AName;
Fld.DataSet:=CDS;
end;
tkSet : begin
case TRttiSetType(typ).ElementType.TypeKind of
tkEnumeration : begin
for I := TRttiEnumerationType(TRttiSetType(Typ).ElementType).MinValue to TRttiEnumerationType(TRttiSetType(Typ).ElementType).MaxValue do
begin
S:=GetEnumName(TRttiSetType(Typ).ElementType.Handle,I);
Fld:=CDS.FindField(AName+'_'+S);
if Fld<>nil
then exit;
Fld:=TBooleanField.Create(CDS);
Fld.Name :=CDS.Name+AName+'_'+S;
Fld.FieldName:=AName+'_'+S;
Fld.DataSet:=CDS;
end;
end
else raise Exception.Create('Error Message')
end;
end;
tkFloat : begin
if Fld<>nil
then exit;
Fld:=TFloatField.Create(CDS);
Fld.Name :=CDS.Name+AName;
Fld.FieldName:=AName;
Fld.DataSet:=CDS;
end;
tkInt64 : begin
if Fld<>nil
then exit;
Fld:=TLargeintField.Create(CDS);
Fld.Name :=CDS.Name+AName;
Fld.FieldName:=AName;
Fld.DataSet:=CDS;
end;
tkDynArray : begin
ORM_AddSubField (CDS,AName,TRttiDynamicArrayType(Typ).ElementType.Handle);
end;
tkRecord : begin
for RField in Typ.GetFields do
ORM_CreateCDSFields(CDS,RField.Name,RField.FieldType.Handle);
end;
tkClass : begin
if CDS.Tag=0
then begin(*Root CDS, create Info*)
OInfo:=TORMCDSinfo.Create;
OInfo.CDS:=CDS;
OInfo.SQLRecordClass:=TSQLRecordClass(TRttiInstanceType(Typ).MetaclassType);
CDS.Tag:=Integer(OInfo);
end;
for RProp in Typ.GetProperties do
begin
if RProp.IsWritable
then ORM_CreateCDSFields(CDS,RProp.Name,RProp.PropertyType.Handle);
end;
end
else raise Exception.Create('Error Message');
end;
Ctx.Free;
end;
procedure ORM_AddSubField (CDS:TClientdataset;AFieldName,ALinkField:RawUTF8;AClass:TSQLRecordClass);
var OInfo : TORMCDSinfo;
begin
OInfo:=TORMCDSinfo.Create;
OInfo.DatasetField:=TDatasetField.Create(CDS);
OInfo.DatasetField.Name :=CDS.Name+AFieldName;
OInfo.DatasetField.FieldName:=AFieldName;
OInfo.DatasetField.DataSet :=CDS;
OInfo.CDS :=TClientDataset.Create(CDS.Owner);
OInfo.CDS.Name:=AFieldName;
OInfo.CDS.DataSetField :=TDatasetField(OInfo.DatasetField);
OInfo.LinkField :=ALinkField;
OInfo.SQLRecordClass :=AClass;
OInfo.DatasetField.Tag:=Integer(OInfo);
OInfo.CDS .Tag:=Integer(OInfo);
ORM_CreateCDSFields(OInfo.CDS,AFieldName,AClass.ClassInfo);
end;
procedure ORM_AddSubField (CDS:TClientdataset;AFieldName:RawUTF8;ADynArrayType:PTypeInfo);
var OInfo:TORMCDSinfo;
begin
OInfo:=TORMCDSinfo.Create;
OInfo.DatasetField:=TDatasetField.Create(CDS);
OInfo.DatasetField.Name :=CDS.Name+AFieldName;
OInfo.DatasetField.FieldName:=AFieldName;
OInfo.DatasetField.DataSet :=CDS;
OInfo.CDS :=TClientDataset.Create(CDS.Owner);
OInfo.CDS.Name:=AFieldName;
OInfo.CDS.DataSetField :=TDatasetField(OInfo.DatasetField);
OInfo.RecordTypeInfo :=ADynArrayType;
ORM_CreateCDSFields(OInfo.CDS,AFieldName,ADynArrayType);
OInfo.DatasetField.Tag:=Integer(OInfo);
OInfo.CDS .Tag:=Integer(OInfo);
end;
procedure ORM_LoadCDSFields(DB:TSQLRest;CDS:TClientdataset;AName:RawUTF8;AValue:TValue);
var Ctx : TRttiContext;
Typ : TRttiType;
Fld : TField;
I : Integer;
S : String;
RField:TRttiField;
RProp :TRttiProperty;
BValue: TValue;
Obj : TObject;
DA : TDynArray;
Rec : TSQLRecord;
I64 : TID;
RSTR : PUTF8Char;
OInfo : TORMCDSinfo;
begin
Fld:=CDS.FindField(AName);
{ if Fld=nil
then exit;}
Ctx:=TRttiContext.Create;
Typ:=Ctx.GetType(AValue.TypeInfo);
case Typ.TypeKind of
tkString,
tkUString,
tkLString : begin
Fld.AsString:=AValue.AsString;
end;
tkInteger : begin
Fld.AsInteger:=AValue.AsInteger;
end;
tkEnumeration: begin
Fld.AsVariant:=Integer(AValue.GetReferenceToRawData^);
end;
tkSet : begin
case TRttiSetType(typ).ElementType.TypeKind of
tkEnumeration : begin
I:=Integer(AValue.GetReferenceToRawData^);
for I := TRttiEnumerationType(TRttiSetType(Typ).ElementType).MinValue to TRttiEnumerationType(TRttiSetType(Typ).ElementType).MaxValue do
begin
S:=GetEnumName(TRttiSetType(Typ).ElementType.Handle,I);
Fld:=CDS.FindField(AName+'_'+S);
if Fld<>nil
then begin
if ((1 shl I) and Integer(AValue.GetReferenceToRawData^))=(1 shl I)
then Fld.AsBoolean:=True
else Fld.AsBoolean:=False;
end;
end;
end
else raise Exception.Create('Error Message')
end;
end;
tkFloat : begin
Fld.AsFloat:=AValue.AsExtended;
end;
tkInt64 : begin
Fld.AsLargeInt:=AValue.AsInt64;
end;
tkDynArray : begin
if Fld<>nil
then begin
{$IFDEF DEBUG}
if not (TObject(Fld.Tag) is TORMCDSinfo)
then raise Exception.Create('ErrorMessage256');
{$ENDIF DEBUG}
OInfo:=TORMCDSinfo(Fld.Tag);
end;
{BValue.Make(nil,TRttiDynamicArrayType(Typ).ElementType.Handle,BValue);}
for I := 0 to Pred(AValue.GetArrayLength) do
begin
BValue:=AValue.GetArrayElement(I);
OInfo.CDS.Insert;
ORM_LoadCDSFields(DB,OInfo.CDS,'Value',BValue);
{ Cln.Post;}
end;
end;
tkRecord : begin
CDS.Insert;
for RField in Typ.GetFields do
begin
BValue:=RField.GetValue(AValue.GetReferenceToRawData);
ORM_LoadCDSFields(DB,CDS,RField.Name,BValue);
end;
{ CDS.Post;}
end;
tkClass : begin
Obj:=AValue.AsObject;
CDS.Insert;
for I := 0 to Pred(CDS.Fields.Count) do
begin
if (CDS.Fields[I] is TDataSetField)
then begin
{$IFDEF DEBUG}
if not(TObject(CDS.Fields[I].Tag) is TORMCDSinfo)
then raise Exception.Create('ErrorMessage286');
{$ENDIF DEBUG}
OInfo:=TORMCDSinfo(CDS.Fields[I].Tag);
if OInfo.IsArrayLink
then begin(*Array link*)
end
else begin(*Dataset link*)
RStr:=PUTF8Char(OInfo.LinkField+' = ?');
I64 :=TSQLRecord(Obj).ID;
Rec:=OInfo.SQLRecordClass.CreateAndFillPrepare(DB,RStr,[I64]);
while Rec.FillOne do
begin
ORM_LoadCDSFields(DB,OInfo.CDS,CDS.Fields[I].FieldName,Rec);
end;
Rec.Free;
continue;
end;
end;
RProp:=Typ.GetProperty(CDS.Fields[I].FieldName);
if RProp<>nil
then begin
(*BValue.Make(nil,RProp.PropertyType.Handle,BValue);*)
BValue:=RProp.GetValue(AValue.AsObject);
ORM_LoadCDSFields(DB,CDS,RProp.Name,BValue);
end;
end;
end
else raise Exception.Create('Error Message');
end;
Ctx.Free;
end;
function ORM_SaveCDSFields(DB:TSQLRest;CDS:TClientdataset;AName:RawUTF8;var AValue:TValue;AForceUpdate:Boolean):Integer;
(*AForceUpdate introduced to force update on usInserted record as TClientdataset does not always reflect OldValue=null on newly inserted record but rather OldValue=NewValue*)
var Ctx : TRttiContext;
Typ : TRttiType;
Fld : TField;
I,I2: Integer;
S : String;
RField:TRttiField;
RProp :TRttiProperty;
BValue: TValue;
Obj : TObject;
DA : TDynArray;
Rec : TSQLRecord;
I64 : TID;
RSTR : PUTF8Char;
PDS : TPacketDataSet;
Changed:Integer;
OInfo,
BInfo : TORMCDSinfo;
ArrLen : NativeInt;
P,PP : Pointer;
US : TUpdateStatus;
begin
Result:=0;
Fld:=CDS.FindField(AName);
{ if Fld=nil
then exit;}
Ctx:=TRttiContext.Create;
Typ:=Ctx.GetType(AValue.TypeInfo);
case Typ.TypeKind of
tkString,
tkLString : begin
if not AForceUpdate and (Fld.OldValue=Fld.NewValue)
then exit;
Inc(Result);
AValue:=Fld.AsString;
end;
tkInteger : begin
if not AForceUpdate and (Fld.OldValue=Fld.NewValue)
then exit;
Inc(Result);
AValue:=Fld.AsInteger;
end;
tkEnumeration: begin
if not AForceUpdate and (Fld.OldValue=Fld.NewValue)
then exit;
Inc(Result);
case Fld.DataType of
ftInteger: Integer(AValue.GetReferenceToRawData^):=Fld.AsInteger;
ftBoolean: Boolean(AValue.GetReferenceToRawData^):=Fld.AsBoolean
else raise Exception.Create('ErrorMessage429');
end;
end;
tkSet : begin
Inc(Result);
I:=Integer(AValue.GetReferenceToRawData^);
case TRttiSetType(typ).ElementType.TypeKind of
tkEnumeration : begin
I2:=0;
for I := TRttiEnumerationType(TRttiSetType(Typ).ElementType).MinValue to TRttiEnumerationType(TRttiSetType(Typ).ElementType).MaxValue do
begin
S:=GetEnumName(TRttiSetType(Typ).ElementType.Handle,I);
Fld:=CDS.FindField(AName+'_'+S);
if Fld<>nil
then begin
if Fld.AsBoolean
then I2:=I2 or (1 shl I);
end;
end;
if AForceUpdate or (I2<>Integer(AValue.GetReferenceToRawData^))
then begin
Inc(Result);
Integer(AValue.GetReferenceToRawData^):=I2;
end;
end
else raise Exception.Create('Error Message')
end;
end;
tkFloat : begin
if not AForceUpdate and (Fld.OldValue=Fld.NewValue)
then exit;
Inc(Result);
AValue:=Fld.AsFloat;
end;
tkInt64 : begin
if not AForceUpdate and (Fld.OldValue=Fld.NewValue)
then exit;
Inc(Result);
AValue:=Fld.AsLargeInt;
end;
tkDynArray : begin
if Fld=nil
then exit;
BInfo:=TORMCDSinfo(Fld.Tag);
BValue.Make(nil,TRttiDynamicArrayType(Typ).ElementType.Handle,BValue);
I:=AValue.GetArrayLength;
P :=PPointer(AValue.GetReferenceToRawData)^;
if I<>BInfo.CDS.RecordCount
then begin
Inc(Result);
{ ArrLen:=0;
DynArraySetLength(P,Typ.Handle,1,@ArrLen);}
(***************METHOD A *************************)
DA.Init(Typ.Handle,P);
I:=DA.Count;
DA.Count:=I+1;
I:=DA.Count;
(*************************************************)
(***************METHOD B *************************)
{ ArrLen:=BInfo.CDS.RecordCount;
DynArraySetLength(P,Typ.Handle,1,@ArrLen);}
(*************************************************)
AValue.Make(@P,Typ.Handle,AValue);(*Seems here old array in AValue is not dereferenced, causing memory leaks*)
I:=AValue.GetArrayLength;
(*I:=DA.Count;*)
end;
BInfo.CDS.First;
while not BInfo.CDS.Eof do
begin
US:=BInfo.CDS.UpdateStatus;
{TValue.Make(nil,TRttiDynamicArrayType(Typ).ElementType.Handle,BValue);}
BValue:=AValue.GetArrayElement(Pred(BInfo.CDS.RecNo));
if ORM_SaveCDSFields(DB,BInfo.CDS,(*Fld.FieldName*)BInfo.CDS.RecNo.ToString,BValue,True{(US=usInserted)}{AForceUpdate})>0
then begin
AValue.SetArrayElement(Pred(BInfo.CDS.RecNo),BValue);
Inc(Result);
end;
BInfo.CDS.Next;
end;
end;
tkRecord : begin
{$IFDEF DEBUG}
if not (TObject(CDS.Tag) is TORMCDSinfo)
then raise Exception.Create('ErrorMessage428');
{$ENDIF DEBUG}
OInfo:=TORMCDSinfo(CDS.Tag);
for Fld in CDS.Fields do
begin
RField:=Typ.GetField(Fld.FieldName);
{???}{Set/ENum fields!!!}
if RField=nil
then continue;
BInfo:=TORMCDSinfo(CDS.Fields[I].Tag);
if Assigned(BInfo) and not BInfo.IsArrayLink (*Skip sublists, do them separate later*)
then continue;
BValue.MakeWithoutCopy(nil,RField.FieldType.Handle,BValue);
BValue:=RField.GetValue(AValue.GetReferenceToRawData);
if ORM_SaveCDSFields(DB,OInfo.CDS,Fld.FieldName,BValue,AForceUpdate)>0
then begin
Inc(Result);
RField.SetValue(AValue.GetReferenceToRawData,BValue);
end;
end;
(*Check special-case SET fields*)
for RField in Typ.GetFields do
begin
case RField.FieldType.TypeKind of
tkSet : begin
BValue.From(RField.GetValue(AValue.GetReferenceToRawData));
BValue:=RField.GetValue(AValue.GetReferenceToRawData);
if ORM_SaveCDSFields(DB,CDS,RField.Name,BValue,AForceUpdate)>0
then begin
RField.SetValue(AValue.GetReferenceToRawData,BValue);
Inc(Result);
end;
end;
end;
end;
end;
tkClass : begin
{$IFDEF DEBUG}
if not (TObject(CDS.Tag) is TORMCDSinfo)
then raise Exception.Create('ErrorMessage428');
{$ENDIF DEBUG}
OInfo:=TORMCDSinfo(CDS.Tag);
Rec:=TSQLRecord(AValue.AsObject);
(*Update local fields*)
for I := 0 to Pred(CDS.Fields.Count) do
begin
Fld:=CDS.Fields[I];
RProp:=Typ.GetProperty(Fld.FieldName);
if RProp=nil
then continue;
BInfo:=TORMCDSinfo(CDS.Fields[I].Tag);
if Assigned(BInfo) and not BInfo.IsArrayLink (*Skip sublists, do them separate later*)
then continue;
BValue.MakeWithoutCopy(nil,RProp.PropertyType.Handle,BValue);
BValue:=RProp.GetValue(Rec);
if ORM_SaveCDSFields(DB,CDS,Fld.FieldName,BValue,AForceUpdate)>0
then begin
Changed:=Changed+1;
RProp.SetValue(Rec,BValue);
end;
end;
if Changed>0
then begin
if Rec.ID=0
then begin
I64:=DB.Add(Rec,True);
OInfo.CDS.Edit;
OInfo.CDS.FieldByName('ID').AsLargeInt:=I64;
end
else begin
I64:=Rec.ID;
DB.Update(Rec);
end;
end;
(*Update SubLists*)
for I := 0 to Pred(CDS.Fields.Count) do
begin
if not(TObject(CDS.Fields[I].Tag) is TORMCDSinfo) or ((TObject(CDS.Fields[I].Tag) as TORMCDSinfo).IsArrayLink)
then continue;
BInfo:=TORMCDSinfo(CDS.Fields[I].Tag);
{$IFDEF DEBUG}
if BInfo.LinkField=''
then raise Exception.Create('ErrorMessage457');
{$ENDIF}
BValue.MakeWithoutCopy(nil,TypeInfo(TSQLRecordClass),BValue);
BValue:=BInfo.SQLRecordClass;
BInfo.CDS.First;
while not BInfo.CDS.EOF do
begin
if (BInfo.CDS.FieldByName(BInfo.LinkField).AsLargeInt<>I64)
then begin
BInfo.CDS.Edit;
BInfo.CDS.FieldByName(BInfo.LinkField).AsLargeInt:=I64;
end;
BInfo.CDS.Next;
end;
Changed:=Changed+ORM_SaveCDSFields(DB,BInfo.CDS,CDS.Fields[I].FieldName,BValue,AForceUpdate);
BInfo.CDS.EnableControls;
end;
Result:=Changed;
end;
tkClassRef : begin
{$IFDEF DEBUG}
if not (TObject(CDS.Tag) is TORMCDSinfo)
then raise Exception.Create('ErrorMessage427');
{$ENDIF DEBUG}
CDS.DisableControls;
if CDS.State<>dsBrowse
then try
CDS.Post;
except
CDS.EnableControls;
exit;
end;
CDS.First;(*Fix uplinks before TPacketDataset fix*)
PDS := TPacketDataSet.Create(nil);
PDS.Data:=CDS.Data;
PDS.InitAltRecBuffers(True);
PDS.Free;
OInfo:=TORMCDSinfo(CDS.Tag);
CDS.First;(*Fix uplinks before TPacketDataset fix*)
if OInfo.LinkField<>''
then begin
while not CDS.EOF do
begin
if CDS.FieldByName(OInfo.LinkField).AsLargeInt=0
then begin
CDS.Edit;
CDS.FieldByName(OInfo.LinkField).AsLargeInt:=TClientDataset(OInfo.DatasetField.DataSet).FieldByNAme('ID').AsLargeint;
Inc(Changed);
end;
CDS.Next;
end;
end;
CDS.First;
CDS.StatusFilter:=[usDeleted];
CDS.First;
while not CDS.EOF do
begin
I64:=CDS.FieldByName('ID').AsLargeInt;
(*Delete all child records*)
for I := 0 to Pred(CDS.Fields.Count) do
begin
if not(TObject(CDS.Fields[I].Tag) is TORMCDSinfo) or ((TObject(CDS.Fields[I].Tag) as TORMCDSinfo).IsArrayLink)
then continue;
(*Delete all children tables*)
RStr:=PUTF8Char(TORMCDSinfo(CDS.Fields[I].Tag).LinkField+' = ?');
DB.Delete(TORMCDSinfo(CDS.Fields[I].Tag).SQLRecordClass,RStr^,[I64]);
end;
DB.Delete(OInfo.SQLRecordClass,I64);
CDS.Next;
Inc(Changed);
end;
CDS.StatusFilter:=[];
Rec:=OInfo.SQLRecordClass.Create;
CDS.First;
while not CDS.EOF do
begin
(*Check all fields for changes. Arrays must update as well.*)
US:=CDS.UpdateStatus;
{ if (CDS.UpdateStatus=usModified)or(CDS.UpdateStatus=usInserted)
then begin}
(*Update all fields+Arrays*)
I64:=CDS.FieldByName('ID').AsLargeInt;
{$IFDEF DEBUG}
{ US:=CDS.UpdateStatus;
if (I64=0)and (US=usModified)
then raise Exception.Create('ErrorMessage506');}
{$ENDIF DEBUG}
if I64=0
then begin
Rec.ClearProperties;
Changed:=Changed+1;
end
else DB.Retrieve(I64,Rec,True);
BValue:=Rec;
Changed:=Changed+ORM_SaveCDSFields(DB,CDS,CDS.Fields[I].FieldName,BValue,(I64=0));
if I64<>0
then DB.Unlock(Rec);
{end;}
CDS.Next;
end;
CDS.EnableControls;
Rec.Free;
Result:=Changed;
end
else raise Exception.Create('Error Message');
end;
Ctx.Free;
end;
{ TORMCDSinfo }
function TORMCDSinfo.IsArrayLink: Boolean;
begin
Result:=LinkField='';
end;
end.