Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
Home
Discussion GroupsVB SyntaxEnterprise DevelopmentDatabase AccessControlsCOMWin APICrystal ReportDeploymentGeneralGeneral 2
Related Topics
VB.NET / ASP.NETMS SQL ServerMS AccessOther Database ProductsMore Topics ...

VB Forum / Database Access / September 2008



Tip: Looking for answers? Try searching our database.

foreign key ado syntax

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
MP - 07 Aug 2008 03:55 GMT
I have a db with a string field "dwgname"
as many records will share a common value i'm considering "normalizing" that
field to it's own table
then instead of the string "dwgname" I'll store the foreign key of the
"dwgNameTable"

I create fields via adox using the following(wrapped in a function in a
class)
Dim oField as Field
....
Set oField = New cField: oField.Name = "fldDwgName": oField.DataType =
adVarWChar

so if i store a key, that will just be a long? right? so i'd just change the
datatype to adInteger???

or do I do something different with a field that is to receive foreign
key??/
for example to create a primary key in the table i do...
      Set oField = New cField: oField.Name = "fldIDPrimary":
oField.DataType = adInteger: oField.IsPrimary = True: oField.IsNullable =
False: oField.AutoIncrement = True

so does the receiving field need special attributes? or is it just set up to
receive a long value?
Set oField = New cField: oField.Name = "fldDwgNameKey": oField.DataType =
adInteger

and in the context of why its' good to normalize, I'm still storing
duplicate values, it's just that the value is a long instead of a string and
therefore saves memory???

you normalize to avoid duplicate data(i think...among other reasons i
guess), but i'm still storing the same number of duplicate datas, just a
different data type, right???

thanks
mark
MP - 07 Aug 2008 04:31 GMT
>I have a db with a string field "dwgname"
> as many records will share a common value i'm considering "normalizing"
> that field to it's own table
> then instead of the string "dwgname" I'll store the foreign key of the
> "dwgNameTable"

found it
Set oKey = New ADOX.key
     With oKey
       .Type = adKeyForeign

now to figure out how to tell it what table's id to use...
MP - 07 Aug 2008 05:20 GMT
>>I have a db with a string field "dwgname"
>> as many records will share a common value i'm considering "normalizing"
[quoted text clipped - 8 lines]
>
> now to figure out how to tell it what table's id to use...

duh! how about .RelatedTable
:-)
I forgot I was using a cFields class to wrap the ado.Field object and give
some other properties,
sorry for the confusion

this might work, added foreign key option to my create table function in
cDatabaseController cls
it looks bad cause i have to loop through fields collection twice, since i
have to add a field to a table, and append the table to the catalog before
setting some properties of the field(key)
but i don't know if there is a way around that duplication?

also i'm not sure if i should add a .DeleteRule
     adRICascade
    1
    Cascade changes.

     adRINone
    0
    Default. No action is taken.

so if in the "foreign" table, if an entry were deleted(which I don't expect
in my case) then any entries in the "main" table
that have that "fk" would be deleted? if the setting were adRiCascade...is
that right?
and if adRiNone , they would remain but would contain 'invalid' data in that
field, right?

Public Sub CreateTable(TableName As String, FieldObjectsCollection As
Collection)
 On Error GoTo CreateTable_Error
 Dim ADOXtable As Table
 Dim oField As cField
 Dim oKey As ADOX.key
 Set ADOXtable = New ADOX.Table
 ADOXtable.Name = TableName

 Set ADOXtable.ParentCatalog = moCatalog
 With ADOXtable
   'Add Columns to the table
     For Each oField In FieldObjectsCollection
       .Columns.Append oField.Name, oField.DataType
       If oField.AutoIncrement Then
          .Columns(oField.Name).Properties("AutoIncrement") = True
       End If
       If oField.IsNullable Then
         .Columns(oField.Name).Properties("Nullable") = True
       End If
    Next oField
 End With 'table

'append tables to database before setting keys
 moCatalog.Tables.Append ADOXtable

  For Each oField In FieldObjectsCollection
'could add boolean to ensure only one primary key, but if i try to add more,
i expect i'd get an error here anyway?
   If oField.IsPrimary Then
     Set oKey = New ADOX.key
     With oKey
         .Name = "PrimaryKey"
         .Type = adKeyPrimary
         .RelatedTable = TableName
         .Columns.Append oField.Name
     End With
     ADOXtable.Keys.Append oKey
   End If

   'can have multiple fks
   If oField.IsForeignKey Then
     Set oKey = New ADOX.key
     With oKey
       .Name = "fk_" & oField.RelatedTableName
       .Type = adKeyForeign
       .RelatedTable = oField.RelatedTableName
       .Columns.Append oField.Name
     End With

     ADOXtable.Keys.Append oKey
   End If
 Next oField
'Clean up
Set ADOXtable = Nothing
msDbTableName = TableName

ExitHere:
 On Error GoTo 0
 Exit Sub

CreateTable_Error:
 LogError ProcName
End Sub
MP - 12 Aug 2008 13:17 GMT
I'm still not getting it to work :-(
per the help on ado on dev site
<quote
under the section on create table syntax on setting constraints:
...or if the field in the foreign table is the primary key, you only need
name the table and the database engine references it by default:

CREATE TABLE Sales (SalesID INTEGER, ProductID INTEGER CONSTRAINT
ForeignKeyRef REFERENCES Products, Item TEXT);
end quote>

so with adox I'm trying to create a key (which is how i understand on e
creates constraints if not using sql "Create Table" statement and rather
building a table via adox.

here's what i'm trying
LogEntry "Create foreign key"
'Create foreign key constraint on field in main table
'oField is instance of cField class holding name, datatype, and properties
of a Field object to be created
     Set oKey = New ADOX.key
     With oKey
       .Name = "fk_" & oField.RelatedTableName
       .Type = adKeyForeign <------------------------- i assumed this was
equiv to "CONSTRAINT ForeignKeyRef "
       .RelatedTable = oField.RelatedTableName <----- i assumed this was
equiv to "REFERENCES TableName"
       .Columns.Append oField.Name
     End With

     ADOXtable.Keys.Append oKey <--------------- this is the line throwing
error

the error i'm getting:
<-2147217903>  <Microsoft JET Database Engine> (Column ID is invalid.)

anyone see what i'm doing wrong?
thanks
mark
Schmidt - 12 Aug 2008 18:18 GMT
> I'm still not getting it to work :-(
Don't know, why you are trying to tell the
JET-engine, that a special Field in your
Main-Table is a Foreign-Key-Field at all.
That would only be necessary, if you want
to use some special features of the DB-engine
itself (e.g. cascaded deletes, which I read you
don't need in your scenario) ... or if you want
to use it inside the Access.exe-GUI, to let
some special Access-GUI-Controls behave
properly (e.g. in the Access-Grid-Control
you would get automagic filling of a combo-
box for your foreign-key-field, not showing
the adInteger-Keys, but the "clear-text" instead
in the DropDown.

But if this is for usage in VB6, then you normally
don't need "extended definitions" for foreign-keys
in your table-design.
In my DB-Schemas I simply follow a Field-
Naming-Convention:
Each Table always gets an ID-Field as its
first Field.
If the ID-Field of some table is used inside
other tables, I simply name it appropriately
with SomeTableName_ID.

Simply put a normal Integer-Field into your
Main-Table, and handle the ID-referencing of
the associated table in your class-wrappers
manually. Should only be a few lines of code.
If you follow the above mentioned naming-
convention, then you could even parse
automatically for Fields with the Suffix '_ID',
split the Foreign-Table-Name-Prefix from
such a FieldName - and handle everything
generically in your Object-Model.

BTW - why ADO (I assume with JET) ...
since you are a user of dhRichClient.dll, why not
using the builtin SQLite-Wrapper - you'd get
some more "degrees of freedom" this way
(smaller, faster, InMemory-DBs, definable
triggers, e.g. for selfdefined cascaded deletes,
which don't need special Table-Defines or
Field-Attributes, to work properly).

Olaf
MP - 12 Aug 2008 19:16 GMT
>> I'm still not getting it to work :-(
> Don't know, why you are trying to tell the
> JET-engine, that a special Field in your
> Main-Table is a Foreign-Key-Field at all.

because i'm a total newbie to databases :-)
I just read the help files and these ngs to try to learn
I saw the foreign key attribute and assumed I needed to use that.

> That would only be necessary, if you want
> to use some special features of the DB-engine
> itself (e.g. cascaded deletes, which I read you
> don't need in your scenario)

haven't figured out if or why I'd need the cascade function...pretty sure in
this tiny app I don't and won't in the future.

... or if you want
> to use it inside the Access.exe-GUI

no access to access :-)

> But if this is for usage in VB6, then you normally
> don't need "extended definitions" for foreign-keys
> in your table-design.

great, one less thing to worry about :-)

> In my DB-Schemas I simply follow a Field-
> Naming-Convention:
[quoted text clipped - 23 lines]
> which don't need special Table-Defines or
> Field-Attributes, to work properly).

well, I've watched with interest over the last year or so your conversations
with rb smissert re sqlite
I've saved all that for future reference for when I have time to learn how
to use sqllite.
at this point it's one more learning curve i've been putting off...since I
just barely am getting the hang of ado/adox
(creating tables, fields, getting values, inserting values etc)

but maybe you'll convince me it's not as hard as I fear
:-)

and on the general topic of foreign keys...this whole issue came up just
because I realize i'm not normalizing my single table database.
its' probably not required at all for this tiny app I'm trying to build but
I just didn't realize how hard it would be to learn how to do and from what
I'd read on normalization I thought it was the "right" thing to do ;-)

I have a field that will contain duplicate strings (fldDwgName)
so i figured it was better to have a filed that contained duplicate
longs(FK_primarykey from 'foreign table') (fldDwgName_ID)

that's my (mis)understanding of the theory of why to "normalize" <g>

If I ever succeed in getting a foreignkey field created (either with ado/jet
or sqllite) I don't even know how to use it exactly...

I think when making an entry I'll have to read the "foreign table", find the
string value matching my current data, find the id for that string value,
then put that id into the "fk_field" in the main table....
is that right?

seems like more work than what I'm doing now, just putting the string value
in my main table, but I'm trying to learn the "right" way to do these
things.

:-)
any pointers appreciated
mark
Schmidt - 14 Aug 2008 16:40 GMT
> >> I'm still not getting it to work :-(
> > Don't know, why you are trying to tell the
[quoted text clipped - 6 lines]
> I saw the foreign key attribute and assumed I
> needed to use that.
As said, only if you need special DB-Features,
which are based on these "Extra-Definitions".

> > That would only be necessary, if you want
> > to use some special features of the DB-engine
[quoted text clipped - 4 lines]
> cascade function...pretty sure in
> this tiny app I don't and won't in the future.
As said, in other DB-Systems (e.g. MS-SQL-
Server or Postgres or even SQLite you can
define appropriate Triggers for that purpose.

> > But if this is for usage in VB6, then you normally
> > don't need "extended definitions" for foreign-keys
[quoted text clipped - 14 lines]
> but maybe you'll convince me it's not as hard as I fear
> :-)

To learn ADO, and how to work with Cnn-, Command-
and Recordset-Objects is not a bad idea - it is a well
established DB-abstraction-layer, but if you know ADO,
you will have no problems with my SQLite-Wrapper,
it is not completely compatible, but behaves very similar.

> and on the general topic of foreign keys...this whole
> issue came up just because I realize i'm not normalizing
> my single table database. its' probably not required at all
> for this tiny app I'm trying to build but I just didn't realize
> how hard it would be to learn how to do and from what I'd
> read on normalization I thought it was the "right" thing to do ;-)
Normalization is usually a good idea - it requires planning
and makes some additional work - but it worth the effort
regarding future maintenance and enhancements on the DB-
Schema-Layout. Though sometimes it affects performance -
but only on huge Databases - and if your Indexes are not
placed properly. Finally (over-)normalized DBs behave a
bit like "too overdesigned OOP-models" - so one has to find
a good balance between "strict flatness" and "overboarding
hierarchy-levels" - strictly following "the rules" can lead to
the same mess like over-laziness.

> I have a field that will contain duplicate strings (fldDwgName)
> so i figured it was better to have a filed that contained duplicate
> longs(FK_primarykey from 'foreign table') (fldDwgName_ID)
>
> that's my (mis)understanding of the theory of why to "normalize" <g>
If that is a misunderstanding, depends (as always ...<g>).
E.g., if in your case for example a file will be renamed, then
you would only have to reflect this name-change in your
separated FileName-Table (under the same old ID).
That's all - in your MainTable (which uses only the IDs),
this new, changed Name would be automatically "reflected".
If you want to avoid exactly that (e.g. if you want to store
the FileName-Data in your Main-Table, for archive-reasons),
then you should rethink about your Table-Splitting again (or
implement another solution, which is based on the "normalized
approach", but never allows renaming on once given FileName-
IDs, but would require a new record with a new FileName-ID
for Namechanges of already stored Files).

> If I ever succeed in getting a foreignkey field created
> (either with ado/jet or sqllite) I don't even know how
> to use it exactly...
Maybe a better suited example is easier to understand...,
e.g. two (normalized) tables 'Persons' and 'Addresses'
(somewhat simplified).
Persons with: ID, Name
Addresses with: ID, ZipCode, Street, Persons_ID

Persons_ID is the (Integer) Foreign-Key-Field,
pointing to table 'Persons' - and this allows us,
to have more than one Address-Record for each
Person (beside the fact, that tables which identify
a Person or a "User" will also be of use in many
more tables than only Addresses in a typical DB-
Design).

Anyway, assuming you get a call from "Some Person",
that this is now the third advertising-letter he receives
from your side where his name was misspelled, you
will only have to change the name in one record and
in one single table -> 'Persons'.

Will try to put a small example together, based on
the above, using the SQLite-Wrapper in dhRichClient
(including a Trigger, which deletes automatically from
table 'Addresses' - as soon as a Record from 'Persons'
is deleted - just to demonstrate a "cascaded delete" the
SQLite-way).

Olaf
MP - 14 Aug 2008 20:18 GMT
>> >> I'm still not getting it to work :-(
>> > Don't know, why you are trying to tell the
[quoted text clipped - 8 lines]
> As said, only if you need special DB-Features,
> which are based on these "Extra-Definitions".

thanks

>> well, I've watched with interest over the last year
>> or so your conversations
>> with rb smissert re sqlite
>> I've saved all that for future reference for when I have
>> time to learn how to use sqllite.

well, I *thought* i'd saved them...but cant find now...will have to search
the ng arcives to get that trove...<g>

> To learn ADO, and how to work with Cnn-, Command-
> and Recordset-Objects is not a bad idea - it is a well
> established DB-abstraction-layer, but if you know ADO,
> you will have no problems with my SQLite-Wrapper,
> it is not completely compatible, but behaves very similar.

great, I've started reading your code in the dhSqllite demo project

Finally (over-)normalized DBs behave a
> bit like "too overdesigned OOP-models" - so one has to find
> a good balance between "strict flatness" and "overboarding
> hierarchy-levels" - strictly following "the rules" can lead to
> the same mess like over-laziness.

i'm thinking I don't realy need to do the splitting now
this db is just being used as a temporary storing/ sorting tool

> If that is a misunderstanding, depends (as always ...<g>).
> E.g., if in your case for example a file will be renamed, then
> you would only have to reflect this name-change in your
> separated FileName-Table (under the same old ID).

makes sense, in my case that won't happen so I probably don't need to split

after storing/sorting/assigning id-code to entries, i write the new data
(computed id-codes) back to the source dwgs:

Call ReadDatabaseIntoBlockDataObjects(mcolBlockDataObjects)
'which is basically
   Set oRs = oConn.Execute ( "Select * FROM " & sTableName & " ORDER BY
fldDwgName")
   Call ReadRecordsetWriteBlockDataObjects(oRs, mcolBlockDataObjects)

then those objects put data back into the appropriate source files (.dwg
files)
For Each oBdo In mcolBlockDataObjects
 DwgFullName = oBdo.DwgName
 If Len(LastDwgFullName) > 0 Then
   'not in first dwg, save, close lastdwg and open next dwg
     If Not DwgFullName = LastDwgFullName Then
       'store name for next check
       LastDwgFullName = DwgFullName
       'save current dwg
       Save
       'close current dwg
       Close
       'open next dwg in list
       Open DwgFullName
     Else
       'already in that dwg
     End If
   Else 'in first dwg
       'store name for next check
       LastDwgFullName = DwgFullName
       'open first dwg in list
       Open DwgFullName
 End If 'in first dwg
'enter data into file
   ...
Next oBdo

I see the point in your example of why to normailze, thanks

> Anyway, assuming you get a call from "Some Person",
> that this is now the third advertising-letter he receives
[quoted text clipped - 10 lines]
>
> Olaf

appreciate the offer, Id be interested to see how to read and write to a
record when using a FK
eg, maybe the person is already in the Persons table or maybe not.
I assume there has to be some way to put the first occurrance into the table
and retrieve the index to use as FK, then with each successive entry, grab
the idex for the existing person out of the table and use that as FK for the
main table entry
something like...
fk_ID_Person =
(Select fld_ID_Person From tblPersons
   Where Exists (Select * From tblPersons Where fldPersonName = "thisname")
).Value

then if "thisname" is not on list
(Select fld_ID_Person From tblPersons Where Exists (Select ...)).Value
either would error or return 0 is what I'm guessing???
and I'd have to make an entry for that person and get the last iD...???

is that close?

thanks
mark
Schmidt - 14 Aug 2008 20:49 GMT
> > To learn ADO, and how to work with Cnn-, Command-
> > and Recordset-Objects is not a bad idea - it is a well
[quoted text clipped - 3 lines]
>
> great, I've started reading your code in the dhSqllite demo project
Yep, if you compare for example the two VB-Forms
fTestNWindADO and fTestNwinddhSQLite, you will
find both of them very similar code-wise.

> Finally (over-)normalized DBs behave a
> > bit like "too overdesigned OOP-models" - so one has to find
[quoted text clipped - 4 lines]
> i'm thinking I don't realy need to do the splitting now
> this db is just being used as a temporary storing/ sorting tool
Especially if you want to achieve a good performance,
then the "direct-saving" is probably better in your case
(though that would need more memory on disk of
course, dependent on how many duplicate filenames
you have in your scenario, an Integer takes up only
four Bytes in the DB).

> ... Id be interested to see how to read and write to a
> record when using a FK - eg, maybe the person is already
> in the Persons table or maybe not...
Ok, will try to address that special case too with
an enhanced example (based on the simple one
I've just posted).

Olaf
Schmidt - 15 Aug 2008 18:57 GMT
> > ... Id be interested to see how to read and write to a
> > record when using a FK - eg, maybe the person is already
> > in the Persons table or maybe not...
> Ok, will try to address that special case too with
> an enhanced example ...

Here's the Download-Link:
www.datenhaus.de/Downloads/ForeignKeyFileScan.zip

Have tried, to match a bit with your Filescan-Scenario.
The Demo uses two Classes (one for ADO/JET and
one for SQLite), which both implement an iFileScan-
Interface, which was defined in an "abstract class" in
the same project.

Tried my best, to implement both Classes with the
fastest known methods (using Command-Objects
for the inserts) - anyway, the ADO/JET approach
is ca. factor 15 away from the SQLite-implementation.

Both versions currently operate directly against the
DB, so in principle should produce consistent results
even in multiuser-mode.

If you always create new DBs for your FileScans and/or
are the only user working on this DB, then you could
speedup both versions, if you "buffer" the FileName-
ExistsInDB-check with a Collection or the SortedDictionary.

Olaf
Schmidt - 16 Aug 2008 08:51 GMT
> www.datenhaus.de/Downloads/ForeignKeyFileScan.zip
> ...
> ... you could speedup both versions, if you "buffer" the FileName-
> ExistsInDB-check with a Collection or the SortedDictionary.

Just enhanced the small Demo-Code above with the
suggested Dictionary-Lookup (which can be switched
inside the Class-Implementations for ADO/JET and
SQLite over a constant now, between Dict-Filename-
Lookups and the slower Rs-FileName-Lookups) -
and that change boosts both implementations.
The SQLite-Version profites the most from this change
and so you have now a factor 40-advantage in this
scenario compared with ADO/JET.

The new code is downloadable under the same link.

Olaf
MP - 18 Aug 2008 16:55 GMT
Fantastic,
Thanks Olaf, will dl that and study
I appreciate very much your taking time to help.
Mark

>> www.datenhaus.de/Downloads/ForeignKeyFileScan.zip
>> ...
[quoted text clipped - 14 lines]
>
> Olaf
MP - 18 Aug 2008 18:02 GMT
Olaf,
Just read through your sample.
that is a thing of beauty!
Thank you again!
Mark

> Fantastic,
> Thanks Olaf, will dl that and study
[quoted text clipped - 19 lines]
>>
>> Olaf
Schmidt - 14 Aug 2008 20:35 GMT
[simple Persons, Addresses scenario, to demonstrate
Foreign-Keys]
> Will try to put a small example together, based on
> the above, using the SQLite-Wrapper in dhRichClient
> (including a Trigger, which deletes automatically from
>  table 'Addresses' - as soon as a Record from 'Persons'
>  is deleted - just to demonstrate a "cascaded delete" the
>  SQLite-way).

Ok, for those who are interested:

'***Into a Form (Project needs a reference to dhRichClient)
Option Explicit

Private Cnn As cConnection

Private Sub Form_Load()
 Set Cnn = New cConnection
 Cnn.CreateNewDB 'without FileName opens a new InMemory-DB

 CreateSchema 'here we define two tables, indexes and a trigger
End Sub

Private Sub Form_Click()
 'first two simple dumps, to check the content of our two tables
 DumpRsContent Cnn.OpenRecordset("Select * from Persons")
 DumpRsContent Cnn.OpenRecordset("Select * from Addresses")

 'now a simple Join, to "integrate" the "cleartext" of our foreign table
 DumpRsContent Cnn.OpenRecordset("Select Addresses.*, Name From " & _
                                 "Addresses Inner Join Persons " & _
                                 " On Persons_ID = Persons.ID")

 'and finally a demonstration, that the defined trigger works
 Cnn.Execute "Delete From Persons Where ID > 2"
 Debug.Print "After a cascaded Delete on Table Persons, let's check..."
 DumpRsContent Cnn.OpenRecordset("Select * from Addresses")
End Sub

Private Sub DumpRsContent(Rs As cRecordset)
Dim Fld As cField
 Debug.Print "Content of: "; Rs.SQL
 'the header
 For Each Fld In Rs.Fields
   Debug.Print Fld.Name,
 Next Fld
 Debug.Print vbCrLf; String(128, "-")

 'and the content
 Do Until Rs.EOF
   For Each Fld In Rs.Fields
     Debug.Print Fld.Value,
   Next Fld
   Debug.Print
   Rs.MoveNext
 Loop
 Debug.Print
End Sub

Private Sub CreateSchema()
 'Create a simple Persons-Table
 Cnn.Execute "Create Table Persons(ID    Integer Primary Key," & _
                                 " Name  Text)"
 'to speedup caseinsensitive "reverse search" per Name on Persons-Table
 Cnn.Execute "Create Index Idx_Persons_Name On " & _
                              "Persons(Name Collate NoCase)"

 'now the Addresses-Table, which points to table Persons with Persons_ID
 Cnn.Execute "Create Table Addresses(ID         Integer Primary Key," & _
                                   " ZipCode    Text," & _
                                   " City       Text," & _
                                   " Street     Text," & _
                                   " Persons_ID Integer)"
 'to speedup joining with the Persons-Table over Persons_ID
 Cnn.Execute "Create Index Idx_Addresses_Persons_ID On " & _
                              "Addresses(Persons_ID)"

 'now we create a trigger for cascaded deletes
 Cnn.Execute "CREATE TRIGGER Delete_On_Addresses " & _
               "BEFORE DELETE ON Persons " & _
               "FOR EACH ROW BEGIN " & _
                 "DELETE from Addresses WHERE Persons_ID = OLD.ID; " & _
               "END;"

 'and finally we fill in some Demo-Data
 FillDemoData
End Sub

Private Sub FillDemoData()
Dim i&, j&, Rs As cRecordset
 'fill in some Persons
 Set Rs = Cnn.OpenRecordset("Select * from Persons")
 For i = 1 To 4
   Rs.AddNew
   Rs.Fields("Name").Value = "Person " & i
 Next i
 Rs.UpdateBatch

 Set Rs = Cnn.OpenRecordset("Select * from Addresses")
 For i = 1 To 8
   If (i Mod 2) Then
     j = j + 1 'ensure, each Person has a secondary residence ;-)
   End If
   Rs.AddNew
   Rs.Fields("ZipCode").Value = "Zip " & i
   Rs.Fields("City").Value = "City " & i
   Rs.Fields("Street").Value = "Street " & i
   Rs.Fields("Persons_ID").Value = j
 Next i
 Rs.UpdateBatch
End Sub

Olaf
MP - 14 Aug 2008 20:54 GMT
Thanks, will study that
Mark

> [simple Persons, Addresses scenario, to demonstrate
> Foreign-Keys]
>
> Olaf
MP - 22 Aug 2008 02:37 GMT
Hi Olaf,

> [simple Persons, Addresses scenario, to demonstrate
> Foreign-Keys]
[quoted text clipped - 4 lines]
>
> Private Cnn As cConnection

Great, thanks for the sample

ok, now you got me all excited about learning sqlite and replacing my
dependence on ado/adox/mdb

I have a project where i've been using some classes that wrap ado stuff
so I wanted to make a drop in replacement with minimal re-writing so I could
switch from ado to sqlite to test differences
Like in your sample, where you use IFileScan interface...with an ado version
and an sqlite version

I previously had cDatabaseController class with all the functions i needed
to create dbs, tables, execute sql, retrieve recordsets etc...which wrapped
ado/adox

Now I added IDatabaseController (based on your IFileScan and adding calls I
had in cDatabaseController)
changed cDatabaseController to Implement IDatabaseController
Then I adapted your cFileScanSqllite.cls to cDatabaseControllerSqlite and
had it also implement IDatabaseController
so in theory I can use either one...
here I have a couple questions...

I previously had a method .ExecuteSql(ssql as string)as Recordset
<that called adodb.Connection.Execute (which returns a recordset or not
depending on the sql statement)>

I notice your cConnection.Execute doesn't return a recordset...
you'd use .OpenRecordset for that...
but .OpenRecordset returns a cRecordset, not an adodb.Recordset (naturally
<g>)

So I'm thinking how to rewrite all  my client calls from other classes to
cDatabaseController.ExecuteSql that expect a Recordset...

I guess I just need to rewrite all of that so it can either deal with a
Recordset or a cRecordset interchangably
   (as long as all my calls to the oRs are shared by both Recordset and
cRecordset)
   (or create an "adapter pattern" of some kind to interface with either a
recordset or a cRecordset)

then redefine IDatabaseController.ExecuteSql to return an Object instead of
an adodb.Recordset ???

then internally cDatabaseController.ExecuteSql can call
IDatabaseController_ExecuteSql and cast the return to a Recordset
and internally cDatabaseControllerSqlite.ExecuteSql can call .OpenRecordset
and return a cRecordset

??? is that more or less the way it would be handled???

is this too confusing???

or is there any way to make an Adapter pattern that would not require
rewriting all the calls to different implementations (ado vs sqlite) that
provided different object types as returns ??? ...(thinking out loud here)
<g>
create a cMultiRecodsetObject
give it a .Return object property
dim oRs as cMultiRecodsetObject
If type of oRs.Return is cRecordset then
   ElseIf type of oRs.Return is adodb.Recordset then
...???

thanks for any light you can shine on my confusion
:-)
mark
Schmidt - 22 Aug 2008 12:41 GMT
> I have a project where i've been using some classes that
> wrap ado stuff so I wanted to make a drop in replacement
> with minimal re-writing so I could switch from ado to sqlite
> to test differences...
I would define a small IRecordset-Interface, which
includes the Rs-Methods and -Properties you want
to use in your App.

.EOF, .BOF
.MoveFirst, .MoveNext, .MoveLast
.AddNew
.UpdateBatch
including some Properties/Methods, which allow you
to work with the Field-Names and Field-Values,
without exposing the Field(s)-Object, to keep things
simple inside the IRecordset-Implementation (something
in analogy to VBs Fonts, which you can either address
with Font.Name, Font.Size or more directly one ObjLevel
above with .FontName, .FontSize).

so your IRecordset should also contain:
.FieldsCount() as Long
.FieldValue(KeyOrIndex As Variant) As Variant
.FieldName(KeyOrIndex As Variant) As Variant

this way you can avoid, exposing the Field-
Object-stuff and writing additional IFields and
IField-Interfaces + Wrappers.

Then I would split your combined Method (.ExecuteSQL )
in your current Database-Wrapper-Object into two
Methods - one for the DDL-stuff and the Write-Direction
against the DB, called .Execute (returning only a Long
with the affected Rows-Count if you want) and another one
for the Read-Direction, addressing the "Selects", returning
an IRecordset (maybe called OpenRecordset, like
in my SQLite-Wrapper). That'd make things more
"obvious" in your Client-Code and adds only one Method.

Depending on ADOJet or SQLite, the OpenRecordset-
Implementations would look something like this:

inside cADOJETDatabase (implementing IDataBase):
Private mCnn as Connection 'the internal ADO-Cnn

Private Function IDataBase_OpenRecordset(SQL as String) as IRecordset
Dim Rs as cADOJetRecordset
   'instantiate the implementation of IRecordset
   Set Rs = New cADOJetRecordset

   'now call a Friend-Method on cADOJetRecordset
   Rs.Initialize SQL, mCnn

   Set OpenRecordset = Rs 'and finally cast and return
End Function

inside the IRecordset-Implementation cADOJetRecordset:
(additionaly to the Implementation of IRecordset, which
should be trivial, the Friend-Method on the Default-
Interface of this Class should be something like...)
Private mRs as Recordset 'the internal ADO-Rs

Friend Sub Initialize(SQL as String, Cnn as Connection)
   Set mRs = New Recordset
   mRs.Open SQL, Cnn, adOpenStatic, adLockBatchOptimistic
End Sub

And in analogy you would have to code the two SQLite-
Implementations for IDatabase and IRecordset, using
the appropriate internal Objects (cConnection and
cRecordset of course).

HTH, and let me know, how you come along -
and of course how the performance looks like
in your real-world-app...

Beside that, the SQLite-Wrapper is able to give
you a real ADO-Recordset as a copy from its
internal SQLite-Recordset-Content as a
disconnected ADO-Rs, containing all the Data
of the SQLite-Recordset using the Method:
Set ADORs = SQLiteRs.GetADORsFromContent

But this ADORs is then a real disconnected one,
without any relation to the SQLite-DB or SQLite-
Connection, so .UpdateBatch on this ADORs-Copy
will of course not write its changes back into the
SQLite-DB (you would have to use the SQLite-
Command- or Cnn.Execute-Methods for the
Write-Direction against the DB).
And this Extra-Copy-Method will of course
affect the Read-Performance of SQLite - basically
this should result in ca. the same (slower) Read-Performance
as if retrieving an ADO-Recordset directly from
a JET-*.mdb, maybe in sum (including the Extra-
Copy) some 10 or 20% slower whilst retrieving
smaller Recordsets and ca. the same amount faster,
if the Recordsets become larger.

So I would go with the additional IRecordset-
Implementation for your App, but for a first try
you could use this method, to avoid the Implementation-
efforts for IRecordset, simply returning an
ADO-Recordset from bot IDataBase-Implementations.

Olaf
MP - 23 Aug 2008 03:29 GMT
>> I have a project where i've been using some classes that
>> wrap ado stuff so I wanted to make a drop in replacement
[quoted text clipped - 3 lines]
> includes the Rs-Methods and -Properties you want
> to use in your App.

snip

> So I would go with the additional IRecordset-
> Implementation for your App, but for a first try
[quoted text clipped - 3 lines]
>
> Olaf

Thanks Olaf that's great! Very understandable and I'm heading that way now.
Thanks much for clearing the cobwebs out...thats exactly what I needed
...and your explanation is very clear on how to proceed.
Thanks again

Mark
MP - 26 Aug 2008 19:25 GMT
>> So I would go with the additional IRecordset-
>> Implementation for your App

Olaf,
I'm in the process of converting (refactoring <g>) my client code to use
IRecordset and IDbController

the only question here is the efficiency difference between For Each and For
lIdx = 0 to oRs.FieldsCount -1
although I'm sure it's negligible especially in my small data sets...I was
just wondering...

previously in client code using ado objects...
<Dim oRs as adodb.Recordset, oField as Field>
'ors is a specially sorted recordset to get records in a particular order
Do Until ors.EOF
   sTest = ""
   'for each supposed to be better than index...
   For Each oField In ors.Fields
   'compose pipe delim string for postprocessing
     sTest = sTest & "|" & oField.Value
   Next oField
   'send trigger to "pseudo StateMachine"<g> for postprocessing
   StateMachine sTest
   ors.MoveNext
 Loop

'revise to use Interfaces which can be either ado or sqlite
since cRecordset doesn't have a Fields collection, and if I use your
suggestion of not exposing the field level interface
<eg: in IRecordset:
   Public Property Get FieldValue(KeyOrIndex As Variant) As Variant
   Public Property Let FieldValue(KeyOrIndex As Variant, ByVal vFieldValue
As Variant)

then I assume I need to rewrite the above something like thus:
Dim oRs as IRecordset, lIdx as long
Do Until ors.EOF
   sTest = ""
   For lIdx = 0 to oRs.FieldCount -1
     sTest = sTest & "|" & oRs.FieldValue(lidx)
   Next lIdx

   StateMachine sTest
   ors.MoveNext
 Loop

and what ever small inefficiency might be introduced by indexed loop would
be more than offset by all the sqlite efficiency over the ado approach in
any case (or so  I assume)

besides in my small case, efficiency isn't a current concern, I'm just
trying to get a functioning, maintainable, program
I can "performance tune/optimize" if I ever get it all working in the
field(office <g>)

ps
not that it affects the above but fyi, inside stateMachine sub
the strings pass through various tests and a number is calculated to apply
to that record
this gets stored in a cSortedDictionary(thanks to you know who! :-) )
Call moDStore.Add(sTest, mlMarkNumber)
and that later gets written back to the database by parsing the pipe delim
string to find the appropriate record(s) and enter the calculated number
value

thanks again, (as always)
:-)
mark
Schmidt - 26 Aug 2008 22:34 GMT
> the only question here is the efficiency difference between
> For Each and For lIdx = 0 to oRs.FieldsCount -1
[quoted text clipped - 7 lines]
>       sTest = sTest & "|" & oRs.FieldValue(lidx)
>     Next lIdx

There'd be not much of a difference measurable IMO.
For Each has the extra-overhead of initializing and jumping
into the IEnumVariant-Implementation (whilst preparing
for "For Each"), though once done will give you the
next Field-Object a little bit faster than the direct indexing.
Finally, especially if you don't enumerate huge Field-Lists,
the gains and losses of both methods will probably balance.

For example you can achieve a larger gain, if you handle the
String-Concats a bit more efficiently inside an additional
Method on your IRecordset-interface:
'Implementaion-Example:
Function GetJoinedFieldValues(Separator as String) as String
Dim i as Long
Static FArr() as String, FCount as Long
   With mRs.Fields
       If FCount <> .Count Then
           FCount = .Count
           if FCount = 0 Then Exit Function
           Redim Preserve FArr(0 To FCount - 1)
       End If

       For i = 0 to FCount - 1
           FArr(i) = .Item(i).Value
       Next i
   End With
   GetJoinedFieldValues = Join(FArr, Separator)
End Function

This way you can spare the indirect method-calls through
the IRecordset-Implementation-Layer for each single
Field in this high-frequented loop - and also the somewhat
more LOC for the faster StringArray-Concats are hidden to
your application, which codewise can become now:
Do Until oRs.EOF
   'send trigger to "pseudo StateMachine"<g> for postprocessing
   StateMachine oRs.GetJoinedFieldValues("|")
   oRs.MoveNext
Loop

At this occasion an advice regarding Null-Values inside
the Fields - ADO-Fields which contain Nulls, will give
you vbNull in the returned Variant-FieldValues, which
do not "AutoCast" and require an extra-check IsNull(),
before filling your String-Array-Idxposition.
'ADO
If IsNull(.Item.Value) Then
   FArr(i) = ""
Else
   FArr(i) = .Item(i).Value
End If

In the SQLite-Wrapper I'm not using vbNull, but
vbEmpty instead, to "mark" NullFields, returned
by the Variant-FieldValues. These cast automatically
to an empty String, so you don't need to code that
explicitely as in the ADO-Implementation.

On the other hand, if you instead want to explicitely "show"
Null-Values inside your StringConcat-return, you can use e.g.:
'SQLite
If IsEmpty(.Item.Value) Then
   FArr(i) = "<NULL>"
Else
   FArr(i) = .Item(i).Value
End If

'ADO
If IsNull(.Item.Value) Then
   FArr(i) = "<NULL>"
Else
   FArr(i) = .Item(i).Value
End If

Olaf
MP - 26 Aug 2008 23:39 GMT
Thanks again,
great advice all
Thank you
mark

>> the only question here is the efficiency difference between
>> For Each and For lIdx = 0 to oRs.FieldsCount -1
[quoted text clipped - 83 lines]
>
> Olaf
MP - 03 Sep 2008 01:50 GMT
Hi Olaf,
If I havent' worn out my welcome yet
<g>
in your sample you "set up" a command object when you create the table
 'prepare a command-object for faster inserts of new FileContents
 Set CmdContents = Cnn.CreateCommand("Insert Into FileContents(ContentType,
FileNames_ID) Values(?,?)")
then later in
Private Sub iFileScan_InsertFileContent(FileName As String, FileContent As
String)
 'that's the found content of a given File, so we check this in
 CmdContents.SetText 1, FileContent

  you set the values (i'm guessing that's what .SetText is doing)

in my 'adaptation' of your sample to my previous code
i'm creating the tables thus:
Private Sub CreatePcmkDatabase(TableName As String)
 Cnn.Execute "Create Table DwgNames (ID Integer Primary Key," & _
                                    " fldDwgName Text)"
 Cnn.Execute "Create Index Idx_DwgNames " & _
                         " On DwgNames(fldDwgName Collate NoCase)"

   Cnn.Execute "Create Table " & TableName & " (ID Integer Primary Key," &
_
                                       " ContentType  Text," & _
                                       " DwgNameKey_ID Integer, " & _
                                       " fldType Text, " & _
                                       " fldCond Text, " & _
                                       " fldCondVar Text, " & _
                                       " fldCondVarDesc Text, " & _
                                       " fldCondLbr Text, " & _
                                       " fldFinLbr Text, " & _
                                       " fldLengthMax Double, " & _
                                       " fldLengthMit Double, " & _
                                       " fldLengthAvg Double, " & _
                                       " fldHandle Text, " & _
                                       " fldPcmk Text, " & _
                                       " fldMark Text, " & _
                                       " fldOpp Text, " & _
                                       " fldBlockName Text " & _
                                       " fldSequence Text " & _
                                       " fldLayoutName Text " & _
                                       " fldCount Integer " & _
                                        ")"

 Cnn.Execute "Create Index Idx_DwgNames_ID " & _
                         " On " & TableName & "(DwgNameKey_ID)"

End Sub
so as you see i'm not "pre-loading" a command object with the "template" you
set up in your sample...
Cnn.CreateCommand("Insert Into FileContents(ContentType, FileNames_ID)
Values(?,?)")

should I be doing that???

i'm guessing that  Values(?,?) is like a place holder for the values to be
put into fields later,,, with one ? for each field to be filled in???

is that right?

previously(ado version) i had an 'insert' function that built the value
string and concated it with the insert fieldlist string then sent it to
moConnection.Execute (insertString)

now i've adapted to your sqlite version it looks like:

(ValueString is the built string...created in another function that puts the
values in order of field names)

sInsertsql = "Insert Into " & EDW_PCMKS_TABLE & " (...list of field
names...) VALUES " & ValueString

then i just do
Set CmdContents = Cnn.CreateCommand(sInsertsql)
     CmdContents.Execute

is that ok? or should i be pre-building the command in the database creation
routine like your sample?

thanks for all your help
(still trying to get everything working to adapt to your sample...getting
closer every week <g>)
mark
Schmidt - 03 Sep 2008 19:25 GMT
Your Table- and Index-Creation looks Ok for me.

[Regarding Command-Objects]
> so as you see i'm not "pre-loading" a command object
> with the "template" you set up in your sample...
Yep.

> Cnn.CreateCommand("Insert Into FileContents(ContentType,
> FileNames_ID) Values(?,?)")
>
> should I be doing that???
If you want to work in the fastest possible way, then yes.

> i'm guessing that  Values(?,?) is like a place holder for the
> values to be put into fields later,,, with one ? for each field
> to be filled in???
> is that right?
Yes, the '?' are placeholders for the Field-Values.

A standard-insert-statement would expect the Values
directly in the Insert-SQL, which you can
throw at the DB using "concrete Values":
Cnn.Execute "Insert Into FileContents(ContentType,
                    FileNames_ID) Values('A', 1)")

This would immediately insert the String-Value A and
the Long-Value 1 into the DB-Table.

But working with these explicite Insert-Statements
over Cnn.Execute is not recommended, performancewise
because the SQL-Interpreter will have to parse each
statement independently (although if you are in a loop for
multiple Record-inserts one usually only changes the values )
and typewise it is not recommended, because you will
have to perform your own String-Concats, to place
the FieldValue correctly inside that Statement-String
using their correct "encoding" (Strings within '...', or
ByteArray-Blobs would have to be Hex-Encoded, etc.).

That's why these Command-Objects exist, which only
require the SQL-Interpreter to parse and prepare the
given Command once, precreating Field-Structs for
each '?', which will only receive your changed Values for
each record - and that with TypeSafety and without
String-Concats on your side.
SQLite-Commandobjects are currently only for the
Write-Direction, and after preparing such a command-
Object you will only have to change the '?'-Parameters
in correct (OneBased) order.
CmdContents.SetText 1, FileContent
the above will place a String- or Text-Value inside
the CommandObject at '?'-Param-position 1.
CmdContents.SetInt32 2, FileID
and that will place a Long-Value appropriately at
'?'-param-position 2.
To beam this fully filled Record into the DB, you
will only have to call CmdContents.Execute.

Then proceed with the next Record-Values on the
very same (preparsed) CommandObject - all
wrapped ideally inside a transaction.

> previously(ado version) i had an 'insert' function that
> built the value string and concated it with the insert
> fieldlist string then sent it to
> moConnection.Execute (insertString)
As said, this method will work in my wrapper in
exaclty the same way too - but although it works,
it is not recommended, neither in ADO nor in SQLite.

> now i've adapted to your sqlite version it looks like:
>
[quoted text clipped - 9 lines]
>
> is that ok?
No, the command-object is wasted here - you can directly
execute such a "complete" (with explicite Values) Insert-
Statement with Cnn.Execute.

As said, for a given "insert-job" you can precreate a
fixed statement for a given table using one Command-
Object using the correct count of matching '?' in the
Values-list. Then you can forget that "InsertString"
completely and further use only the CommandObject,
which is based on that string (same thing for ADO).

If you currently have a routine, which is building your
concrete InsertValues-String, then you should pass
now the Command-Object into that Routine, together
with the needed Parameters.

e.g. your old version may be look like:
Sub PlaceValuesIn (strInsertCommand, P1 as String, P2 as Long, ...)
   strInsertCommand = strInsertCommand & "'" & P1 & "', "
   strInsertCommand = strInsertCommand & P2 & ")"
End Sub
followed by Cnn.Execute strInsertCommand

...and should now become:
Sub PlaceValuesIn (oCmd as cCommand, P1 as String, P2 as Long, ...)
 oCmd.SetText 1, P1
 oCmd.SetInt32 2, P2
End Sub
followed by oCmd.Execute

> or should i be pre-building the command in the database creation
> routine like your sample?
Why not, the Command-Objects can remain alive as long
as your Connection-Object remains alive - you can also
create them dynamically "as needed", but normally there
are not as many different "Insert, or Update-Jobs"
per Application  - in your case I suspect, there are only
2-4 (mostly one Cmd-Obj for each different Table-Type).

And BTW, there's also of course Rs.UpdateBatch, working
very similar in both, the SQLiteWrapper and ADO too
(ADO will require the right Rs-Settings (adUseClient,
adOpenStatic, adLockBatchOptimistic) and then there
are no big differences in using that feature.
Here you also have TypeChecking whilst updating or
inserting new values into the Recordset-Fields.
And as the method-name suggests, you can do more
than one change in the Rs before calling UpdateBatch -
e.g. 10 Deletes, followed by a few Fld.Updates (simply
by using Rs.Fields(xxx).Value = NewValue), followed
by 100 Inserts using (Rs.AddNew and placing new
Values in the Rs.Fields-List.).
Rs.UpdateBatch can take care of all three WriteDirection-
Types and performes them all together (after each other)
in one single UpdateBatch-Call.
In both cases (ADO and my Wrapper), these Call is
using an implicite Transaction, with AutoRollback in
case of an error.
Very comfortable that, but with less control of course -
your decision (performancewise UpdateBatch is
a slower than direct usage of the Command-Objects,
because of more internal Overhead, although I use
Command-Objects too inside the method-implementation,
but keeping the state of the Rs-internal stuctures for the
case of an eventual Rollback is costly).

So for the write direction there are three different ways
(in both "DB-Acces-Wrappers")
Cnn.BeginTrans
   Cnn.Execute "Some Direct Statement"
Cnn.CommitTrans

oCmd-Create-and-Prepare
Cnn.BeginTrans
 oCmd-FillValuesIn
 oCmd.Execute
Cnn.CommitTrans

or simply
Rs-Do-a-lot-of-changes
Rs.UpdateBatch

HTH

Olaf
MP - 03 Sep 2008 20:19 GMT
Fantastic
Thanks for the very clear explanation...
back to the drawing board
:-)
mark

> Your Table- and Index-Creation looks Ok for me.
MP - 04 Sep 2008 01:36 GMT
> Your Table- and Index-Creation looks Ok for me.
>
>> should I be doing that???
> If you want to work in the fastest possible way, then yes.

ok

just to confirm, given the table structure
'TABLE STRUCTURE
'  bSuccess = Cnn.Execute("Create Table " & TableName & " (ID Integer
Primary Key," & _
'                                        " DwgNameKey_ID Integer, " & _
'                                        " fldType Text, " & _
/etc etc

and given the command 'setup' thus
Set CmdContents = Cnn.CreateCommand("Insert into " & TableName &
"(DwgNameKey_ID, fldType, etc ....) Values(?,?,?, etc...)")

then ID is field 1
DwgNameKey_ID is field 2
fldType is field 3
is that right?
(in whatever order the table is created in...)

then a command object fill in the values function would be like:

'don't put any thing for field 1 since it's primary key it's going to be
automatically created right????
'except i don't see any settings that tell it to be unique or
autonumber...i'm just guessing that's a default in sqlite????

 oCommand.SetInt32 , 2, mlDwgNameKey
 oCommand.SetText , 3, sType
   etc etc

is that right?

or is ID possibly field number 0 and that's how it would never get filled in
manually by code since the field indexes start at 1???

thanks again
mark
Schmidt - 04 Sep 2008 01:55 GMT
> just to confirm, given the table structure
>  'TABLE STRUCTURE
[quoted text clipped - 13 lines]
> is that right?
> (in whatever order the table is created in...)
No, it is not the FieldOrder of your CreateTable-statement
which counts here, but the FieldOrder of the InsertStatement
(the FieldNames you give within the first parentheses).
And the count of the ?-Placeholders has to match not
the FieldCount of your Table-Definition, but the FieldCount
of your Command-Statement.
So - DwgNameKey_ID is Index 1 , fldType is 2, etc.

> 'don't put any thing for field 1 since it's primary key it's
> going to be automatically created right????
Right in principle, but we achieve that not by starting at
index 2, but by leaving out the ID-Field in our Insert-
Command-Description completely beforehand.
As said, Index 1 points to DwgNameKey_ID,...

Olaf
MP - 04 Sep 2008 18:28 GMT
>> just to confirm, given the table structure
>>  'TABLE STRUCTURE
[quoted text clipped - 29 lines]
>
> Olaf

duh!
of course...
sorry for the dumb question
:-)
mark
MP - 13 Sep 2008 02:43 GMT
Hi Olaf,
   if you're still 'watching' :-)

   ...so i've gotten to where I can create a db, write (maybe) records,
open existing db, read records....

   to open I used the .Open method on the connection object...
   oConnection.Open dbName

   how do i close the connection when i'm done?
   I don't see a oConnection.Close method
   do I just set oConnection = Nothing to close connection and release all
objects???

for example, this was just to look at some temp dbs created during
debugging....if they're empty i wanted to delete them
Sub ReadDb(dbName as String, TableName as String, lRecords as Long)
 Set modb = New cDBControllerSQLite
 modb.Connect
 bSuccess = modb.OpenDB(dbName)
 If bSuccess Then
     LogEntry "Opened db"
   Else
     LogErr methodFailed, ProcName, "OpenDb"
 End If

 modb.DumpSQLSelect "Select * From " & TableName, lRecords

'this was just to clean up some temp original dbs created during debug
practise...
 If lRecords = 0 Then
   If MsgBox("Delete empty database?", vbYesNo Or vbDefaultButton2, "EMPTY
DATABASE") = vbYes Then
       'does this close db and release db file for deletion??
     Set modb = Nothing
     Kill dbName
   End If
 End If
End Sub
Schmidt - 13 Sep 2008 03:13 GMT
> if you're still 'watching' :-)
Apparently... ;-)

>   ...so i've gotten to where I can create a db, write
> (maybe) records, open existing db, read records....
As said, would be interested, to hear from you regarding
some timing-results - or generally, how it compares with
JET in your real App-scenario.

>  to open I used the .Open method on the connection object...
>  oConnection.Open dbName
Yep.

> how do i close the connection when i'm done?
> I don't see a oConnection.Close method
> do I just set oConnection = Nothing to close
> connection and release all objects???
Yes, that was the intention in my wrapper (tried
to avoid all these Close-Methods, that we see in
ADO-Objects - no circular references in my
Wrapper-Objects-Model, ergo not that much
Code to write for cleanup).
Look at the comments in my iFileScan-Demo,
the SQLite-Implementation which wraps the
Cnn and Rs-Objects does not need a
Class_Terminate at all, since all the internal
SQLite-Wrapper-Objects die automatically
when they get Out-Of-Scope (when the implementing
Parent-Class terminates).

BTW, just read your other post in m.p.v.g.d. regarding
ADOX-based Table-Creation vs DDL-Executes -
both methods don't really differ much in performance.
If you look at the iFileScan-Sources, there I use
Table- and Index-DDL for both versions (SQLite and
ADO/Jet) - better to maintain IMO, because you
can create/save your Schema-Definitions (all your
DDL-Statements) also into your File of choice or
inside another DB(-Table) or whereever.
DDL is available on all DBs I know and does not
differ all that much (mostly only on the Type-Def-Syntax),
but in principle the Statements have the same structure
and are easy to formulate after you become somewhat
familiar with them.

Olaf
MP - 13 Sep 2008 05:13 GMT
Thanks for the addn'l info

>> if you're still 'watching' :-)
> Apparently... ;-)
[quoted text clipped - 4 lines]
> some timing-results - or generally, how it compares with
> JET in your real App-scenario.

I'm still struggling to get it inserted into the actual app.
Due to my inexperience coding and limited time to work on this particular
project it's taking a long time.
I'm having to disentangle all the ways I was working before where it was
tied to the ado based approach.
Trying to get it so I can literally plug in the sql object and the ado
object so I could time the comparison.

I have no doubt your sql version will be much faster than the ado version
for that portion of the process

Getting the info from Acad takes a huge amount of time so I doubt if i'll
see a big diff in overall time from the two for now
(im building a dll that will be run in acad's process when it's
compiled...then the time will be much faster)
for working on uncompiled (referencing the vbp instead of the dll) it's
wayyyy slow

I have an object that harvests and stores the information from an acad
object from the acad documents.
So I collect a few hundred of these objects each with it's own packet of
information.
The way I was doing it each object wrote it's info to the db one at a
time(actually made the dbController object write it's info)
your batch update idea would probably be better if I make a way to collect
all the info into some form and then write it all at one time...not sure yet
how to do that.

Then I either
version one
send that object to the database object(sql or ado) ...
'make the database object responsible to "read" a "datacontaining object"

OR...
version two
I send the database object to that object...
'make the "data containing object' responsible to put it's information into
the database

I'm not sure which is the "correct" approach
previously I was sending the db object to the "datacontaining" object  (to
get the info written to the db)

'assign responsibility to "DataContainingObject" to put it's own information
in database
'in cBlockData.cls
Public Function ToDatabase(oDatabaseController As cDatabaseController, _
                                     bCancel As Boolean, _
                                     bDebug As Boolean) As Boolean
'compose value string
sInsertValues = EPADataValueString(mlDwgNameKey)

'send to db
    sInsertSql = "Insert Into " & oDatabaseController.TableName & "
(fldType, fldCond, fldCondVar, fldCondVarDesc, fldCondLbr, fldFinLbr,
fldLengthMax, fldLengthMit, fldLengthAvg,  fldHandle, fldDwgNameKey,
fldBlockName, fldSequence, fldLayoutName, fldCount) "
    sInsertSql = sInsertSql & "VALUES " & sInsertValues

   ToDatabase = oDatabaseController.ExecuteSql(sInsertSql)
End Function
'-------------------------

so now i'm redesigning the whole project to work with the interfaces you
suggested
the above couples the BlockData object to a cDatabaseController object (ado
wrapper)

now i'm rewriting everything to refereince and IDatabaseController etc...
but i'm not sure if i want to couple the BlockData object to the database
object or the other way around....

I could just create a reference in the database object to a "source of data"
object to "read"

so anyway all that confusion and timeconsuming rewriting and debugging is
why I don't have any progress to report as yet.
:-)

Thanks for all your help and patience on my database education :-)
I just wish there was some way I could repay all the kindness you've shown
me over these years!
mark
MP - 16 Sep 2008 20:17 GMT
>> if you're still 'watching' :-)
> Apparently... ;-)

Hi Olaf,
   still contemplating how to modify my previous implementation to
incorporate your sqlite wrapper.
   I was wondering if you'd have any insights to share how you'd go about
it....
   my scenario is this
   I have a collection of documents which are a collection of objects
containing data
in my previous ado approach I was getting a "Values ..." string from each
object and passing that to an "Insert " statement on the connection object

basic pseudocode:
   in cDocuments.cls
   Sub ToDatabase(oDbController as IDBController)
       For Each oDoc in mcolDocuments
           oDoc.ToDatabase oDbController
       Next
   End Sub

   in cDocument.cls
   Sub ToDatabase(oDbController as IDBController)
       For Each oBlockData in mcolDataObjects
           oBlockData .ToDatabase oDbController
       Next
   End Sub

   in oBlockData.cls
   Sub ToDatabase(oDbController as IDBController)
       'previous ado Insert string implementation
       'collect values from internal variables and compose insert sql
statement
         ValueString = " ( '" & sType _
 & "', '" & sCond _
 & "', '" & sCondVar _
 & "', '" & sCondVarDesc _
 ..etc ... & "')"

'---------------------------------- < > -----------------------------
'that string gets passed to moDBController sql insert line like the
following
'  sSql = "INSERT INTO " & msTableName & _
         'list of field names in same order as list of values to be input
         '" (fldType, fldCond, fldCondVar, fldCondVarDesc, ...etc...,)
         'VALUES " & ValueString
'---------------------------------- < > -----------------------------
       oDbController .Execute sSql
   End Sub

That was probaby the wrong way to do it from the start since it calls an
execute for each record(instead of doing a batch...which i've never done
yet)

now since the sqlite implementation takes a command object and uses .SetText
etc I need to modify how i'm composing the info
and probably want to collect them all into one group then do one .Execute (i
presume)

but somehow i'd have to set it up so either ado or sqlite would work...so I
could give you those timings you were curious about (me too)
the sqlite version wants something like

...inside cBlockData.ToDatabase...or somewhere???
Each oBlockData object needs to do something like this...or somehow give
it's collection of data to some object that passes it to IDbController
'new sqlite command object type implemetation...
oCommand.SetInt32 1, mlDwgNameKey
 oCommand.SetText 2, sType
 oCommand.SetText 3, sCond
 oCommand.SetText 4, sCondVar
 oCommand.SetText 5, sCondVarDesc
 oCommand.SetText 6, sCondLbr
 oCommand.SetText 7, sFinLbr
 oCommand.SetDouble 8, dLengthMax
 oCommand.SetDouble 9, dLengthMit
 oCommand.SetDouble 10, dLengthAvg
 oCommand.SetText 11, msHandle
 oCommand.SetText 12, msName
 oCommand.SetText 13, sSequence
 oCommand.SetText 14, msLayoutName
 oCommand.SetInt32 15, lCount
'either execute once for each object
'  oCommand.Execute
'....or
'  maybe I just collect all Command objects into a collection for that
document
'moDocument.CommandsCollection.Add oCommand

'instead of executing once for each object
'then collect all documents command collections into the
Documents.CommandCollection....
'then somehow cSqliteImplementation.ExecuteCommandCollectionBatch
(colAllCommandsAllDocs)
...or something like that????

but the ado version still uses Conn.Execute "Insert Into ....etc "
so I need some kind of (adapter pattern???) object that can either build an
insert string for ado, or fill in command objects for sqlite...

...is that more or less the direction I should be looking in???

thanks
obviously still confused :-)
mark
Schmidt - 17 Sep 2008 12:57 GMT
> I have a collection of documents which are a collection of objects
> containing data
[quoted text clipped - 15 lines]
>         Next
>     End Sub

First, you should wrap this DB-Inserts with a transaction -
it is your decision, if you want to wrap the "whole story"
oDBController.BeginTrans
  For Each oDoc in mcolDocuments
     oDoc.ToDatabase oDbController
  Next
oDBController.CommitTrans

or if you want to have Transactions at the Document-Level:
For Each oDoc in mcolDocuments
   oDBController.BeginTrans
       oDoc.ToDatabase oDbController
  oDBController.CommitTrans
Next

Of course you should also place an appropriate
oDBController.RollbackTrans in case of an error.

>     in oBlockData.cls
>     Sub ToDatabase(oDbController as IDBController)
[quoted text clipped - 73 lines]
> obviously still confused :-)
> mark
MP - 17 Sep 2008 14:25 GMT
>> I have a collection of documents which are a collection of objects
>> containing data
[quoted text clipped - 6 lines]
>   Next
> oDBController.CommitTrans

ah! that's what I was missing...I saw it but just didnt' click...

> Of course you should also place an appropriate
> oDBController.RollbackTrans in case of an error.

So the individual command.Executes (at the individual blockobject level)
won't take place till the .CommitTrans?
so there's just one write after all?

in that case there'd be no difference between (version 2 below) collecting
all commands and running them at one time
versus
running each command each time a blockobject is encountered (version 1
below)

?
is that right?

>>     in oBlockData.cls
>>     Sub ToDatabase(oDbController as IDBController)
>> 'new sqlite command object type implemetation...
>> oCommand.SetInt32 1, mlDwgNameKey
       etc...

version 1
>> 'either execute once for each object
>> '  oCommand.Execute
>> '....or

version 2
>> 'moDocument.CommandsCollection.Add oCommand
'and eventually...ForEach ocommand in collection ocommand.Execute

and then in BlockData.ToDatabase would I just do a TypeOf check on
oDbController0 and have two branches of implementation for sqlite versus
ado?

thanks again
mark
Schmidt - 17 Sep 2008 16:33 GMT
Sorry first, that post was send unintentionally
and was not completed.

[Transactions]
> > Of course you should also place an appropriate
> > oDBController.RollbackTrans in case of an error.
>
> So the individual command.Executes (at the individual
> blockobject level) won't take place till the .CommitTrans?
> so there's just one write after all?
More or less, yes.
Speaking for SQLite - all the single Executes are stored
in a journal-file first (when a transaction is open) and
in case of a commit, all the new pages in the journal are
written into the real DB-File internally (in fast page-mode).
Without "transaction-wrapping" SQLite performs much
slower on the inserts, because then "direct-DB-Writes"
will by default be done in secure-mode for each single
Cmd.Execute (single-record-writes directly to disk, and
not against the Write-Cache of the OS, as in contrast
e.g. JET does by default).

...
What was missing in the previous post is the following:

Regarding Command-Abstraction...
Simply define an iCommand-Interface in the same way
as you already done that for iRecordset.

To make it a bit easier,  I've already coded that in this
Demo: www.datenhaus.de/Downloads/DBController.zip
This includes abstracting interfaces for iDBController
iCmdInsert and iRecordset - and the appropriate
implementations for ADO/JET and also for SQLite.

The Demo-Form then shows, how to use these
interface-implementations in a generic way, writing
some 5000 Records, wrapped in a transaction -
and reading these Records back into an iRecordset,
followed by a Rs-dump.

That should help you IMO, to bring something similar
into your own App.

Olaf
MP - 17 Sep 2008 17:01 GMT
> Sorry first, that post was send unintentionally
> and was not completed.

snip

> That should help you IMO, to bring something similar
> into your own App.
>
> Olaf

once again, thank you very much
will study this and hope to make some progress soon
mark
MP - 17 Sep 2008 23:12 GMT
snip

> To make it a bit easier,  I've already coded that in this
> Demo: www.datenhaus.de/Downloads/DBController.zip
[quoted text clipped - 12 lines]
>
> Olaf

Man! You are nothing short of amazing!!!
:-))))))))))))))
You make it look so Simple!!!!!
:-))))))
Thank you so much!!!!!!!
mark
Schmidt - 18 Sep 2008 18:51 GMT
> You make it look so Simple!!!!!
Nahh, it *is* simple if one becomes a bit familiar with
the VB6 interfaces- and implementation-concept (and
of course a bit of experience with the ADO- or
SQLite-Command-Object-stuff ...  agreed).

Just one small advise regarding the used cSortedDictionaries
inside the Cmd-Object-Implementations - please make sure,
that you switch the .StringCompareMode of all the dictionaries
to TextCompare explicitely (after instantiation this defaults
to BinaryCompare, which can cause some problems with
the FieldNames, if these are not written exactly the same).
So, sorry I simply forgot that in the example.

Olaf
MP - 18 Sep 2008 22:46 GMT
>> You make it look so Simple!!!!!
> Nahh, it *is* simple if one becomes a bit familiar with
> the VB6 interfaces- and implementation-concept (and
> of course a bit of experience with the ADO- or
> SQLite-Command-Object-stuff ...  agreed).

> Just one small advise regarding the used cSortedDictionaries
> inside the Cmd-Object-Implementations - please make sure,
[quoted text clipped - 3 lines]
> the FieldNames, if these are not written exactly the same).
> So, sorry I simply forgot that in the example.

thanks,  will keep that in mind...

I'm thinking of using your cleverly elegant FieldType-DescriptionPairs to
send to both create table and commands

Private Function GetFieldTypes(oDB As iDBController)as cSortedDictionary
   Dim CmdFieldTypes As cSortedDictionary
 'Create FieldType-DescriptionPairs for our InsertCommand and also creating
schema?
 Set CmdFieldTypes = New cSortedDictionary

'list of fields and types
 CmdFieldTypes.Add "Txt", enmFieldTypes.fldText
...etc

Set GetFieldTypes = CmdFieldTypes
End Function

Then ...
Dim CmdFieldTypes As cSortedDictionary, Cmd As iCmdInsert
   Set CmdFieldTypes = GetFieldTypes
   Set Db = oDB.CreateSchema("dbName", CmdFieldTypes)
   Set Cmd = oDB.CreateInsertCmd("cmdName", CmdFieldTypes)

object to send to both create table and create cmdInsert
using your elegant structure in

Private Sub CreateSchema(FieldList as cSortedDictionary)

 Cnn.Execute "Create Table Test (ID Integer Primary Key," & _
   'parse Field list for name and types like you did in createCmd
function...
                               " Txt  Text," & _
                              ...etc

End Sub

does that make sense?

Thanks again,
Mark
Schmidt - 19 Sep 2008 00:50 GMT
> I'm thinking of using your cleverly elegant FieldType-
> DescriptionPairs to send to both create table and commands
For commands it is not important, in what order the
Field-Parameters sit in the Commands Insert-Statement,
but remember that we have a *sorted* Dictionary here -
so if you want to use it also for Table-Creation, then
I would at least keep the ID-Column "out of the game"
(what you probably want to do too) - but the order
of the rest of your Table-Fields is then not determined.
That's probably not such a big problem, but sometimes
you want to do a simple "Select * from Table" and expect
a certain Field-Order in this case.
If you always want to give the FieldOrder explicitely
in your Rs-Selects, then this sounds like a good idea.

I've just build this into a new version of the DBController-
Demo (together with the TextCompareMode-fixes for the
Dictionaries) - same link:.
www.datenhaus.de/Downloads/DBController.zip

So, now you can create your Command-Objects
simply by specifying the TableName - the relevant
TypeInfo-Dictionaries are already internally stored
(in the Controller) whilst creating the DB-Tables from
appropriate FieldType-Pairs (each FieldType-
Dictionary is saved in an additional Dict with the
Tablename as Key).

Olaf
MP - 19 Sep 2008 03:13 GMT
>> I'm thinking of using your cleverly elegant FieldType-
>> DescriptionPairs to send to both create table and commands
[quoted text clipped - 3 lines]
> so if you want to use it also for Table-Creation, then
> I would at least keep the ID-Column "out of the game"

Right

> (what you probably want to do too) - but the order
> of the rest of your Table-Fields is then not determined.

not sure I get what you mean...I didn't think there was any particular
ordering to fields except  when you ask for a rs and then select whatever
fields you want in whatever order you want?

> That's probably not such a big problem, but sometimes
> you want to do a simple "Select * from Table" and expect
> a certain Field-Order in this case.

so that would come out in the order the table was created in?

> If you always want to give the FieldOrder explicitely
> in your Rs-Selects, then this sounds like a good idea.

I wasn't so much caring about the order, just that in my case I have a lot
of fields and am always having to page back and forth to remember their
exactnames/positions in the select statements etc
just thought it would simplify my poor memory if I could build the
'structure' once then use it everywhere
this is a very unusual(i'm sure) use of a database where I really only have
a couple selects for a special purpose...not a general usage db where users
are looking up varieties of info and adding/deleting records...it's just a
one time use db to record/sort/identify/and label some parts read from a
list of documents(acad dwgs)

so the idea of a fixed list of fields for both table creation and command
creation is probably not a 'general use' concept, but seems handy in this
case.

> I've just build this into a new version of the DBController-
> Demo (together with the TextCompareMode-fixes for the
[quoted text clipped - 10 lines]
>
> Olaf

You are Too Much! as they say in the States :-)
Thanks again
mark
Schmidt - 19 Sep 2008 04:33 GMT
[Dictionary-sorted Field-Order for Create Table Statements]
> not sure I get what you mean...I didn't think there was
> any particular ordering to fields except  when you ask for
> a rs and then select whatever fields you want in whatever
> order you want?
Yep, if you explicitely give the FieldNames in a Select,
then the 'Create Table order' is not important (this has
also no performance-impact).

> > That's probably not such a big problem, but sometimes
> > you want to do a simple "Select * from Table" and expect
> > a certain Field-Order in this case.
>
> so that would come out in the order the table was created in?
Yes, the * will give you that order.
But that's also not a big problem, since you can always
define a View with the needed Field-SubSet and
Field-Order you want.
And then simply perform the * - based Select against
that ViewName.

> > If you always want to give the FieldOrder explicitely
> > in your Rs-Selects, then this sounds like a good idea.
[quoted text clipped - 4 lines]
> just thought it would simplify my poor memory if I could build the
> 'structure' once then use it everywhere
As said, using Views you can predefine your Selects once
and in your App later use a simple "Select * from ViewName ..."
instead directly working with the tables, managing the
different FieldLists explicitely in your Client-Code.
Views can be created very easy:
Cnn.Execute "Create View ViewName As " & _
          "Select F1, F3, F7, F23... From Table"
You can also predefine Where-Conditions and Joins
inside the Views Select-part - very handy all that for
more generic Client-Code (e.g. Reports which are
based on View-Definitions - no need to change
Client-Code, just to include an additional Column
into the "Table-Printout" - a View-Redefinition with
that additional Column should be enough).

> so the idea of a fixed list of fields for both table creation and
> command creation is probably not a 'general use' concept,
> but seems handy in this case.
Yep.

Olaf
MP - 21 Sep 2008 02:12 GMT
snip

>> so the idea of a fixed list of fields for both table creation and
>> command creation is probably not a 'general use' concept,
>> but seems handy in this case.
> Yep.
>
> Olaf

Thanks
I'll be working this wkend to study and incorporate the new pattern.
Really appreciate your taking all this time to "baby step" me through this
process.
You are really patient...
I know I'm not too quick on the up take...it's very generous of you not to
just blow me off for being so slow .
thanks
Mark
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2009 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.