First implementation for foDoNotInsert and foDoNotUpdate

This commit is contained in:
Daniele Teti 2024-03-13 13:00:11 +01:00
parent 09ecb5f5a1
commit ab92225d12
6 changed files with 145 additions and 21 deletions

View File

@ -207,7 +207,6 @@ var
lModuleName: string; lModuleName: string;
lPath: string; lPath: string;
lFormat: string; lFormat: string;
lNow: TDateTime;
begin begin
{$IF Defined(Android)} {$IF Defined(Android)}
lModuleName := TAndroidHelper.ApplicationTitle.Replace(' ', '_', [rfReplaceAll]); lModuleName := TAndroidHelper.ApplicationTitle.Replace(' ', '_', [rfReplaceAll]);
@ -221,7 +220,6 @@ begin
lFormat := fLogFileNameFormat; lFormat := fLogFileNameFormat;
lPath := fLogsFolder; lPath := fLogsFolder;
lNow := Now();
lFormat := lFormat lFormat := lFormat
.Replace('{module}', lModuleName, [rfReplaceAll]) .Replace('{module}', lModuleName, [rfReplaceAll])
.Replace('{number}', aFileNumber.ToString.PadLeft(2,'0') , [rfReplaceAll]) .Replace('{number}', aFileNumber.ToString.PadLeft(2,'0') , [rfReplaceAll])

View File

@ -231,6 +231,32 @@ type
property City: string read fCity write fCity; property City: string read fCity write fCity;
end; end;
[MVCNameCase(ncLowerCase)]
[MVCTable('customers')]
TCustomerWithOptions = class(TCustomEntity)
private
{$IFNDEF USE_SEQUENCES}
[MVCTableField('id', [foPrimaryKey, foAutoGenerated])]
{$ELSE}
[MVCTableField('id', [foPrimaryKey, foAutoGenerated],
'SEQ_CUSTOMERS_ID' { required for interbase } )]
{$ENDIF}
fID: Integer;
[MVCTableField('code', [foDoNotInsert, foDoNotUpdate])]
fCode: NullableString;
[MVCTableField('description', [foDoNotInsert])]
fCompanyName: string;
[MVCTableField('city', [foDoNotUpdate])]
fCity: string;
public
property ID: Integer read fID write fID;
property Code: NullableString read fCode write fCode;
property CompanyName: string read fCompanyName write fCompanyName;
property City: string read fCity write fCity;
end;
[MVCNameCase(ncLowerCase)] [MVCNameCase(ncLowerCase)]
[MVCTable('order_details')] [MVCTable('order_details')]
TOrderDetail = class(TCustomEntity) TOrderDetail = class(TCustomEntity)

View File

@ -2,7 +2,7 @@ object MainForm: TMainForm
Left = 0 Left = 0
Top = 0 Top = 0
Caption = 'TMVCActiveRecord - ShowCase' Caption = 'TMVCActiveRecord - ShowCase'
ClientHeight = 626 ClientHeight = 660
ClientWidth = 1094 ClientWidth = 1094
Color = clBtnFace Color = clBtnFace
Font.Charset = DEFAULT_CHARSET Font.Charset = DEFAULT_CHARSET
@ -14,7 +14,7 @@ object MainForm: TMainForm
OnShow = FormShow OnShow = FormShow
DesignSize = ( DesignSize = (
1094 1094
626) 660)
TextHeight = 13 TextHeight = 13
object btnCRUD: TButton object btnCRUD: TButton
Left = 8 Left = 8
@ -27,7 +27,7 @@ object MainForm: TMainForm
end end
object btnSelect: TButton object btnSelect: TButton
Left = 8 Left = 8
Top = 242 Top = 283
Width = 121 Width = 121
Height = 33 Height = 33
Caption = 'Queries' Caption = 'Queries'
@ -38,7 +38,7 @@ object MainForm: TMainForm
Left = 280 Left = 280
Top = 8 Top = 8
Width = 806 Width = 806
Height = 610 Height = 644
Anchors = [akLeft, akTop, akRight, akBottom] Anchors = [akLeft, akTop, akRight, akBottom]
Ctl3D = True Ctl3D = True
DoubleBuffered = True DoubleBuffered = True
@ -55,10 +55,11 @@ object MainForm: TMainForm
TabOrder = 2 TabOrder = 2
WantReturns = False WantReturns = False
WordWrap = False WordWrap = False
ExplicitHeight = 610
end end
object btnRelations: TButton object btnRelations: TButton
Left = 8 Left = 8
Top = 281 Top = 322
Width = 121 Width = 121
Height = 35 Height = 35
Caption = 'Relations' Caption = 'Relations'
@ -67,7 +68,7 @@ object MainForm: TMainForm
end end
object btnInheritance: TButton object btnInheritance: TButton
Left = 8 Left = 8
Top = 322 Top = 363
Width = 121 Width = 121
Height = 34 Height = 34
Caption = 'Inheritance' Caption = 'Inheritance'
@ -76,7 +77,7 @@ object MainForm: TMainForm
end end
object btnValidation: TButton object btnValidation: TButton
Left = 8 Left = 8
Top = 362 Top = 403
Width = 121 Width = 121
Height = 34 Height = 34
Caption = 'Validation' Caption = 'Validation'
@ -94,7 +95,7 @@ object MainForm: TMainForm
end end
object btnRQL: TButton object btnRQL: TButton
Left = 8 Left = 8
Top = 402 Top = 443
Width = 121 Width = 121
Height = 34 Height = 34
Caption = 'RQL Query' Caption = 'RQL Query'
@ -240,7 +241,7 @@ object MainForm: TMainForm
end end
object btnReadOnly: TButton object btnReadOnly: TButton
Left = 8 Left = 8
Top = 442 Top = 483
Width = 121 Width = 121
Height = 34 Height = 34
Caption = 'Read/Only Entities' Caption = 'Read/Only Entities'
@ -249,7 +250,7 @@ object MainForm: TMainForm
end end
object btnSpeed: TButton object btnSpeed: TButton
Left = 8 Left = 8
Top = 482 Top = 523
Width = 121 Width = 121
Height = 34 Height = 34
Caption = 'Metadata Speed Test' Caption = 'Metadata Speed Test'
@ -285,7 +286,7 @@ object MainForm: TMainForm
end end
object btnIntegersAsBool: TButton object btnIntegersAsBool: TButton
Left = 8 Left = 8
Top = 522 Top = 563
Width = 121 Width = 121
Height = 34 Height = 34
Caption = 'Integers As Booleans' Caption = 'Integers As Booleans'
@ -294,7 +295,7 @@ object MainForm: TMainForm
end end
object btnObjectVersion: TButton object btnObjectVersion: TButton
Left = 8 Left = 8
Top = 562 Top = 603
Width = 121 Width = 121
Height = 34 Height = 34
Caption = 'Object Version' Caption = 'Object Version'
@ -310,6 +311,15 @@ object MainForm: TMainForm
TabOrder = 30 TabOrder = 30
OnClick = btnCustomTableClick OnClick = btnCustomTableClick
end end
object btnCRUDWithOptions: TButton
Left = 8
Top = 242
Width = 121
Height = 33
Caption = 'CRUD With Fields Opts'
TabOrder = 31
OnClick = btnCRUDWithOptionsClick
end
object FDConnection1: TFDConnection object FDConnection1: TFDConnection
Left = 312 Left = 312
Top = 40 Top = 40

View File

@ -63,6 +63,7 @@ type
btnIntegersAsBool: TButton; btnIntegersAsBool: TButton;
btnObjectVersion: TButton; btnObjectVersion: TButton;
btnCustomTable: TButton; btnCustomTable: TButton;
btnCRUDWithOptions: TButton;
procedure btnCRUDClick(Sender: TObject); procedure btnCRUDClick(Sender: TObject);
procedure btnInheritanceClick(Sender: TObject); procedure btnInheritanceClick(Sender: TObject);
procedure btnMultiThreadingClick(Sender: TObject); procedure btnMultiThreadingClick(Sender: TObject);
@ -96,6 +97,7 @@ type
procedure btnIntegersAsBoolClick(Sender: TObject); procedure btnIntegersAsBoolClick(Sender: TObject);
procedure btnObjectVersionClick(Sender: TObject); procedure btnObjectVersionClick(Sender: TObject);
procedure btnCustomTableClick(Sender: TObject); procedure btnCustomTableClick(Sender: TObject);
procedure btnCRUDWithOptionsClick(Sender: TObject);
private private
procedure Log(const Value: string); procedure Log(const Value: string);
procedure LoadCustomers(const HowManyCustomers: Integer = 50); procedure LoadCustomers(const HowManyCustomers: Integer = 50);
@ -412,6 +414,78 @@ begin
end; end;
end; end;
procedure TMainForm.btnCRUDWithOptionsClick(Sender: TObject);
var
lCustomer: TCustomerWithOptions;
lID: Integer;
begin
Log('** CRUD test with fields options');
lCustomer := TCustomerWithOptions.Create;
try
{
'Code' will not be persisted on table because defined as 'foReadOnly'
}
lCustomer.Code := '1234'; // "Code" will be skipped in insert and in update as well
lCustomer.CompanyName := 'Google Inc.'; // "CompanyName" will be skipped in insert
lCustomer.City := 'Montain View, CA'; // "City" will be skipped in update
lCustomer.Insert;
lID := lCustomer.ID;
Log('Just inserted Customer ' + lID.ToString + ' with fields options');
finally
lCustomer.Free;
end;
//let's check that code is empty
lCustomer := TMVCActiveRecord.GetByPK<TCustomerWithOptions>(lID);
try
Assert(lCustomer.Code.IsNull); // it's null
Assert(lCustomer.CompanyName.IsEmpty); //empty string
Assert(lCustomer.City = 'Montain View, CA'); //inserted
lCustomer.Code := '1234'; // "Code" will be skipped in insert and in update as well
lCustomer.CompanyName := 'Google Inc.'; // "CompanyName" will be saved
lCustomer.City := 'Via Roma 10, ITALY'; // "City" will be skipped in update
lCustomer.Update;
finally
lCustomer.Free;
end;
//let's check
lCustomer := TMVCActiveRecord.GetByPK<TCustomerWithOptions>(lID);
try
Assert(lCustomer.Code.IsNull); // it's null
Assert(lCustomer.CompanyName = 'Google Inc.'); //correctly updated
Assert(lCustomer.City = 'Montain View, CA'); // not updated, mantains old value
finally
lCustomer.Free;
end;
{
//if underlying field is not null, it is loaded as usual
TMVCActiveRecord.CurrentConnection.ExecSQL('update customers set code = ''XYZ'' where id = ?', [lID]);
lCustomer := TMVCActiveRecord.GetByPK<TCustomerWithReadOnlyFields>(lID);
try
Assert('XYZ' = lCustomer.Code);
lCustomer.CompanyName := lCustomer.CompanyName + ' changed!';
lCustomer.Code := 'this code will not be saved';
lCustomer.Update; //do not save field "code"
Log('Just updated Customer ' + lID.ToString);
finally
lCustomer.Free;
end;
//but being foReadOnly is not updated
lCustomer := TMVCActiveRecord.GetByPK<TCustomerWithReadOnlyFields>(lID);
try
Assert('XYZ' = lCustomer.Code);
lCustomer.Delete;
Log('Just deleted Customer ' + lID.ToString + ' with a R/O field');
finally
lCustomer.Free;
end;
}
end;
procedure TMainForm.btnCRUDWithStringPKsClick(Sender: TObject); procedure TMainForm.btnCRUDWithStringPKsClick(Sender: TObject);
var var
lCustomer: TCustomerWithCode; lCustomer: TCustomerWithCode;

View File

@ -66,9 +66,11 @@ type
TMVCActiveRecord = class; TMVCActiveRecord = class;
TMVCActiveRecordFieldOption = (foPrimaryKey, { it's the primary key of the mapped table } TMVCActiveRecordFieldOption = (foPrimaryKey, { it's the primary key of the mapped table }
foAutoGenerated, { not written, read - similar to readonly } foAutoGenerated, { not written, read - similar to readonly }
foReadOnly, { not written, read } foReadOnly, { not written, read - like foDoNotInsert+foDoNotUpdate }
foWriteOnly, { written, not read } foWriteOnly, { written, not read }
foVersion {used for versioning, only one field with foVersion is allowed in class} foVersion, {used for versioning, only one field with foVersion is allowed in class}
foDoNotInsert, { this field is not included in SQL INSERT commands }
foDoNotUpdate { this field is not included in SQL UPDATE commands }
); );
TMVCActiveRecordFieldOptions = set of TMVCActiveRecordFieldOption; TMVCActiveRecordFieldOptions = set of TMVCActiveRecordFieldOption;
TMVCEntityAction = (eaCreate, eaRetrieve, eaUpdate, eaDelete); TMVCEntityAction = (eaCreate, eaRetrieve, eaUpdate, eaDelete);
@ -104,7 +106,7 @@ type
FieldName: string; FieldName: string;
FieldOptions: TMVCActiveRecordFieldOptions; FieldOptions: TMVCActiveRecordFieldOptions;
DataTypeName: string; DataTypeName: string;
Writeable, Readable, IsVersion: Boolean; Writeable, Readable, Insertable, Updatable, IsVersion: Boolean;
procedure EndUpdates; procedure EndUpdates;
end; end;
@ -3899,7 +3901,7 @@ begin
begin begin
Result := Result + GetFieldNameForSQL(lPair.Value.FieldName) + ' = ' + Result := Result + GetFieldNameForSQL(lPair.Value.FieldName) + ' = ' +
GetParamNameForSQL(lPair.Value.FieldName) + ' + 1,'; GetParamNameForSQL(lPair.Value.FieldName) + ' + 1,';
end else if lPair.Value.Writeable then end else if lPair.Value.Updatable then
begin begin
Result := Result + GetFieldNameForSQL(lPair.Value.FieldName) + ' = :' + Result := Result + GetFieldNameForSQL(lPair.Value.FieldName) + ' = :' +
GetParamNameForSQL(lPair.Value.FieldName) + ','; GetParamNameForSQL(lPair.Value.FieldName) + ',';
@ -4236,13 +4238,27 @@ begin
begin begin
Writeable := false; Writeable := false;
Readable := false; Readable := false;
Insertable := False;
Updatable := False;
end end
else else
begin begin
Writeable := ((FieldOptions * [foReadOnly, foAutoGenerated]) = []); Writeable := ((FieldOptions * [foReadOnly, foAutoGenerated]) = []);
Readable := (FieldOptions * [foWriteOnly]) = []; Readable := not (foWriteOnly in FieldOptions);
Insertable := not (foDoNotInsert in FieldOptions);
Updatable := not (foDoNotUpdate in FieldOptions);
end; end;
IsVersion := foVersion in FieldOptions; IsVersion := foVersion in FieldOptions;
// field options consistency check
// if Writeable and (not Updatable) then
// begin
// raise EMVCActiveRecord.CreateFmt('Field "%s" cannot be Writeable but not Updateable', [FieldName]);
// end;
// if (not Writeable) and (Readable) and (Updatable or Insertable) then
// begin
// raise EMVCActiveRecord.CreateFmt('Field "%s" cannot be ReadOnly but (Updateable or Insertable)', [FieldName]);
// end;
end; end;
{ TMVCUnitOfWork<T> } { TMVCUnitOfWork<T> }

View File

@ -85,7 +85,7 @@ begin
for lKeyValue in TableMap.fMap do for lKeyValue in TableMap.fMap do
begin begin
if lKeyValue.Value.Writeable then if lKeyValue.Value.Insertable then
begin begin
lSB.Append(GetFieldNameForSQL(lKeyValue.Value.FieldName) + ','); lSB.Append(GetFieldNameForSQL(lKeyValue.Value.FieldName) + ',');
end; end;
@ -109,7 +109,7 @@ begin
if lKeyValue.Value.IsVersion then if lKeyValue.Value.IsVersion then
begin begin
lSB.Append(OBJECT_VERSION_STARTING_VALUE + ','); lSB.Append(OBJECT_VERSION_STARTING_VALUE + ',');
end else if lKeyValue.Value.Writeable then end else if lKeyValue.Value.Insertable then
begin begin
lSB.Append(':' + GetParamNameForSQL(lKeyValue.Value.FieldName) + ','); lSB.Append(':' + GetParamNameForSQL(lKeyValue.Value.FieldName) + ',');
end; end;