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

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

Postby hua » Thu May 02, 2019 9:13 am

1. This is my existing code to use dbfs as database objects with unique aliases.
Code: Select all  Expand view  RUN

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  Expand view  RUN

  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.12
BCC5.82/BCC7.3
xHarbour/Harbour
hua
 
Posts: 1072
Joined: Fri Oct 28, 2005 2:27 am

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

Postby hua » Thu May 02, 2019 9:44 am

Solved by subclassing TDataBase
Code: Select all  Expand view  RUN

class TDataX from TDataBase
  data cFile
  method new()
endclass
method new() class TDataX
  ::super:new()
  ::cAlias := alias()
  ::nArea  := select()
return self
 
hua
 
Posts: 1072
Joined: Fri Oct 28, 2005 2:27 am

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

Postby James Bott » Thu May 02, 2019 11:29 pm

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  Expand view  RUN
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
User avatar
James Bott
 
Posts: 4840
Joined: Fri Nov 18, 2005 4:52 pm
Location: San Diego, California, USA

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

Postby hua » Fri May 03, 2019 2:35 am

.
Last edited by hua on Fri May 03, 2019 3:15 am, edited 1 time in total.
FWH 11.08/FWH 19.12
BCC5.82/BCC7.3
xHarbour/Harbour
hua
 
Posts: 1072
Joined: Fri Oct 28, 2005 2:27 am

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

Postby hua » Fri May 03, 2019 3:15 am

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  Expand view  RUN
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.12
BCC5.82/BCC7.3
xHarbour/Harbour
hua
 
Posts: 1072
Joined: Fri Oct 28, 2005 2:27 am

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

Postby hua » Fri May 03, 2019 3:30 am

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  Expand view  RUN
 
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.12
BCC5.82/BCC7.3
xHarbour/Harbour
hua
 
Posts: 1072
Joined: Fri Oct 28, 2005 2:27 am

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

Postby James Bott » Fri May 03, 2019 5:05 am

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: 4840
Joined: Fri Nov 18, 2005 4:52 pm
Location: San Diego, California, USA

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

Postby James Bott » Fri May 03, 2019 5:23 am

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  Expand view  RUN
#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
User avatar
James Bott
 
Posts: 4840
Joined: Fri Nov 18, 2005 4:52 pm
Location: San Diego, California, USA

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

Postby hua » Sat May 04, 2019 1:09 am

Thanks for verifying it for me James :D
FWH 11.08/FWH 19.12
BCC5.82/BCC7.3
xHarbour/Harbour
hua
 
Posts: 1072
Joined: Fri Oct 28, 2005 2:27 am

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

Postby nageswaragunupudi » Sat May 04, 2019 11:26 pm

hua wrote:Solved by subclassing TDataBase
Code: Select all  Expand view  RUN

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  Expand view  RUN

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: 10646
Joined: Sun Nov 19, 2006 5:22 am
Location: India

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

Postby nageswaragunupudi » Sat May 04, 2019 11:46 pm

hua wrote:1. This is my existing code to use dbfs as database objects with unique aliases.
Code: Select all  Expand view  RUN

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  Expand view  RUN

  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  Expand view  RUN
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
nageswaragunupudi
 
Posts: 10646
Joined: Sun Nov 19, 2006 5:22 am
Location: India

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

Postby James Bott » Sun May 05, 2019 8:34 pm

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  Expand view  RUN
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  Expand view  RUN
 
   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
James Bott
 
Posts: 4840
Joined: Fri Nov 18, 2005 4:52 pm
Location: San Diego, California, USA

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

Postby nageswaragunupudi » Sun May 05, 2019 9:56 pm

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

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

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

Postby James Bott » Mon May 06, 2019 12:36 am

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
User avatar
James Bott
 
Posts: 4840
Joined: Fri Nov 18, 2005 4:52 pm
Location: San Diego, California, USA

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

Postby hua » Mon May 06, 2019 2:16 am

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  Expand view  RUN
function OpenDbfs( aDbfs, lExcl, o )
    .
    .
    .
 


That's great! Thanks Rao
FWH 11.08/FWH 19.12
BCC5.82/BCC7.3
xHarbour/Harbour
hua
 
Posts: 1072
Joined: Fri Oct 28, 2005 2:27 am

Next

Return to FiveWin for Harbour/xHarbour

Who is online

Users browsing this forum: No registered users and 60 guests