Jump to content
Ian Branch

Fill Class Variables from a table row??

Recommended Posts

Hi Team,

I have the following construct..

unit CompanyData;

interface

{
  mlXX = memory logical - Boolean
  miXX = memory integer = Integer
  msXX = memory string = String
  mXuXX = User data.
  mXcXX = Company data.
}
type
  TCompanyData = class
  class var
    //
    mscRecordEditInsertMessage: string;
    //
    mlcApplicationGlobalEmails: Boolean;
    //
    mlcSendSurveyEmails: Boolean;
    micSendSurveyDays: Integer;
    mlcPartsBinNoCompulsory: Boolean;
    mlcSendAPEmails: Boolean;
    micAPUpdateDays: SmallInt;
    micACUpdateDays: SmallInt;
    micACUpdateLimit: SmallInt;
    mlcSendDailyReportEmails: Boolean;
    mlcSendWeeklyReportEmails: Boolean;
    mlcSendMonthlyReportEmails: Boolean;
    mscSurveysEmailAddress: string;
    mscStillAPMsgName: string;
    mscStillACMessageName: string;
...
...
end;

Each variable represents a field in the Company TTable.

Is there a simple mechanism in Delphi by which I can populate these variables directly from the TTable?

Or, do I have to iterate through the fields and assign the value of each field to the variable??

If I have to iterate, is it best to do that in the Class Constructor??

i.e.  Something like..

...
...
...
    //
    class constructor Create;
    //
  end;

  /// /////////////////////////////////////////////////////////////////////////////////
implementation

class constructor TCompanyData.Create;
var
	i: smallint;
begin
  //
	For i = 0 to Company.FieldCount do
	begin
		TCompanyData[i].AsVariant := Company.Fields[i].AsVariant;
		Next i;
	end;
	//
end;

 

Regards & TIA,

Ian

Share this post


Link to post
1 hour ago, Ian Branch said:

Is there a simple mechanism in Delphi by which I can populate these variables directly from the TTable?

Yes, there is. I blogged about it here: Dataset Enumerator Reloaded. The latest sources can be found on GitHub: https://github.com/UweRaabe/DataSetEnumerator.

 

You may need to remove the class var, because the sources are to be used with instance fields.

 

Is there a special reason for using class var?

  • Like 1

Share this post


Link to post

Hi Uwe,

Thank you for your input.

The CompanyData Unit is normally populated in the App's DataModule by simple assignment..

uses CompnyData;
....
....
Company.Open;
TCompanyData.mlcApplicationGlobalEmails := Company.FieldByName('ApplicationGlobalEmails').AsBoolean;
...
...
Company.Close;
...
...

I am using the unit in various other Apps/Units to pass around the Company Record field values without having to keep the Company table open.

So, in various App/Units I might have something like..

uses CompanyData;
..
..
...

 if TCompanyData.mlcApplicationGlobalEmails then 
	do something
else
	don't;

If there is a better way to do this I am open to suggestions..

FWIW I have attached the full Unit.

 

Regards,

Ian

CompanyData.zip

Edited by Ian Branch

Share this post


Link to post

Just curious - since the data has to be imported - why bother with class vars instead of using a regular instance with regular fields?

Share this post


Link to post

Hi Lars,

Please clarify what you mean?  I am not 100% into this instancing stuff.

If you are referring to keeping the Company table open and using its normal field addressing, for various reasons not for here, I don't want to.

If you are referring to something else, I am all ears... :-)

Regards,

Ian

Share this post


Link to post

An instance is a value of a class type that has been constructed with a call to Create.

 

A class var is shared across all instances of the class - and they are accessible directly from the uninstanced type as TCompany.ClassVarName.

The drawback is that you can only have one value for all the TCompany instances.

 

If you need to have multiple companies in memory, you need to do a TCompany.Create and load up the instances ordinary fields/props per company.

 

 

Share this post


Link to post

Hi Lars,

It is the User's Company and the User is only associated with one company so that Company table row/data is loaded to TCompany.

There are multiple Users, 80+, with 3 Companies in the Company table.  Each User has a Unique UserID related to one of the companies.

When they log in their related Company details are loaded to TCompany for use throughout the various units in the App(s).

In essence, this is my way of having Global Company variables.

Ian

Edited by Ian Branch
  • Thanks 1

Share this post


Link to post

I have slightly changed the CompanyData unit to get rid of the many class vars in favor of a singleton implementation. Instead of TCompanyData.mlcApplicationGlobalEmails you should write MyCompanyData.mlcApplicationGlobalEmails. 

 

With this change you can make use of the DataSetHelper unit mentioned above. You only need to use that unit inside your data module and load the customer data like this:

Company.Open;
Company.LoadInstanceFromCurrent(MyCompanyData);
Company.Close;

 

CompanyData1.zip

Edited by Uwe Raabe
  • Like 1

Share this post


Link to post

Tks Uwe,

I haven't fully digested your first Unit yet, just a quick look, when I do I will then go through this one.

A quick look suggests I have a couple of other applications I can use it in as well.

There is another thread in the Forum that I guess this is indirectly related to... 

Thanks Again,

Regards,

Ian 

Edited by Ian Branch

Share this post


Link to post

Hi Uwe,

I am coming to grips with this methodology.  I need to change a few things.  Not the least of which is the order of the variables in the Class to match the order of records in the Company table.

 

So, it occurred to me, is it possible to add the variables dynamically? 

i.e.  The Class starts out without any variables defined and then programmatically the variables and values are added based on the field name & value?

Or possibly all the variables are added as variants and then the assignment of the value from the fields sets the variant type??

This would add enormously to the whole package flexibility/usability.

 

Don't sweat on it, just thinking out loud here... 😉

 

Regards,

Ian

 

Share this post


Link to post
6 hours ago, Ian Branch said:

add the variables dynamically

We do that for configuration data - it is basically like a key/value dictionary.

Share this post


Link to post
8 hours ago, Ian Branch said:

So, it occurred to me, is it possible to add the variables dynamically? 

i.e.  The Class starts out without any variables defined and then programmatically the variables and values are added based on the field name & value?

 

I don't really understand. What do you mean with adding variables dynamically?

Fields inside classes have to be known at compile time. Otherwise a construct like

if MyCompanyData.mlcApplicationGlobalEmails then
  ...

would not compile.

 

8 hours ago, Ian Branch said:

Not the least of which is the order of the variables in the Class to match the order of records in the Company table.

The order of the field in the class doesn't matter at all.

 

Basically LoadInstanceFromCurrent iterates all the fields and properties in the class and looks if there is a corresponding field in the dataset its value is assigned to the field of the class.

Share this post


Link to post
2 hours ago, Uwe Raabe said:

I don't really understand. What do you mean with adding variables dynamically?

Fields inside classes have to be known at compile time

Actually there's some kind of syntax sugar for Variants - they could accept any "member" that is translated to g(s)etter with string name of that member. Not really helpful though but could shorten some code a bit

Share this post


Link to post
8 hours ago, Uwe Raabe said:

I don't really understand. What do you mean with adding variables dynamically?

Fields inside classes have to be known at compile time.

Hi Uwe,

Yes, my fingers got ahead of my brain.  Some little time later I realised it wouldn't work for that very reason.

That'll teach me to think out loud...  ;-(

8 hours ago, Uwe Raabe said:

The order of the field in the class doesn't matter at all.

 

Basically LoadInstanceFromCurrent iterates all the fields and properties in the class and looks if there is a corresponding field in the dataset its value is assigned to the field of the class.

Ah Ha!  That makes it a whole heap easier. 

Presumably if there is a property in the class that isn't in the data set it ignores it?

I am going to rethink my approach now, again. :-)

 

Regards,

Ian

Share this post


Link to post
4 hours ago, Ian Branch said:

Presumably if there is a property in the class that isn't in the data set it ignores it?

Exactly! And vice versa.

Share this post


Link to post

Hi Uwe,

Finally got to implement this, sorta.

Customer had some issue I needed to deal with before I could get to this again.  :-(

So, I am using your Singleton implementation.

As the Apps have grown over the years there were some legacy issues I had to deal with.  One of which was matching the fields to the file field class definitions.  What a pain but much more maintainable/readable now.

I changed the MyCompanyData to AppCompanyData.

As I went to implement the "  Company.LoadInstanceFromCurrent(AppCompanyData);", it works fine but I realised that I had other AppCompanyData variables being set conditionally.

e.g.

  if Company.FieldByName('UDR1').IsNull then
    AppCompanyData.UserDefRep1 := 'No User Defined Report added.'
  else
    AppCompanyData.UserDefRep1 := Trim(Company.FieldByName('UDR1').AsString);
  if Company.FieldByName('UDR2').IsNull then
    AppCompanyData.UserDefRep2 := 'No User Defined Report added.'
  else
    AppCompanyData.UserDefRep2 := Trim(Company.FieldByName('UDR2').AsString);
  if Company.FieldByName('UDR3').IsNull then
    AppCompanyData.UserDefRep3 := 'No User Defined Report added.'
  else
    AppCompanyData.UserDefRep3 := Trim(Company.FieldByName('UDR3').AsString);
  if Company.FieldByName('UDR4').IsNull then
    AppCompanyData.UserDefRep4 := 'No User Defined Report added.'
  else
    AppCompanyData.UserDefRep4 := Trim(Company.FieldByName('UDR4').AsString);
  if Company.FieldByName('UDR5').IsNull then
    AppCompanyData.UserDefRep5 := 'No User Defined Report added.'
  else
    AppCompanyData.UserDefRep5 := Trim(Company.FieldByName('UDR5').AsString);
  if Company.FieldByName('UDR6').IsNull then
    AppCompanyData.UserDefRep6 := 'No User Defined Report added.'
  else
    AppCompanyData.UserDefRep6 := Trim(Company.FieldByName('UDR6').AsString);

So for now I am sticking with my original "SetCompanyGlobals;" function.

I guess I can still use the "  Company.LoadInstanceFromCurrent(AppCompanyData);" and then just have the "SetCompanyGlobals;" function set these odd ones.

 

I changed MyCompanyData to AppCompanyData because I am going to apply the same singleton principle/technique to UserData => AppUserData and UserPermissions => AppUserPermissions.   "Myxxxxxxxxxx" just didn't seem right. ;-)

 

Thanks once again for your support,

Ian

Share this post


Link to post

Hi @Uwe Raabe

Just came across an issue.  The DataSetHelper LoadFromField works fine with my Data in D10.4.2.

Unfortunately I am getting an " Invalid class typecast." error for the same code/data in D11 at line  454.

image.thumb.png.9edb04722634afbc942ccd2c46f954ef.png

It is happening on a couple of Apps.

This may be an ANSI v UniCode issue that I haven't correctly set in D11 as yet but I thought I would give you a heads-up.

I have not embraced UniCode as my main Customers are still on Win 7 32Bit. :-(

 

Regards,

Ian

Edited by Ian Branch

Share this post


Link to post

Could it be that the DB field value is Null when it crashes? 

 

Can you please try to adjust the LoadFromField code to this:

procedure TDataSetHelper.TDataSetRecord<T>.TPropMapping.LoadFromField(var Target: T);
var
  val: TValue;
begin
  if FField.IsNull then
    val := TValue.Empty
  else
    val := TValue.FromVariant(FField.Value);
  FRTTIProp.SetValue(GetPointer(Target), val);
end;

 

Share this post


Link to post

Hi Uwe,

Yes, there could be a field with a Null.

I made the change.

I still get an " Invalid class typecast." error and this..

image.thumb.png.a406e598de2f937a4a98572165b876a8.png

To validate your theory I put some text into the Null field.  Now it works fine, with and without the code change.

Does the handling of Null need to be elsewhere??

There are other NULL fields in the record but they don't seem to affect it.

With a Null in the field it works fine in D10.4.2 but not D11.

 

Update - I changes this code..

procedure TDataSetHelper.TDataSetRecord<T>.TFieldMapping.LoadFromField(var Target: T);
begin
  FRTTIField.SetValue(GetPointer(Target), TValue.FromVariant(FField.Value));
end;

To this..

procedure TDataSetHelper.TDataSetRecord<T>.TFieldMapping.LoadFromField(var Target: T);
var
  val: TValue;
begin
  if FField.IsNull then
    val := TValue.Empty
  else
    val := TValue.FromVariant(FField.Value);
  FRTTIField.SetValue(GetPointer(Target), val);
end;

And it seems to be working.

I will do some more testing tomorrow and advise.

Regards,

Ian

Edited by Ian Branch

Share this post


Link to post

Hi Uwe,

Something changed under-the-hood from D10.4 to D11.

All working fine now in both D10.4 & D11.

Thanks for the quick fix.

Regards,

Ian

Edited by Ian Branch

Share this post


Link to post
55 minutes ago, Ian Branch said:

Something changed under-the-hood from D10.4 to D11.

 

 

  RSP-35486 TRttiField.SetValue breaks past bevaviour with Null and String

  • Like 1
  • Thanks 3

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×