Ian Branch 128 Posted January 31, 2022 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
Uwe Raabe 2064 Posted January 31, 2022 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? 1 Share this post Link to post
Ian Branch 128 Posted February 1, 2022 (edited) 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 February 1, 2022 by Ian Branch Share this post Link to post
Lars Fosdal 1793 Posted February 1, 2022 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
Ian Branch 128 Posted February 1, 2022 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
Lars Fosdal 1793 Posted February 1, 2022 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
Ian Branch 128 Posted February 1, 2022 (edited) 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 February 1, 2022 by Ian Branch 1 Share this post Link to post
Uwe Raabe 2064 Posted February 1, 2022 (edited) 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 February 1, 2022 by Uwe Raabe 1 Share this post Link to post
Ian Branch 128 Posted February 1, 2022 (edited) 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 February 1, 2022 by Ian Branch Share this post Link to post
Ian Branch 128 Posted February 2, 2022 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
Lars Fosdal 1793 Posted February 2, 2022 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
Uwe Raabe 2064 Posted February 2, 2022 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
Fr0sT.Brutal 900 Posted February 2, 2022 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
Rolphy Reyes 0 Posted February 2, 2022 Hi! Maybe you can use attributes. Share this post Link to post
Ian Branch 128 Posted February 2, 2022 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
Uwe Raabe 2064 Posted February 2, 2022 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
Ian Branch 128 Posted February 7, 2022 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
Ian Branch 128 Posted February 13, 2022 (edited) 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. 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 February 13, 2022 by Ian Branch Share this post Link to post
Uwe Raabe 2064 Posted February 13, 2022 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
Ian Branch 128 Posted February 13, 2022 (edited) 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.. 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 February 13, 2022 by Ian Branch Share this post Link to post
Ian Branch 128 Posted February 13, 2022 (edited) 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 February 13, 2022 by Ian Branch Share this post Link to post
FredS 138 Posted February 13, 2022 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 1 3 Share this post Link to post