Free, Open Source Object Persistence Framework for
Free Pascal & Delphi
Answer: In the Delphi IDE, turn off compile with runtime packages, and in your dpr ensure your code is similar to that below:
tiQueryXXX, // or whatever layer (ie tiQueryIBX)
ctiPersist, // Contains the cTIPersistxxx constants
gTIPerMgr.LoadDatabaseLayer( cTIPersistIBX, // Matches one above
The result will be a single exe with no package dependencies.
Question: I have just upgraded to the latest version of the framework, but now when I compile the tiSQLManager, tiSQLEditor or DemoTIPerFramework applications, I get an error like this:
Answer: This error is typically caused when you have an old version of a bpl file higher up on the search path than the most recent version. From this error, it looks like the procedure HOS has been added to TtiPerMgr. The application will compile because it can see the correct version of the PAS and DCP files, but it is loading the wrong BPL file at runtime. Do a search on your system for all tiPersistCore.bpl files and you will probably find more than 1. (I usually have the output directory for my BPL files set to C:\TechInsite\Bin. The BPLs will be put into the Delphi\Bin directory by default so if you did not set the output directory correctly when you first loaded the framework, there could be multiple BPLs on your system.)
Question: When I run the SQLManager and SQLEditor from inside Delphi, they are fine and very well behaved. However, when I run them outside of the Delphi IDE, things start to go wrong.
Answer: Take a look at the Run|Parameters options for both applications in the Delphi IDE. Some parameters must be passed to identify the persistence layer, database, user name and password.
Question: I can't build/compile either the SQL manager or editor. Delphi complains about a missing tiSQLMgr_Cli which is used in the FChildQuery, FFindQuery, FMainSQLManager and FSQLEditor units.
Answer: Make sure you have \TechInsite\tiPersist in Delphi's search path. Read Geting Started for more details on the search paths that are necessary.
Question: Why does the bitmap of the SQL manager on the web show SQL with colour syntax highlighting, but the SQLManager has just black text when I compile an run it?
Answer: The colour syntax highlighting is achieved with the help of an open source component called SynEdit which can be downloaded from http://synedit.sourceforge.net/ Add the compiler directive SYNEDIT to the SQLManager and tiSQLEditor to include these components.
Question: With Interbase 6.0 installed, any attempts on opening the sample database distributed with the TechInsite framework will fail with the following error message: "Error connecting to the requested database: Unsupported on-disk structure for file C:\TECHINSITE\DEMOS\DEMOTIPERFRAMEWORK\ADRS.GDB;
found 9, support 10";
Answer: Please check whether you are using Borland's Interbase 6.01. This particular version dropped support for databases created by previous versions of Interbase.
Solution 1: If you are proficient with SQL and Interbase, you can manually recreate the database in IB6.01. You can find the DDL script in the "C:\TECHINSITE\DEMOS\DEMOxxxxx\DDL\" folder.
Solution 2: Replace IB6.01 with another version of IB6 that doesn't have the ODS restriction such as Borland Interbase 6.0 (download link?) or the FireBird Interbase distribution (download link?).
Question: The sql field in table <sqlman_sql> is now a text blob. I can no longer use the SQLMgrDDL_Interbase.sql script, or my own for that matter :-(
Answer: While the text blobs can't be specified directly in sql, you can use a UDF (user defined
function) to enter them. There are a number for free UDF libraries available.
This link... http://www.ibphoenix.com/ibp_contrib_download.html#DEVCOMPS listed a number of them. The "FreeUDFLib for windows" provides StrBlob (String to Text Blob) function. There is a declaration script and compiled DLL.
The declare script is...
declare external function f_StrBlob
returns parameter 2
entry_point 'StrBlob' module_name 'FreeUDFLib.dll';
You run this in ISQL/WSQL or the like against your database.
Now we can code...
insert into sqlman_sql
( oid, group_oid, query_name, sql ) values
( 1, 1, 'GET_NEXT_OID', f_StrBlob('select oid from next_oid') ) ;
Other functions appear to be worth checking out too (eg. soundex function)
Question: I have a question with regards to writing a ADO version of tiQuery: where should
I place the connection string required for a ADO connection? e.g.
FConnection.ConnectionString := 'Provider=Microsoft.Jet.OLEDB.4.0;' +
'Mode=ReadWrite;Persist Security Info=True;' +
'Data Source=C:\data.mdb' +
';Jet OLEDB:System '+
It is not clear how the TtiDatabase procedures--Create, Connect, GetConnected,
and SetConnected--should be use for a ADO connection.
Answer: Where to put the connection string? Good question.
You have three place holders: DatabaseName, UserID and Password.
One option would be to break it into three parts:
DatabaseName := 'c:\data.mdb' ;
UserName := 'Admin' ;
UserPassword := 'manager' ;
Then join the parts up again when the thing attempts to connect:
'Provider=Microsoft.Jet.OLEDB.4.0;Mode=ReadWrite;Persist Security Info=True;DataSource=' + DatabaseName + ';Jet OLEDB:System database=C:\system.mdw;User ID=' + UserName + ';Password=' + UserPassword ;
... I like this option.
...Another, which Ian has been pressing for is to add a Params property to to the abstract database connection. That way we could add any extra info there we want.
The thind option would be to use a
gCommandLineParams( 'MyParamName' ) call inside the database connection, when the connection string is being built up to get a param name from the command line. This would get you out of the woods if you needed to make somthing like the Mode=ReadWrite property accessable, and did not want to do a lot of work.
I know the database connection looks like it's very complex (probably because it is), but there are reasons for this. Some of these reasons are that the framework uses database connection pooling, so it will create as many connections as is required at the time ( say you have some threads running, each requining a connection). When these connections have not been used for <n> minutes (this setting is in tiPoolAbs as a constant) the connections will be closed off.
Question: I tried to add 100 TPerson (with no Address or TEAddress) in DemoTIFramework demo. The task was very slow (about 2 minutes). How do I optimise the tiOPF?
Answer: I expect the problem you are having is due to the way the visitors are registered
in the demo application. (The approach has been optimised for simplicity, rather
than performance), but in case I’m wrong, I will walk you through the optimisation
FYI, I load a 5000 node tree in about 10 seconds. I do this inside a thread
which kicks off before the user logon screen shows. This way, all the background
data is loaded by the time the user has finished typing her password.
Firstly, open tiLog.pas and find the constant cSevToLog which will be declared
around about line 400. This constant sets up a filter to limit the type of messages
which are logged. Uncomment the entry for lsVisitor and run the application.
You will see that the visitor manager is doing much more work that you will
have expected. (One day, we must make it easier to change the logging levels,
perhaps from a GUI or INI file.)
The problem is, that for every TPerson you have added to the list, queries to
read both the addresses and Eaddresses have been run.
There are several way around this problem:
In the FtiAdrsListMain.pas unit, you will see the following code in the read
gTIPerMgr.ReadPK( gAdrsBook ) ;
gTIPerMgr.Read( gAdrsBook ) ;
The first line will read the people into the list, and the second line will
cause each person’s addresses to be read. You can comment this line out, and
add it to the TTreeView’s OnSelectNode event. The code is already there and
commented out –
if pData is TPerson then
gTIPerMgr.Read( TPerson( pData ))
if pData is TCompany then
gTIPerMgr.Read( TCompany( pData )) ;
This will give you delayed loading, which will speed things up a lot.
Now, as I said, the way the visitors are registered is designed to make it simple
for a new programmer to get started, but is not very efficient.
Visitors are being registered like this:
gTIPerMgr.RegReadVisitor( TVisCompanyRead_Detail ) ;
gTIPerMgr.RegReadVisitor( TVisPersonRead_Detail ) ;
gTIPerMgr.RegReadVisitor( TVisAdrsRead ) ;
gTIPerMgr.RegReadVisitor( TVisEAdrsRead ) ;
Which means, when ever gTIPerMgr.Read( bla ) is called, all the read visitors
execute. As the programmer of the system, you will probably know exactly which
visitors you want to execute in a given situation, so you can register them
under different names like this:
and so on…(This will give quite a big performance improvement)
Now, if you are using auto generated object -> database mappings (which I think
you are not), you will find these very slow. The system will be writing rather
verbose SQL, and running each query many times. Hard coded object -> database
mappings, or the tiSQLManager mapping stratergy will be much faster.
If you are using the tiSQLManager, you can check the timing of the queries.
Run a query and there will be two times in the status bar of the grid that shows
the result set. One will show how long the query took to run on the server,
and the other will show how long it took to scan the result set and convert
it into objects. This information is also available from the tiLog if you turn
the lsQueryTiming option on (it’s on by default).
Finally, one big query is faster to run than lots of little queries. The demo
has been setup so we can switch between the three OO-SQL mapping strategies,
but this has meant the tiSQLManager strategy can not be optimised. Lets say
we want to read all the info about a person, there are three ways of doing this.
a) Run a query to read all the people, then run another two queries for each
person, one for the addresses and one for the Eaddresses. This is what we are
doing and it is very slow.
b) Run a query to read all the people, then just on query for each of the addresses
and eaddresses. For each address and eaddress found, search the list of people
so the relationship can be setup in Delphi code. This will be much quicker.
c) Sometimes, depending on your database, the quickest approach will be to join
the person, address and Eaddress tables and return on huge result set. This
can be turned into a tree of objects in a single pass.
Answer: Remove $(DELPHI)\source\rtl\sys from the directories/conditionals search
path (project options). Alternatively, create FFMT.OBJ by following the
instructions at http://community.borland.com/article/0,1410,20492,00.html .
Answer: Remove $(DELPHI)\source\rtl\sys from the directories/conditionals search path (project options). Alternatively, create FFMT.OBJ by following the instructions at http://community.borland.com/article/0,1410,20492,00.html .
Question: When attempting to run the demo application, I get an error dialog with a message along the lines of ‘The procedure entry point @TIQuery@…could not be located in the dynamic link library tiPersistCore.’ A copy of this error dialog is shown below.
Answer: You have an abstract class, with a virtual abstract method in one package. You have created a descendant class, and overridden the virtual abstract method in another package, but you called inherited in the concrete class by mistake.
How this got me:
I created a new method ExecRead( ) in TtiQuery, which is contained in tiPersistCore. I implemented this method in tiQueryBDEParadox, and used Ctrl+C to create the method stub. Ctrl+C will add an Inherited key work in the body of the method if it finds the word override in the interface. Ctrl+C does not check to see if you are overriding an abstract method. Cost me an hour of my life to track down.
Question: I get a compiler message ‘Bad package unit format’ (Shown below)
Answer: You have a core package that is shared by two applications. One of the applications has a conditional define, like DEBUG set. You hit build all for one application, then try to compile the other without building all. The offending unit will cause the compile to fail.
Question: My application dies when calling the save method of an object. Running it in the IDE, I just get the CPU window. Running it stand-alone, it just terminates the application without any error message.
Answer: You have inadvertently created a circular reference between two published properties. When you call the Save method it produces a stack overflow. For example, if you have a stock control application, you might have a Stock item class with a Stock Group class object. The Stock Group class could also have a list of stock items that are of that type. If this class is Published you will get this problem. The trick is to move the Stock list from Published to Public and the problem will be solved.