MS-Office-Forum

Zurück   MS-Office-Forum > Microsoft Access & Datenbanken > Microsoft Access
Registrieren Forum Hilfe Alle Foren als gelesen markieren

Banner und Co.

Antworten
Ads
Themen-Optionen Ansicht
Alt 15.05.2019, 22:30   #1
Michael O.
MOF User
MOF User
Standard Acc2013 - BackEnd-DB updaten per VBA

Hallo Forum,

die Anwendung ist draußen beim Kunden. Nun gibt es neue Anforderungen, die Änderungen am Datenmodell erfordern. Natürlich ist die Anwendung in FE und BE getrennt. Ich möchte nun die bei einem Update erforderlichen BE-Updates per VBA-Script vom FE aus durchführen lassen. Die Vorteile liegen auf der Hand:
  • Ich muss nicht bei jedem Update die BE-Dateien aller Kunden einsammeln und manuell ändern.
    Ich kann die BE-Updates ausführlich testen und damit Fehler bei der manuellen Anpassung des BE ausschließen.

Per VBA kann ich ganze Tabellen hinzufügen oder vorhandenen Tabellen Felder hinzufügen:
Code:

Dim var_BEpfad As String

Dim db_BE As Database
Dim obj_table As DAO.TableDef
Dim obj_field As DAO.Field
Dim obj_index As DAO.Index

' BE öffnen
var_BEpfad = mod_db.db_BE_path
Set db_BE = DBEngine.Workspaces(0).OpenDatabase(var_BEpfad)

' Neue Tabellen anlegenSet obj_table = db_BE.CreateTableDef("Version_BE")
With obj_table
    Set obj_field = .CreateField("Version", dbLong)
    .Fields.Append obj_field
    Set obj_field = .CreateField("SubVersion", dbLong)
    .Fields.Append obj_field
End With
' Neue Tabelle in der Datenbank erstellen
db_BE.TableDefs.Append obj_table

' Neue Attribute anlegen
Set obj_table = db_BE.TableDefs("REZEPT")
With obj_table
    Set obj_field = .CreateField("Zugabe", dbBoolean)
    .Fields.Append obj_field
End With

Set obj_table = db_BE.TableDefs("ZUTAT")
With obj_table
    Set obj_field = .CreateField("Bio", dbBoolean)
    .Fields.Append obj_field
End With
Wie wir sehen bietet die CreateField-Methode nur die Möglichkeit, den Datentyp zu definieren. Ich möchte aber alle Eigenschaften des Feldes setzen können. Bei einem Ja/Nein-Feld ist zum Beispiel der Standardwert interessant, bei anderen Datentypen "Eingabe erforderlich" oder "Anzahl Dezimalstellen" etc.. Bis hin zur Beschreibung.

Wie kann ich alle Eigenschaften eines Datenbank-Feldes per VBA-Code setzen?

Herzlichen Dank für alle hilfreichen Anregungen!

Viele Grüße
Michael


PS:
Früher habe ich u. a. mit Oracle als DBMS gearbeitet. Im Entwicklungsprozess haben wir unserem DBA alle Änderungen an der Datenbank-Struktur als DDL-Scripts zur Verfügung gestellt. Wenn ich mich richtig erinnere konnte Oracle diese Scripts generieren. So war man bei einem neuen Deployment immer auf der sicheren Seite.
Da MS Access dieser Anforderung - bei aller Wertschätzung - nicht gerecht wird und ich auch noch kein Tool gefunden habe, das eine Differenz-Analyse zweier Datenbank-Stände durchführt, ist wohl ein wenig Handarbeit angesagt. Das Objekt-Modell von Access bietet eigentlich auch alle Möglichkeiten - wenn man nur alle Möglichkeiten verstehen würde....

Geändert von Michael O. (15.05.2019 um 22:42 Uhr). Grund: Hinweis hinzugefügt
Michael O. ist offline  
verlinken auf Del.icio.us Diese Seite zu Mister Wong hinzufügen
Antworten Auf Beitrag antworten
Alt 15.05.2019, 23:02   #2
knobbi38
Neuer Benutzer
Neuer Benutzer
Standard

Hallo,

die Eigenschaften sollten alle in der DAO.Field.Properties-Auflistung stecken. Die Properties selber existieren in Abhängigkeit von den gemachten Einstellungen. Zum Bespiel existiert eine Property "Description" erst, nachdem im Entwurfsmodus eine Beschreibung eingegeben worden ist.

Hinweis: je nach Anwendung, kann es auch benutzerdefinierte Properties geben!

Ulrich

Geändert von knobbi38 (15.05.2019 um 23:11 Uhr).
knobbi38 ist offline  
verlinken auf Del.icio.us Diese Seite zu Mister Wong hinzufügen
Antworten Auf Beitrag antworten
Alt 16.05.2019, 22:18   #3
Michael O.
Threadstarter Threadstarter
MOF User
MOF User
Standard

Tatsächlich ist es ein bisschen komplizierter als zunächst angenommen. Nicht alle Properties stehen sofort zur Verfügung und müssen deshalb im Code hinzugefügt werden. Ich habe das in meinem Code bereits berücksicht.


Code:

Private Function mod_upd_701() As Boolean
' ---------------------------------------------------------------------------------------
' MOD-UPDATE-SERVICE
'   Update von Version 7.00 auf Version 7.01
' ----------------------------------------------------------------------------------------

Dim var_BEpfad As String

Dim db_BE As Database
Dim rs_version As Recordset
Dim obj_table As TableDef
Dim obj_field As Field
Dim obj_index As Index
Dim obj_prop As Property

' Prüfen, ob andere Benutzer mit der BE arbeiten
' ... muss noch definiert werden...

On Error GoTo ErrHandler

' Verbindung zum BE trennen (Verknüpfungen)
' ... erforderlich? Offensichtlich nicht (Stand: 15.05.2019)

' BE öffnen
var_BEpfad = mod_db.db_BE_path
Set db_BE = DBEngine.Workspaces(0).OpenDatabase(var_BEpfad)

' BE initialisieren, um Updates wiederholen zu können
On Error Resume Next
db_BE.TableDefs.Delete "Version_BE"
db_BE.TableDefs("REZEPT").Fields.Delete "Zugabe"
db_BE.TableDefs("ZUTAT").Fields.Delete "Bio"
db_BE.TableDefs.Refresh
On Error GoTo ErrHandler

' Neue Tabellen mit Attributen anlegen
Set obj_table = db_BE.CreateTableDef("Version_BE")
With obj_table
    Set obj_field = .CreateField("Version", dbLong)
    obj_field.Required = True
    obj_field.DefaultValue = 0
    .Fields.Append obj_field
    Set obj_field = .CreateField("SubVersion", dbLong)
    obj_field.Required = True
    obj_field.DefaultValue = 0
    .Fields.Append obj_field
End With
' Neue Tabelle in der Datenbank erstellen
db_BE.TableDefs.Append obj_table
db_BE.TableDefs.Refresh

' Neue Attribute anlegen        ?????? DefaultValue Nein statt False
Set obj_table = db_BE.TableDefs("REZEPT")
With obj_table
    Set obj_field = .CreateField("Zugabe", dbBoolean)
    obj_field.OrdinalPosition = 3
    obj_field.DefaultValue = False        ' das ist falsch, es muss auf "Nein" gesetzt werden. Aber wie? Oder fehlt hier nur das Format?
    .Fields.Append obj_field
    db_BE.TableDefs.Refresh
    
    Set obj_prop = obj_field.CreateProperty("DisplayControl", dbInteger, acCheckBox)
    db_BE.TableDefs("REZEPT").Fields("Zugabe").Properties.Append obj_prop

    Set obj_prop = obj_field.CreateProperty("Description", dbText, CVar("Zugabe: Rezept, das anderen Rezepten hinzugefügt werden kann. Verändert nicht Bilanz- und Temperatur-Berechnung des Rezeptes."))
    db_BE.TableDefs("REZEPT").Fields("Zugabe").Properties.Append obj_prop
End With
db_BE.TableDefs.Refresh

Set obj_table = db_BE.TableDefs("ZUTAT")
With obj_table
    Set obj_field = .CreateField("Bio", dbBoolean)
    obj_field.OrdinalPosition = 4
    obj_field.DefaultValue = False  ' das ist falsch, es muss auf "Nein" gesetzt werden. Aber wie?
    .Fields.Append obj_field
    db_BE.TableDefs.Refresh
    
    Set obj_prop = obj_field.CreateProperty("DisplayControl", dbInteger, acCheckBox)
    db_BE.TableDefs("ZUTAT").Fields("Bio").Properties.Append obj_prop

    Set obj_prop = obj_field.CreateProperty("Description", dbText, CVar("Bio (aus ökologischem Anbau)"))
    db_BE.TableDefs("ZUTAT").Fields("Bio").Properties.Append obj_prop
End With
db_BE.TableDefs.Refresh

' Daten in neue Tabellen einfügen
Set rs_version = db_BE.OpenRecordset("Version_BE")
With rs_version
    .AddNew
    !Version = 7
    !Subversion = 1
    .Update
End With


' Verbindung zum BE neu aufbauen, Verknüpfungen neu erstellen
' ... erforderlich? Offensichtlich nicht (Stand: 15.05.2019)


' Aufräumen, Speicher freigeben
If Not obj_table Is Nothing Then
    Set obj_table = Nothing
End If
If Not obj_field Is Nothing Then
    Set obj_field = Nothing
End If
If Not obj_index Is Nothing Then
    Set obj_index = Nothing
End If
If Not obj_prop Is Nothing Then
    Set obj_prop = Nothing
End If

db_BE.close
Set db_BE = Nothing


' Ergebnis der Update-Aktion setzen
mod_upd_701 = True

' Fehlerbehandlung vermeiden
Exit Function


' -------------------------------------------------------------------------------------------------------------
' Fehlerbehandlung
' -------------------------------------------------------------------------------------------------------------
ErrHandler:
mod_sys.ErrHandler_Standard Err.Number, Err.Description, "Eisprogramm"
mod_upd_701 = False

End Function
Der Aufwand ist also gigantisch und die Fehleranfälligkeit groß, um nur ein einziges Feld zu einer Tabelle hinzuzufügen. Und dabei gelingt es mir noch nicht einmal, den DefaultValue des Ja/Nein-Feldes korrekt zu setzen!

Was wären nun die Alternativen bei unveränderter Ausgangslage?
- Die neue Tabellen-Struktur in die Datenbank kopieren (TransferDatabase) und anschließend die Daten aus der alten in die neue Struktur übertragen? Aber was passiert dann mit den AutoWert-Feldern? Wenn diese neu vergeben werden stimmen die Relationen nicht mehr!

Wie auch immer:
wer steht vor einer ähnlichen Herausforderung und hat eine Idee, wie wir damit umgehen können?

Viele Grüße
Michael
Michael O. ist offline  
verlinken auf Del.icio.us Diese Seite zu Mister Wong hinzufügen
Antworten Auf Beitrag antworten
Alt 16.05.2019, 23:19   #4
knobbi38
Neuer Benutzer
Neuer Benutzer
Standard

Hallo Michael,

Zitat:

den DefaultValue des Ja/Nein-Feldes korrekt zu setzen

Bei den Properties muß du schon auf den Property.Type achten. Für das Ja/Nein Feld gilt: Properties("DefaultValue").Typ = (12) - dbMemo, d.h. die Value Eigenschaft ist vom Datentyp Variant/String! Demnach muß der Wert "false" als String übergeben werden und das ist in der lokalen Variante entsprechend "Falsch".
Wenn man sich nicht sicher ist, gibt es die Funktion Application.BuildCriteria(), mit der man das im Direktfenster auch ausprobieren kann:
Code:

? BuildCriteria("DefaultValue",dbMemo,false)
DefaultValue="Falsch"
? BuildCriteria("DefaultValue",dbMemo,true)
DefaultValue="Wahr"
Das Setzen/Erstellen der Properties kann auch mit einer eigenen Funktion gemacht werden, um sich die Arbeit etwas zu erleichtern.

Grundsätzlich würde ich bei einem Update zwischen einem Patch und einer Migration unterscheiden. Beim Patch einfach das bestehende Schema verändern und bei einer Migration tatsächlich eine Datenübernahme machen.
Ob TransferDatabase dafür geeignet ist, müßte man prüfen. Aus dem stehgreif weiß ich jetzt nicht, wie sich das dann mit den Relationen und den Indizes verhält. AutowertFelder kann man übernehmen.

Vielleicht gibt es einen Sinn, eine Patch-Steuerdatei zu entwickeln, in der alle Änderungen als Kommandos erfasst und dann von deinem Tool abgearbeit werden.

Gruß Ulrich
knobbi38 ist offline  
verlinken auf Del.icio.us Diese Seite zu Mister Wong hinzufügen
Antworten Auf Beitrag antworten
Alt 17.05.2019, 17:00   #5
Beaker s.a.
MOF Koryphäe
MOF Koryphäe
Standard

Hallo Michael,
Mag sein, dass ich was übersehen habe, aber wenn du ein Feld löscht
Code:

db_BE.TableDefs("REZEPT").Fields.Delete "Zugabe"
gehen auch alle Inhalte verloren. Dann erstellst du das Feld neu und
setzt den Standardwert, der nur bei neuen DS eingetragen wird.
Es muss doch Ziel sein, die erfassten Daten zu erhalten.
Ich würde das Update mit einer Anfügeabfrage in eine leere Tabelle mit
der neuen Struktur realisieren. Danach die alte Tabelle löschen und die
neue umbenennen.
gruss ekkehard

__________________

--
S.M.I.²L.E.
Beaker s.a. ist offline  
verlinken auf Del.icio.us Diese Seite zu Mister Wong hinzufügen
Antworten Auf Beitrag antworten
Alt 19.05.2019, 12:26   #6
Michael O.
Threadstarter Threadstarter
MOF User
MOF User
Standard

Ich bin schon wieder mit meinen Antworten im Rückstand, sorry...

@Ulrich
Tatsächlich erhalte ich bei DefaultValue mit "Falsch" einen Datentypenfehler, mit "false" läuft der Code durch.
Allerdings zeigt Access in der Tabelle Unterschiede zwischen der manuellen und der Code-Version:
Property Wert manuell Wert Code
Format Ja/Nein leer (zeigt allerdings Checkboxen an
Standard-Wert Nein Falsch

So richtig überzeugt mich das noch nicht...

@Ekkehard
Du hast natürlich vollkommen recht - das Löschen führe ich nur durch, damit ich meine Tests beliebig oft wiederholen kann. Denn ich bin noch im Stadium des Experimentierens. Allerdings: wenn ich einer Tabelle ein Feld neu hinzufügen kann es dafür noch keine erfassten Daten geben.

Die Vorteile Deiner Vorgehensweise liegen auf der Hand:[list][*]Ich kann die neue Tabellenstruktur wie gewohnt manuell vorbereiten, das ganze VBA-Gehampel entfällt ersatzlos.manuell[*]Die Anfügeabfrage ist einfach
(z. B.: INSERT INTO REZEPT_neu
SELECT REZEPT.*
FROM REZEPT

Lösen muss ich allerdings zwei Fragen:
  • Wie kopiere ich die neue Tabellenstruktur in die Datenbank (TransferDatatbase?)?
  • Was passiert mit den vorhandenen Relationen???

Ich denke, diese beiden Fragen muss ich beantworten, bevor ich weiter in diese Richtung denken kann.

Viele Grüße und besten Dank für die lebhafte Diskussion
Michael
Michael O. ist offline  
verlinken auf Del.icio.us Diese Seite zu Mister Wong hinzufügen
Antworten Auf Beitrag antworten
Alt 19.05.2019, 15:05   #7
markusxy
MOF Meister
MOF Meister
Standard

@Michael O.,
nimm einfach ein Feld mit gesetztem Standardwert, dann siehst du ja wie die Property aussehen muss und das der Wert Englisch drinnen steht.

Das erstellen einer neuen Tabelle und einfügen, halte ich persönlich für Unsinn - allein schon deswegen, weil Access Daten erst beim Komprimieren löscht.
Ob man die Änderungen per DDL oder per Tabledef durchführt ist dabei Geschmackssache. Wichtig ist nur zu wissen: Manche Dinge gehen nur per DDL manchen nur via Access Objektmodell.

Geändert von markusxy (19.05.2019 um 15:07 Uhr).
markusxy ist offline  
verlinken auf Del.icio.us Diese Seite zu Mister Wong hinzufügen
Antworten Auf Beitrag antworten
Alt 19.05.2019, 16:53   #8
Michael O.
Threadstarter Threadstarter
MOF User
MOF User
Standard

Ich habe auf dem Balkon noch ein bisschen über Ekkehards Idee nachgedacht. Ist es nicht möglich, eine Tabellenbeschreibung oder auch nur ein Feld über die TabelDefs- oder Fields-Auflistung vom FE in das BE zu kopieren?
Für die neue Tabelle etwa so:
Code:

' Neue Tabellen gemäß Vorlage anlegen
Set obj_table = db.TableDefs("Version_BE")
For Each obj_table In db.TableDefs
    Debug.Print obj_table.Name
Next
For Each obj_table In db_BE.TableDefs
    Debug.Print obj_table.Name
Next
db_BE.TableDefs.Append obj_table
db_BE.TableDefs.Refresh
"db" = CurrentDB = FrontEnd
"db_BE" = geöffnete DB _ BackEnd

Debug.Print zeigt mir auch die korrekten Tabellen beider TableDefs.

Dennoch erhalte ich Fehlercode 3367. Der besagt, dass das Objekt sich bereits in der Auflistung befindet.
Also scheint db_BE.TableDefs.Append obj_table nicht auf das BE zu verweisen, sonder falsch auf das FE. Aber warum?

Viele Grüße
Michael
Michael O. ist offline  
verlinken auf Del.icio.us Diese Seite zu Mister Wong hinzufügen
Antworten Auf Beitrag antworten
Ads
Antworten


Aktive Benutzer in diesem Thema: 1 (Registrierte Benutzer: 0, Besucher: 1)
 
Themen-Optionen
Ansicht

Forumregeln
Es ist Ihnen nicht erlaubt, neue Themen zu verfassen.
Es ist Ihnen nicht erlaubt, auf Beiträge zu antworten.
Es ist Ihnen nicht erlaubt, Anhänge anzufügen.
Es ist Ihnen nicht erlaubt, Ihre Beiträge zu bearbeiten.

vB Code ist An.
Smileys sind An.
[IMG] Code ist An.
HTML-Code ist An.
Gehe zu


Alle Zeitangaben in WEZ +1. Es ist jetzt 06:07 Uhr.



Powered by: vBulletin Version 3.6.2 (Deutsch)
Copyright ©2000 - 2019, Jelsoft Enterprises Ltd.

Copyright ©2000-2018 MS-Office-Forum. Alle Rechte vorbehalten.
Copyright ©Design: Manuela Kulpa ©Rechte: Günter Kramer
Eine Verwendung der Inhalte in anderen Publikationen, auch auszugsweise,
ist ohne ausdrückliche Zustimmung der Autoren nicht gestattet.