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

View File

@ -231,6 +231,32 @@ type
property City: string read fCity write fCity;
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)]
[MVCTable('order_details')]
TOrderDetail = class(TCustomEntity)

View File

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

View File

@ -63,6 +63,7 @@ type
btnIntegersAsBool: TButton;
btnObjectVersion: TButton;
btnCustomTable: TButton;
btnCRUDWithOptions: TButton;
procedure btnCRUDClick(Sender: TObject);
procedure btnInheritanceClick(Sender: TObject);
procedure btnMultiThreadingClick(Sender: TObject);
@ -96,6 +97,7 @@ type
procedure btnIntegersAsBoolClick(Sender: TObject);
procedure btnObjectVersionClick(Sender: TObject);
procedure btnCustomTableClick(Sender: TObject);
procedure btnCRUDWithOptionsClick(Sender: TObject);
private
procedure Log(const Value: string);
procedure LoadCustomers(const HowManyCustomers: Integer = 50);
@ -412,6 +414,78 @@ begin
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);
var
lCustomer: TCustomerWithCode;

View File

@ -66,9 +66,11 @@ type
TMVCActiveRecord = class;
TMVCActiveRecordFieldOption = (foPrimaryKey, { it's the primary key of the mapped table }
foAutoGenerated, { not written, read - similar to readonly }
foReadOnly, { not written, read }
foReadOnly, { not written, read - like foDoNotInsert+foDoNotUpdate }
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;
TMVCEntityAction = (eaCreate, eaRetrieve, eaUpdate, eaDelete);
@ -104,7 +106,7 @@ type
FieldName: string;
FieldOptions: TMVCActiveRecordFieldOptions;
DataTypeName: string;
Writeable, Readable, IsVersion: Boolean;
Writeable, Readable, Insertable, Updatable, IsVersion: Boolean;
procedure EndUpdates;
end;
@ -3899,7 +3901,7 @@ begin
begin
Result := Result + GetFieldNameForSQL(lPair.Value.FieldName) + ' = ' +
GetParamNameForSQL(lPair.Value.FieldName) + ' + 1,';
end else if lPair.Value.Writeable then
end else if lPair.Value.Updatable then
begin
Result := Result + GetFieldNameForSQL(lPair.Value.FieldName) + ' = :' +
GetParamNameForSQL(lPair.Value.FieldName) + ',';
@ -4236,13 +4238,27 @@ begin
begin
Writeable := false;
Readable := false;
Insertable := False;
Updatable := False;
end
else
begin
Writeable := ((FieldOptions * [foReadOnly, foAutoGenerated]) = []);
Readable := (FieldOptions * [foWriteOnly]) = [];
Readable := not (foWriteOnly in FieldOptions);
Insertable := not (foDoNotInsert in FieldOptions);
Updatable := not (foDoNotUpdate in FieldOptions);
end;
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;
{ TMVCUnitOfWork<T> }

View File

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