Migrating TDatabase to FWH19.03's from FWH11.08

hua
Posts: 861
Joined: Fri Oct 28, 2005 2:27 am

Migrating TDatabase to FWH19.03's from FWH11.08

Post by hua »

1. This is my existing code to use dbfs as database objects with unique aliases.

Code: Select all

method activate(cAccnFrom, cAccnTo, dFrom, dCOD, oDbCgl, lPrintable) class XBrwGL
  local oldarea := select(), dTo, cStr, a_
  local oDlg, oBrw, oFntTitl, oMFont1, oImg, bInit, oSay, oBtnCgl, nWidth, lTaxCodeAlreadyOpened, cTAccn
  local aDbfs := {                   ;
                   {"oDbHgl", "hgl", "hgl2"}, ;
                   {"oDbSaleman" , "saleman" , "saleman1"}, ;
                   {"oDbJnlhis"  , "jnlhis", "jnlhis2"}  ;
                 }

  cursorWait()
  if !OpenODbfs(aDbfs,,self)
     select (oldarea)
     return nil
  endif
//------------------------------------------------------------------------------------
function OpenODbfs(a_, lExcl, o)
  /* open dbf with unique alias
     a_ must be in the format { {<oDb name>, <dbf name>, <ntx1>, <ntx2>,..., <ntxn>} }
  */

  local lRet := .t., i, nlen := len(a_), j
  local aOpened := {}, oldarea := select()

  default lExcl := .f.


  for i := 1 to nLen
      if net_use(a_[i,2], lExcl,, cGetNewAlias( 'TDF' ))
         aadd(aOpened, select())
         if len(a_[i]) > 2
            for j := 3 to len(a_[i])
                ordListAdd(a_[i,j])
            next
            set order to 1
         endif
         o:&(a_[i,1]) := TDataBase():new()
         o:&(a_[i,1]):cFile := a_[i,2]
         o:&(a_[i,1]):setBuffer(.f.)
      else
         lRet := .f.
         exit
      endif
  next
  // fail? then close those opened so far
  if !lRet
     aeval(aOpened, {|x| (x)->(dbCloseArea())})
  endif
  select (oldarea)
return lRet
 
2. The first error I got when trying to upgrade this to Harbour+FWH19.03 is direct assignment to :cFile is no longer allowed. So I tweaked
o:&(a_[i,1]) := TDataBase():new()
o:&(a_[i,1]):cFile := a_[i,2]
to
o:&(a_[i,1]) := TDataBase():new(,a_[i,2])
3. Then I get other RTE errors. After debugging, I determined it's because :cAlias is empty. So I tweaked the code further to

Code: Select all

  for i := 1 to nLen
      cAlias := cGetNewAlias( 'TDF' )
      if net_use(a_[i,2], lExcl,, cAlias)
         ...
         o:&(a_[i,1]) := TDataBase():new(cAlias, a_[i,2])
         ...
      endif
  next
 
4. At this point RTE still occur. Now it's because :nArea is 0.
How can I set :nArea without using :SetArea()? I do not wish to use :SetArea() because it assigns :cAlias and :cFile with the same value when in my case it's not.
:cAlias is some randomly generated unique alias name while :cFile is the name of the physical dbf.

TIA
Last edited by hua on Mon May 06, 2019 2:13 am, edited 2 times in total.
FWH 11.08/FWH 19.03
xHarbour 1.2.1 (Rev 6406) + BCC
Harbour 3.1 (Rev 17062) + BCC
Harbour 3.2.0dev (r1904111533) + BCC
hua
Posts: 861
Joined: Fri Oct 28, 2005 2:27 am

Re: Migrating TDatabase to FWH19.03's from FWH11.08

Post by hua »

Solved by subclassing TDataBase

Code: Select all

class TDataX from TDataBase
  data cFile
  method new()
endclass
method new() class TDataX
  ::super:new()
  ::cAlias := alias()
  ::nArea  := select()
return self
 
User avatar
James Bott
Posts: 4654
Joined: Fri Nov 18, 2005 4:52 pm
Location: San Diego, California, USA
Contact:

Re: Migrating TDatabase to FWH19.03's from FWH11.08 (Resolved)

Post by James Bott »

Hua,

I think you may be shooting yourself in the foot.

It appears that you are opening all the files at once, and setting your own aliases. Granted this is how we used to do it before using database objects. We also were limited to 255 work areas but we are no longer. Although, you don't need many workareas since you should open and close databases as needed.

With database objects, unique aliases are automatically generated so you don't even need to think about them. You can open multiple copies of the same database and thus not have to worry about saving and restoring the database state (recno, filters, scopes, index, etc.). So you can do things like lookup a customer in a function by opening a copy of the customer file in that function and closing it at the end of that function. You can do this even if there is another copy of the customer file already open. Or even 5 copies open. Everything is so much easier.

Better still is to define a subclass of TDatabase for every DBF.

Code: Select all

Class TCustomers from TDatabase
   Method New()
Endclass

Method New() Class TCustomers
   ::super():New(,"Customers",, .T.)
   ::use()
Return Self
Now you can simply open a customer database like this?

oCustomers:= TCustomers():New()

And even open another copy in the same function.

oCustomers2:= TCustomers():New()

Now you don't have to ever deal with aliases.

You should define a class for each database.

So many options with objects, and the code is much simpler.

James
Last edited by James Bott on Fri May 03, 2019 5:37 am, edited 3 times in total.
FWH 18.05/xHarbour 1.2.3/BCC7/Windows 10
hua
Posts: 861
Joined: Fri Oct 28, 2005 2:27 am

Re: Migrating TDatabase to FWH19.03's from FWH11.08 (Resolved)

Post by hua »

.
Last edited by hua on Fri May 03, 2019 3:15 am, edited 1 time in total.
FWH 11.08/FWH 19.03
xHarbour 1.2.1 (Rev 6406) + BCC
Harbour 3.1 (Rev 17062) + BCC
Harbour 3.2.0dev (r1904111533) + BCC
hua
Posts: 861
Joined: Fri Oct 28, 2005 2:27 am

Re: Migrating TDatabase to FWH19.03's from FWH11.08 (Resolved)

Post by hua »

James Bott wrote: It appears that you are opening all the files at once, and setting your own aliases.
Yup. If any of it failed to be opened there's no point proceeding because all of them are required, at least that's what I think.
I also would like to use net_use() to open dbfs in network environment as it'll attempt to open a dbf for a few second before giving up and display error message.
With database objects, unique aliases are automatically generated so you don't even need to think about them. You can open multiple copies of the same database and thus not have to worry about saving and restoring the database state (recno, filters, scopes, index, etc.). So you can do things like lookup a customer in a function by opening a copy of the customer file in that function and closing it at the end of that function. You can do this even if there is another copy of the customer file already open. Or even 5 copies open. Everything is so much easier.

Better still is to define a subclass of TDatabase for every DBF.

Code: Select all

Class TCustomers from TDatabase
   Method New()
Endclass

Method New(,"Customers") Class TCustomers
   ::use()
Return Self
Now you can simply open a customer database like this?

oCustomers:= TCustomers():New()

And even open another copy in the same function.

oCustomers2:= TCustomers():New()

Now you don't have to ever deal with aliases.

You should define a class for each database.

So many options with objects, and the code is much simpler.

James
Thanks for the code snippet James. I thought TDatabase:open() was the only way to open a dbf. I can see with your code a unique aliases is already handled by TDatabase and :td_ExecLoop() is already functionally similar to net_use().

This will help simplify my code in the future. Thanks again James
FWH 11.08/FWH 19.03
xHarbour 1.2.1 (Rev 6406) + BCC
Harbour 3.1 (Rev 17062) + BCC
Harbour 3.2.0dev (r1904111533) + BCC
hua
Posts: 861
Joined: Fri Oct 28, 2005 2:27 am

Re: Migrating TDatabase to FWH19.03's from FWH11.08 (Resolved)

Post by hua »

James Bott wrote:You can open multiple copies of the same database...
But I notice the following comment in TDatabase:use() James,

Code: Select all

   
if Empty( ::nArea ) // 2017-09-04: Prevent re-opening same dbf in multiple workareas

      if Empty( ::cAlias )
         ::cAlias := ::NewAlias()
      endif
 
Does that mean a dbf can be opened only once?
FWH 11.08/FWH 19.03
xHarbour 1.2.1 (Rev 6406) + BCC
Harbour 3.1 (Rev 17062) + BCC
Harbour 3.2.0dev (r1904111533) + BCC
User avatar
James Bott
Posts: 4654
Joined: Fri Nov 18, 2005 4:52 pm
Location: San Diego, California, USA
Contact:

Re: Migrating TDatabase to FWH19.03's from FWH11.08 (Resolved)

Post by James Bott »

Yup. If any of it failed to be opened there's no point proceeding because all of them are required, at least that's what I think.
If this is a multi-user app, then all databases need to be opened in shared mode, so why would they fail to open?
I also would like to use net_use() to open dbfs in network environment as it'll attempt to open a dbf for a few second before giving up and display error message.
This is built into TDatabase.

James
FWH 18.05/xHarbour 1.2.3/BCC7/Windows 10
User avatar
James Bott
Posts: 4654
Joined: Fri Nov 18, 2005 4:52 pm
Location: San Diego, California, USA
Contact:

Re: Migrating TDatabase to FWH19.03's from FWH11.08 (Resolved)

Post by James Bott »

Does that mean a dbf can be opened only once?
Apparently not-see sample test code below. Each is opened in a different workarea. That comment is either misleading or leftover from a previous version.

James

Code: Select all

#include "fivewin.ch"

Function Main()

   Local oCustomers,oCustomers2
   
   oCustomers:= TCustomers():new()
   
   oCustomers2:= TCustomers():new()
   
   msgInfo(oCustomers:Company,"oCustomers:Company")
   msgInfo(oCustomers:nArea,"oCustomers:nArea")
   
   msgInfo(oCustomers2:Company,"oCustomers2:Company")
   msgInfo(oCustomers2:nArea,"oCustomers2:nArea")

Return nil

Class TCustomers from TDatabase
   Method New()
Endclass

Method New(lShared) Class TCustomers
    default lShared:=.T.
    ::super():New(,"customers",,lShared)
    ::use()
Return self
 
FWH 18.05/xHarbour 1.2.3/BCC7/Windows 10
hua
Posts: 861
Joined: Fri Oct 28, 2005 2:27 am

Re: Migrating TDatabase to FWH19.03's from FWH11.08 (Resolved)

Post by hua »

Thanks for verifying it for me James :D
FWH 11.08/FWH 19.03
xHarbour 1.2.1 (Rev 6406) + BCC
Harbour 3.1 (Rev 17062) + BCC
Harbour 3.2.0dev (r1904111533) + BCC
User avatar
nageswaragunupudi
Posts: 8017
Joined: Sun Nov 19, 2006 5:22 am
Location: India
Contact:

Re: Migrating TDatabase to FWH19.03's from FWH11.08

Post by nageswaragunupudi »

hua wrote:Solved by subclassing TDataBase

Code: Select all

class TDataX from TDataBase
  data cFile
  method new()
endclass
method new() class TDataX
  ::super:new()
  ::cAlias := alias()
  ::nArea  := select()
return self
 
We advise you to open the dbf using the methods of TDatabase only.
Still, if you open a dbf directly, the right way to apply it to Tdatabase is :

Code: Select all

oDbf := TDatabase():New( cAlias )  // or :New( nSelect )
 
Do not directly assign cAlias, cFile and nSelect, as in your above example.
TDatabase will automatically assign the values to the Datas on its own automatically.
Regards

G. N. Rao.
Hyderabad, India
User avatar
nageswaragunupudi
Posts: 8017
Joined: Sun Nov 19, 2006 5:22 am
Location: India
Contact:

Re: Migrating TDatabase to FWH19.03's from FWH11.08 (Resolved)

Post by nageswaragunupudi »

hua wrote:1. This is my existing code to use dbfs as database objects with unique aliases.

Code: Select all

method activate(cAccnFrom, cAccnTo, dFrom, dCOD, oDbCgl, lPrintable) class XBrwGL
  local oldarea := select(), dTo, cStr, a_
  local oDlg, oBrw, oFntTitl, oMFont1, oImg, bInit, oSay, oBtnCgl, nWidth, lTaxCodeAlreadyOpened, cTAccn
  local aDbfs := {                   ;
                   {"oDbHgl", "hgl", "hgl2"}, ;
                   {"oDbSaleman" , "saleman" , "saleman1"}, ;
                   {"oDbJnlhis"  , "jnlhis", "jnlhis2"}  ;
                 }

  cursorWait()
  if !OpenODbfs(aDbfs,,self)
     select (oldarea)
     return nil
  endif
//------------------------------------------------------------------------------------
function OpenODbfs(a_, lExcl, o)
  /* open dbf with unique alias
     a_ must be in the format { {<oDb name>, <dbf name>, <ntx1>, <ntx2>,..., <ntxn>} }
  */

  local lRet := .t., i, nlen := len(a_), j
  local aOpened := {}, oldarea := select()

  default lExcl := .f.


  for i := 1 to nLen
      if net_use(a_[i,2], lExcl,, cGetNewAlias( 'TDF' ))
         aadd(aOpened, select())
         if len(a_[i]) > 2
            for j := 3 to len(a_[i])
                ordListAdd(a_[i,j])
            next
            set order to 1
         endif
         o:&(a_[i,1]) := TDataBase():new()
         o:&(a_[i,1]):cFile := a_[i,2]
         o:&(a_[i,1]):setBuffer(.f.)
      else
         lRet := .f.
         exit
      endif
  next
  // fail? then close those opened so far
  if !lRet
     aeval(aOpened, {|x| (x)->(dbCloseArea())})
  endif
  select (oldarea)
return lRet
 
2. The first error I got when trying to upgrade this to Harbour+FWH19.03 is direct assignment to :cFile is no longer allowed. So I tweaked
o:&(a_[i,1]) := TDataBase():new()
o:&(a_[i,1]):cFile := a_[i,2]
to
o:&(a_[i,1]) := TDataBase():new(,a_[i,2])
3. Then I get other RTE errors. After debugging, I determined it's because :cAlias is empty. So I tweaked the code further to

Code: Select all

  for i := 1 to nLen
      cAlias := cGetNewAlias( 'TDF' )
      if net_use(a_[i,2], lExcl,, cAlias)
         ...
         o:&(a_[i,1]) := TDataBase():new(cAlias, a_[i,2])
         ...
      endif
  next
 
4. At this point RTE still occur. Now it's because :nArea is 0.
How can I set :nArea without using :SetArea()? I do not wish to use :SetArea() because it assigns :cAlias and :cFile with the same value when in my case it's not.
:cAlias is some randomly generated unique alias name while :cFile is the name of the physical dbf.

TIA
As always, we keep advising to open the dbfs by using methods of TDatabase only.

We advise the revise the function OpenDbfs() on these lines:

Code: Select all

function OpenDbfs( aDbfs, lExcl, o )

   local i, j, oDbf, cErr
   local aOpened  := {}
   local lRet     := .t.

   DEFAULT lExcl := .f.

   for i := 1 to Len( aDbfs )
      oDbf  := TDatabase():Open( nil, aDbfs[ i, 2 ], "DBFNTX", !lExcl )
      AAdd( aOpened, oDbf )
      if oDbf:Used()
         for j := 3 to Len( aDbfs[ i ] )
            TRY
               oDbf:OrdListAdd( aDbfs[ i, j ] )
            CATCH
               lRet  := .f.
               cErr  := "Can not open " + aDbfs[ i, j ] + ".ntx"
               EXIT
            END
         next
      else
         lRet  := .f.
         cErr  := "Can not open " + aDbfs[ i, 2 ] + ".dbf"
      endif
      if lRet == .f.
         EXIT
      endif
   next

   if lRet
      AEval( aDbfs, { |a,i| OSend( o, "_" + a[ 1 ], aOpened[ i ] ) } )
   else
      AEval( aOpened, { |oDbf| oDbf:End() } )
      ? cErr
   endif

return lRet
 
Regards

G. N. Rao.
Hyderabad, India
User avatar
James Bott
Posts: 4654
Joined: Fri Nov 18, 2005 4:52 pm
Location: San Diego, California, USA
Contact:

Re: Migrating TDatabase to FWH19.03's from FWH11.08 (Resolved)

Post by James Bott »

Hua,

Here is a more complete New() method. You can use this as a starting point for each class (one class for each database). The file is opened (with automatic retrys) and then the primary key index is set. So with just this one line of code you open the database, open the indexes, and set the order.

oCustomers:= TCustomers():New()

Sample New() method:

Code: Select all

CLASS TCustomers from TDatabase
   Method New()
ENDCLASS

Method New( lShared ) Class TCustomers
   Default lShared:= .T.
   ::Super:New(,"customers",,lShared)
   If ::Use()
      ::SetOrder("CUSTNO") // Primary key
      ::Gotop()
   Endif
Return self
 
Note that at the start of your application you need to do this (assuming you are using CDX's):

Code: Select all

   
   REQUEST DBFCDX
   rddsetdefault( "DBFCDX" )
   SET EXCLUSIVE OFF
   SET(_SET_AUTOPEN, .T. )
 
This causes all the indexes to be automatically opened whenever you open a DBF.
FWH 18.05/xHarbour 1.2.3/BCC7/Windows 10
User avatar
nageswaragunupudi
Posts: 8017
Joined: Sun Nov 19, 2006 5:22 am
Location: India
Contact:

Re: Migrating TDatabase to FWH19.03's from FWH11.08 (Resolved)

Post by nageswaragunupudi »

Mr. James
We can understand from his code that he is using DBFNTX.
Regards

G. N. Rao.
Hyderabad, India
User avatar
James Bott
Posts: 4654
Joined: Fri Nov 18, 2005 4:52 pm
Location: San Diego, California, USA
Contact:

Re: Migrating TDatabase to FWH19.03's from FWH11.08 (Resolved)

Post by James Bott »

Nages,
We can understand from his code that he is using DBFNTX.
Thanks, I missed that. Maybe it is a good time to switch to CDXs.
FWH 18.05/xHarbour 1.2.3/BCC7/Windows 10
hua
Posts: 861
Joined: Fri Oct 28, 2005 2:27 am

Re: Migrating TDatabase to FWH19.03's from FWH11.08 (Resolved)

Post by hua »

nageswaragunupudi wrote: As always, we keep advising to open the dbfs by using methods of TDatabase only.

We advise the revise the function OpenDbfs() on these lines:

Code: Select all

function OpenDbfs( aDbfs, lExcl, o )
    .
    .
    .
 
That's great! Thanks Rao
FWH 11.08/FWH 19.03
xHarbour 1.2.1 (Rev 6406) + BCC
Harbour 3.1 (Rev 17062) + BCC
Harbour 3.2.0dev (r1904111533) + BCC
Post Reply