Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
For Alexandre: Regarding Datamodules and DB components
#6
Quote:You once recommended I put the DBConnection in the UserSession, and then created a datamodule for every form I make, where DB related components should be placed. For me, as you probably all know by now, the DB related components I use is an ADO DBConnection in UserSession, and ADO the components are ADOQuery and ADOStoredproc, in the datamodules.

Yes, I remember that. That's correct.

Quote:With my newfound knowledge of how and where to put variables, coming from other users comment on this forum (thanks to all) and going through a lot on threads in this forum, a lot of documentation on your homepage, a lot of googled information, and some of the older IW forum entries on then Embarcadero site. During all the reading I believe I am certain that ANY form I create at runtime, and any procedure and function I use, and any variable, local for a form or global in UserSession, is safe form interference from other sessions, provided everything is defined within the limits of the Form Class. That is, from Private / Public down to the END definition for the class. Local vars and procedure / function definitions are no go !!

Can you confirm that that is a correct assumption ?

This demo application summarizes all principles involved: https://github.com/Atozed/IntraWeb/tree/...WADODBDemo

Regarding session isolation/thread safety: All forms and data modules (and other objects) owned by a specific session are safe to be used from that session. So, if you have different forms and data modules created in the context of one specific session, they are safe to reference objects from each other, as long as you can guarantee that they exist and are instantiated. Of course you can't try to access an object that has been already destroyed.

You used a *dangerous* word above which is *global* (...in UserSession). I don't know exactly what you mean by "global in UserSession". Global variables declared in the UserSession unit *ARE NOT SAFE* either. Actually, no global variable declared *anywhere* is safe, so don't use them.
However, real UserSesssion properties (and IWForm and data module) are definitely safe. If you need a "kind of global" variable in your UserSession (e.g. the session user name and his numeric code) the simplest way is to declared them as UserSession properties.

Myself I usually go a step further and create a TUser class which contains all necessary user data (like numeric code and name), and this object is "owned" by the UserSession and accessible through it. Example:

Code:
type
  TUser = class
    FName: string;
    FCode: Integer;
  public
    property Name: string read FName write FName;
    property Code: Integer read FCode write FCode;
  end;

  TIWUserSession = class(TIWUserSessionBase)
    procedure IWUserSessionBaseCreate(Sender: TObject);
    procedure IWUserSessionBaseDestroy(Sender: TObject);
  private
    { Private declarations }
    FUser: TUser;
  public
    { Public declarations }
    property User: TUser read FUser;
  end;

implementation

{$R *.dfm}

procedure TIWUserSession.IWUserSessionBaseCreate(Sender: TObject);
begin
  FUser := TUser.Create;
end;

procedure TIWUserSession.IWUserSessionBaseDestroy(Sender: TObject);
begin
  FreeAndNil(FUser);
end;

This is a simplification but it shows how it works. The real code gets more complicated of course... In the real code, the TUser class would be declared in a separate unit, it would contain possibly a DB connection object to talk directly to the DB (which can be passed as a parameter in the constructor - remember the UserSession "knows" the DB connection and can pass it along to any other component)... and so on.

Then, if you need to know the user name from, let's say a Form, you could do:

Code:
var
  UserName: string;
begin
  UserName := UserSession.User.Name;
  // use it here
end;

This will be *always safe* from any session (because each session has its own copy of a TUser instance, which was created when the session was created and will be destroyed with the session too). Please notice that the "User" property is read only. I cannot set the User instance (I can change some of its properties, like the name, but I can't replace that specific TUser instance with another instance of the same class). 

Quote:Also, that the datamodule I need for a form, is included in the forms uses list, defined as dm:= {moduleclassname}; in the private section of the form class, and created in the forms create event with dm := {datamoduleclassname}.create(self); I take it that that is correct as well ?, though I do understand there are room for differences like that the datamodule do NOT HAVETO be created in the forms oncreate event, as long as it is created before it's needed, and that it can have any name, not necessarily "dm".

You are correct. I used a convention. I think each developer out there has its own conventions. I stick to mine which have been tested in real life mission critical applications running 24x7 and *most importantly* have never become a nightmare when it comes to maintenance, because internally they are well organized. Data modules can of course be shared among several forms. But IMO you should avoid the "easy path" which is creating a big dm which contains everything. In the long run you will be shooting yourself on both feet. 

IMO, the most common mistake I always see in Delphi code is that developers forget about OOP rules when they are creating/using data modules. The same rules that apply to OOP also apply to data modules! Each TDataModule in your application should be treated as a class which should encapsulate its data. Delphi makes everything you put in a DataModule public, so it is very easy to, for instance, set or change the ADOQuery.SQL property from a form.

*THIS IS WRONG*. Here, I won't even use "in my opinion". It is simply wrong. The ADOQuery is an internal object of the DataModule and no other object should change it. *My convention* forces me to write proper public methods in that data module which I can use to customize the SQL, pass parameters to it, or refresh it, etc... 

Another OOP rule that should be followed when using Data Modules is that each object should take care of one specific aspect (and not all aspects) of your application. That's why one single data module with all your queries also violates this principle and rapidly becomes a complete unmaintainable mess.

Of course, if you decide that I am wrong and you want to do it differently, you are free to do it and it will certainly work (because there is no technical limit). 

Quote:Finally, I have come to learn, and I am not sure you did not already tell me that long time ago, that it is vital that settings for the ADO components connection property is done in the datamodule at runtime, like in the DataModule OnCreate event, and not at design time. Other users have mentioned that as well (again thanks to you).

Can you confirm that ?

Kind of. It is not vital but it is recommendable. Even if you connect them at design time it will work. Delphi has a clever mechanism which runs when a form or a data module is created and contains objects that reference objects in other forms or data modules. However, you will soon find out that it is very easy to have it "unset" at design time when you are working in the IDE. And the problem won't be found until you run the application (maybe only in production).
When you, for instance, open a form that references other data modules and they are not opened too, the IDE may clear the reference when saving the form again. So, your DataSource1.DataSet property which contained "dm1.DataSetABC" now will become blank. And it will fail at runtime. It happened to me more times that I can count.
You can connect them at design time so you can work with DBAware controls, see the field names, and etc, but I strongly recommend you to also put some code to connect them at run time. The total time you will spend writing that code will be saved several times due to errors that won't happen in testing/production.

Quote:I know ADO are not thread safe, and if I created threads within my form, I should handle threads and make them safe, but also that as long as I do not create threads, it is not necessary. The Session, and among that the ADO components, is shielded from access from other sessions.

Is there anything else I should be aware of, that you (or any of you reading this) can think of ?

You are correct above. Everything that belongs to a form, data module or user session instances are shielded from other instances running in another session context.

I think I covered most of the things. Feel free to ask anything else, though.

I'll just add a note regarding to what MrSpock said regarding putting everything in the same form where they are used: I wouldn't do that because it a secure way to start writing business logic in the UI, which I believe is a fast way to have a un-maintainable application.

Best regards
Reply


Messages In This Thread
RE: For Alexandre: Regarding Datamodules and DB components - by Alexandre Machado - 02-11-2020, 09:34 AM

Forum Jump:


Users browsing this thread: 1 Guest(s)