Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
For Alexandre: Regarding Datamodules and DB components
#1
Hi Alexandre,

This is for you, but everybody else please feel free to comment as well:

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.

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 ?

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".

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).

And that is all there is too it, I believe. Can you confirm that ?

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 ?

I thank you a lot for the information you cave me a long time ago (9 months) and I just wish I could have remembered it all. It would have saved me from a lot of anger and extra work. But such is life. What does not kill you makes you stronger, and what you learn by trial and error, is easier remembered than written words.

Regards
Soren
Reply
#2
It may look a little ugly but until there is a proper intraweb manual, I would place all database components, database connection too, in the same form along with visual components, squeezed in the corner so that they not take up room. I prefer safety to appearance and SQL queries are independent of each other so no need to use UserSession. As we have seen, everything put in the form class is safe, so this is a sensible conclusion. Non-visual components code placed in the visual form are automatically put by intraweb at back-end. Anyway, most books about database have advised that as it is simple. If the form just reads user query, it is all right to put the query string variable in the UserSession, so another form with database components will read the query and execute it. In my country, Poland, the most commonly used database is firebird, which is also free for commercial use.

Some invisible components, especially including intraweb, have to be, after all, put on the visual form, like timer, video etc., and so I would not worry but put database components on the visual form in one place overlapping so they take up very little space. Also I do not see why database connection component should be put in the UserSession. Database connection component can have many copies for each visual form. To access database components properties you have a window in top left Delphi corner where form components are listed.
Reply
#3
Hi mrSpock,

I agree it would look a little ugly, and I'm afraid I'm too tidy a person to accept that. With +20 forms, and still a few to be added, with about half of them having a datamodule, and on each datamodule from 3 to 7 ADOQuery or ADOStoredproc components (one for each table being accessed), it would be too many components in one place.

I have now moved all components back to their original datamodule, and have only one DB connection in the project, placed in UserSession. Each datamodule, mostly one for each of the forms where I have table access, are being created in the the form it belongs to, in that forms FormCreate event. And all the ADOQuery and ADOStoredProc components in the datamodule in question, have it's connection property set to UserSession.DBConnection, it the datamodules onCreate event. Just as I had it from the beginning.

So now the only real difference to my original project is that ALL local vars are moved into the Form class wrapper, in the Public section. And those few procedures I had as local, without a formname in the header, have been given a formname header and has been moved into the public section as well.

All nice and tidy and everything in it's right place. And looking back, I realize I could have saved a lot of hours of trial and error, and frustration and anger and..and... had I just remembered the vise words I once got. Do not have any defs outside the form class definition. That area is no go, it's dangerous, it's burning your data. Worst part is that I have been reminded about it so many times those past days by you guys, and still I did not see the light..... I'm happy the dark hours are behind. Bright light lies ahead.....

To Alexandre: I have now tested it over and over again and have no troubles what so ever, so my request for confirmation about whether it is the way of doing it, no longer stands. Whether it is right or wrong is not so important anymore. What is important is that it works, and so far my tests have failed to break it. You are of course very welcome to comment and hand out wise words. I will look forward to every bit of it, and try to remember it for the future......

Regards
Soren
Reply
#4
Soren,

after making about fifty applications I have an experience that I will always get a bug on finishing coding but I also know, they are usually small bugs and can find them within an hour. Instead of panicking, you should have created a copy of all your source files for testing and that way you would not have had to waste time and effort to move components back to their previous positions.

Regards
Mariusz
Reply
#5
(02-11-2020, 03:51 AM)MrSpock Wrote: Soren,

after making about fifty applications I have an experience that I will always get a bug on finishing coding but I also know, they are usually small bugs and can find them within an hour. Instead of panicking, you should have created a copy of all your source files for testing and that way you would not have had to waste time and effort to move components back to their previous positions.

Regards
Mariusz
One word:  Subversion.

I've been through several version control systems but have relied on them since the DOS days using PVCS.  Heck, I still have a server named Starteam (though we don't use ST any more).  Reverting to previous code is trivial.  I've also tied to a good compare utility to see changes between any two versions.  I've been on UltraCompare for that for some years now.

I don't mean to start a debate on the best VCS, just saying find a good one and use it religiously.  It will save you way more time than it takes to use.

Dan
Reply
#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
#7
(02-10-2020, 05:24 PM)MrSpock Wrote: It may look a little ugly but until there is a proper intraweb manual, I would place all database components, database connection too, in the same form along with visual components, squeezed in the corner

I disagree here. This is not material for a manual. This is application architecture. I've read probably all old Delphi manuals and never found a word about what I just mentioned here to Soren. There is no recommendation, good practices whatsoever. Probably that's why there is so much terrible code written in Delphi out there.

This is covered by some (very few) authors. Unfortunately authors in various areas of human knowledge are people who never actually did in practice what they recommend to others. IT is no different. Several authors of Delphi from its release date in 1994/95 to mid 2000 have never actually developed a real application. Many of them worked for Borland and knew Delphi extremely well, but never created a single application using it and *more importantly* had to maintain it for years in production.

Quote:Also I do not see why database connection component should be put in the UserSession. Database connection component can have many copies for each visual form. To access database components properties you have a window in top left Delphi corner where form components are listed.

Oh... here I disagree again. There are many reasons, not only one.
Database connection is a costly thing. Both in terms of resources and performance. A new DB connection with an Oracle server takes not milliseconds but *seconds*. You can use a pool but then your "simplicity" factor above disappears.

Also, if your application allows 2 or 3 form instance to exist in memory (there are several reasons to do so) and each of them contains 1 DB connection, a single user is keeping 3 connections open with your DB server.

Now, multiply this for 1000. You have 3,000 active DB connections when you actually only need 1,000.

Have you tried to keep 3,000 active connections to a Firebird server? Your server will simply stop responding and there is nothing you can do with your IW application that will change that.
If you own the DB server you can of course use it to the limit. But if that DB server is a shared DB server. How do you explain to your customer s DBA that each active web user has 3, 4, 5... active DB connections (and possibly dozens of others in time-wait which also becomes a problem in busy enviroments)...

The list can go on and on
Reply
#8
Hi Alexandre,

Thanks a million for your extensive explanation.

And just to clarify: For the term Global, in this context, I meant all the vars defined in the UserSession class, accessible from all forms in the session with the UserSession. prefix, just as you have described it.

Regards
Soren
Reply
#9
My pleasure!

Global as "globally available" properties of an object, yes, that's fine.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)