How to maintain COM+ status information in Delphi
Liu Xiaoming (cipherliu)
The problem starts like this: I need to write a COM+ to connect to different databases. Some friends may say that a COM+ should be established for each database, but this cannot be done in my system. We are making an educational assistance system, and the users are schools (including teachers, students, and parents in the school, of course). We build a database for each school. The structures of these databases are the same. Of course, we also have a management database to coordinate the relationships between databases. Every time a school user is added, we activate a new database for the customer to use. In other words, the number of our databases is constantly increasing, and we only have one client. We will not develop different ones for each school. On the client side, we only have one set of COM+, instead of developing one set for each database. So I have to let it connect to different databases based on the user's identity in COM+.
Obviously, this COM+ should provide a method to allow its caller (which can be a client application or other middleware) to select the database to connect to. In practice, we query the database in the management library based on the user's ID. to its database name, and then connect to the user database. Here, in order to simplify the problem, we think that the caller already knows the name of the database, and directly requests to call this database.
Add a private member DBName:string to the COM+ class to save the name of the database to be connected. We should also provide such a method to set its value. I initially wrote it like this
PRocedure TmtsDBConn.ConnectTo(sDBName:string);
begin
try
DBName:=sDBName;
SetComplete;
Except
SetAbort;
end;
end;
Then put ADOConnection, ADODataSet, and DataSetProvider controls in them, named adoc, adods, and dsp respectively. Set the connection relationship between them, set the connection string of adoc to the connection database "DB1", which is the default value, and then in the BeforeConnect event of adoc:
adoc.ConnectionString:=ConnectStringA+'Initial Catalog='+DBName+';'+ConnectStringC;
ConnectStringA and ConnectStringC here are pre-set string constants in order to dynamically build the connection string, as follows:
const
ConnectStringA='Provider=SQLOLEDB.1;PassWord=2003;Persist Security Info=True;User ID=sa;';
ConnectStringB='Initial Catalog=DB1;';
ConnectStringC='Data Source=server3;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID=LXM;Use Encryption for Data=False;Tag with column collation when possible=False';
Compile and install this COM+. Then write a client program to call it.
Put a DCOMConnection in the client program, connect to it and write the COM+ server, put a ClientDataSet, set its RemoteServer and Provider properties, and then write SQL statements in its CommandText. Then, put in the DataSource control and DBGrid control to establish the connection between them. Finally put a button and in its Click event:
Dcomconnection1.Connected:=true;
Dcomconnection1.AppServer.connect('DB2');
ClientDataset1.Active:=true;
Dcomconnection1.Connected:=false;
This code is to test whether the data in the DB2 database can be accessed. But the result is that when clicking the button, an error is always reported. What is the reason?
Go back to the COM+ project, debug it, set breakpoints in ConnectTo and adocBeforeConnect, and find that the program executes to
DBName:=sDBName;
When executing, the value of DBName has indeed been set to "DB2", but when executing
adoc.ConnectionString:=ConnectStringA+'Initial Catalog='+DBName+';'+ConnectStringC;
, DBName became an empty string again, so an error occurred.
Why is the value of DBName lost? It turns out that the SetComplete method is called in ConnectTo. The SetComplete method thinks that the COM+ has completed the task and will release the COM+ object. Therefore, when connecting to the database, a new COM+ is created, and its DBName is of course null. .
I found the reason, changed SetComplete to EnableCommit; compiled, and then ran the client. Finally, it ran successfully and retrieved the data in the DB2 database.
However, in the client program, I put another ClientDataSet. After opening ClientDataSet1, I opened ClientDataSet2 and wanted to continue accessing the data in DB2, but another error was reported. Change the program to
Dcomconnection1.AppServer.connect('DB2');
ClientDataset1.Active:=true;
ClientDataset1.Active:=false;
ClientDataset1.Active:=true;
Even if only one ClientDataSet is used, an error will still occur when it is opened again after it is closed.
But if the client writes
Dcomconnection1.AppServer.connect('DB2');
ClientDataset1.Active:=true;
Dcomconnection1.AppServer.connect('DB2');
ClientDataset2.Active:=true;
Can be executed successfully. But this seems very ugly. Why does COM+ release itself after connecting to the database?
It turns out that TmtsDataModule has an AutoComplete attribute, and the default value is true, so after connecting to the database, it will still release itself.
After setting AutoComplete to false, an error still occurred. Tracking in the OnActivate event of COM+ found that when it was activated, the AutoComplete attribute was automatically set to true, so after it connected to the database for the first time, it still released itself.
In the OnOnActivate event of COM+, write:
AutoComplete:=false;
There is no problem if the client connects once and accesses the database multiple times.
But in this case, COM+ will not be automatically released. You need to add a method to COM+, SetComplete in this method, and then call this method to release COM+ after the client is finished with COM+.
After the above exploration, we came to the following conclusion: In COM+, if you want to maintain state information, you need to do some work, because COM+ is stateless by default. Every time it is called by the client, it will judge whether it should release itself. , if we don’t want it to be released, we have to intervene manually, and finally we have to release it manually.