How do you use version control with Access development?

后端 未结 20 1920
慢半拍i
慢半拍i 2020-11-22 12:55

I\'m involved with updating an Access solution. It has a good amount of VBA, a number of queries, a small amount of tables, and a few forms for data entry & report gene

20条回答
  •  攒了一身酷
    2020-11-22 13:07

    Text-file only solution (queries, tables and relationships included)

    I have altered the Oliver's pair of scripts so that they export/import relationships, tables and queries in addition to modules, classes, forms and macros. Everything is saved into plaintext files, so there is no database file created to be stored with the text files in version control.

    Export into text files (decompose.vbs)

    ' Usage:
    '  cscript decompose.vbs  
    
    ' Converts all modules, classes, forms and macros from an Access Project file (.adp)  to
    ' text and saves the results in separate files to .  Requires Microsoft Access.
    Option Explicit
    
    Const acForm = 2
    Const acModule = 5
    Const acMacro = 4
    Const acReport = 3
    Const acQuery = 1
    Const acExportTable = 0
    
    ' BEGIN CODE
    Dim fso, relDoc, ACCDBFilename, sExportpath
    Set fso = CreateObject("Scripting.FileSystemObject")
    Set relDoc = CreateObject("Microsoft.XMLDOM")
    
    If (Wscript.Arguments.Count = 0) Then
        MsgBox "Please provide the .accdb database file", vbExclamation, "Error"
        Wscript.Quit()
    End If
    ACCDBFilename = fso.GetAbsolutePathName(Wscript.Arguments(0))
    
    If (Wscript.Arguments.Count = 1) Then
     sExportpath = ""
    Else
     sExportpath = Wscript.Arguments(1)
    End If
    
    
    exportModulesTxt ACCDBFilename, sExportpath
    
    If (Err <> 0) And (Err.Description <> Null) Then
        MsgBox Err.Description, vbExclamation, "Error"
        Err.Clear
    End If
    
    Function exportModulesTxt(ACCDBFilename, sExportpath)
        Dim myComponent, sModuleType, sTempname, sOutstring
        Dim myType, myName, myPath, hasRelations
        myType = fso.GetExtensionName(ACCDBFilename)
        myName = fso.GetBaseName(ACCDBFilename)
        myPath = fso.GetParentFolderName(ACCDBFilename)
    
        'if no path was given as argument, use a relative directory
        If (sExportpath = "") Then
            sExportpath = myPath & "\Source"
        End If
        'On Error Resume Next
        fso.DeleteFolder (sExportpath)
        fso.CreateFolder (sExportpath)
        On Error GoTo 0
    
        Wscript.Echo "starting Access..."
        Dim oApplication
        Set oApplication = CreateObject("Access.Application")
        Wscript.Echo "Opening " & ACCDBFilename & " ..."
        If (Right(ACCDBFilename, 4) = ".adp") Then
         oApplication.OpenAccessProject ACCDBFilename
        Else
         oApplication.OpenCurrentDatabase ACCDBFilename
        End If
        oApplication.Visible = False
    
        Wscript.Echo "exporting..."
        Dim myObj
        For Each myObj In oApplication.CurrentProject.AllForms
            Wscript.Echo "Exporting FORM " & myObj.FullName
            oApplication.SaveAsText acForm, myObj.FullName, sExportpath & "\" & myObj.FullName & ".form.txt"
            oApplication.DoCmd.Close acForm, myObj.FullName
        Next
        For Each myObj In oApplication.CurrentProject.AllModules
            Wscript.Echo "Exporting MODULE " & myObj.FullName
            oApplication.SaveAsText acModule, myObj.FullName, sExportpath & "\" & myObj.FullName & ".module.txt"
        Next
        For Each myObj In oApplication.CurrentProject.AllMacros
            Wscript.Echo "Exporting MACRO " & myObj.FullName
            oApplication.SaveAsText acMacro, myObj.FullName, sExportpath & "\" & myObj.FullName & ".macro.txt"
        Next
        For Each myObj In oApplication.CurrentProject.AllReports
            Wscript.Echo "Exporting REPORT " & myObj.FullName
            oApplication.SaveAsText acReport, myObj.FullName, sExportpath & "\" & myObj.FullName & ".report.txt"
        Next
        For Each myObj In oApplication.CurrentDb.QueryDefs
            Wscript.Echo "Exporting QUERY " & myObj.Name
            oApplication.SaveAsText acQuery, myObj.Name, sExportpath & "\" & myObj.Name & ".query.txt"
        Next
        For Each myObj In oApplication.CurrentDb.TableDefs
         If Not Left(myObj.Name, 4) = "MSys" Then
          Wscript.Echo "Exporting TABLE " & myObj.Name
          oApplication.ExportXml acExportTable, myObj.Name, , sExportpath & "\" & myObj.Name & ".table.txt"
          'put the file path as a second parameter if you want to export the table data as well, instead of ommiting it and passing it into a third parameter for structure only
         End If
        Next
    
        hasRelations = False
        relDoc.appendChild relDoc.createElement("Relations")
        For Each myObj In oApplication.CurrentDb.Relations  'loop though all the relations
        If Not Left(myObj.Name, 4) = "MSys" Then
         Dim relName, relAttrib, relTable, relFoTable, fld
         hasRelations = True
    
         relDoc.ChildNodes(0).appendChild relDoc.createElement("Relation")
         Set relName = relDoc.createElement("Name")
         relName.Text = myObj.Name
         relDoc.ChildNodes(0).LastChild.appendChild relName
    
         Set relAttrib = relDoc.createElement("Attributes")
         relAttrib.Text = myObj.Attributes
         relDoc.ChildNodes(0).LastChild.appendChild relAttrib
    
         Set relTable = relDoc.createElement("Table")
         relTable.Text = myObj.Table
         relDoc.ChildNodes(0).LastChild.appendChild relTable
    
         Set relFoTable = relDoc.createElement("ForeignTable")
         relFoTable.Text = myObj.ForeignTable
         relDoc.ChildNodes(0).LastChild.appendChild relFoTable
    
         Wscript.Echo "Exporting relation " & myObj.Name & " between tables " & myObj.Table & " -> " & myObj.ForeignTable
    
         For Each fld In myObj.Fields   'in case the relationship works with more fields
          Dim lf, ff
          relDoc.ChildNodes(0).LastChild.appendChild relDoc.createElement("Field")
    
          Set lf = relDoc.createElement("Name")
          lf.Text = fld.Name
          relDoc.ChildNodes(0).LastChild.LastChild.appendChild lf
    
          Set ff = relDoc.createElement("ForeignName")
          ff.Text = fld.ForeignName
          relDoc.ChildNodes(0).LastChild.LastChild.appendChild ff
    
          Wscript.Echo "  Involving fields " & fld.Name & " -> " & fld.ForeignName
         Next
        End If
        Next
        If hasRelations Then
         relDoc.InsertBefore relDoc.createProcessingInstruction("xml", "version='1.0'"), relDoc.ChildNodes(0)
         relDoc.Save sExportpath & "\relations.rel.txt"
         Wscript.Echo "Relations successfuly saved in file relations.rel.txt"
        End If
    
        oApplication.CloseCurrentDatabase
        oApplication.Quit
    
    End Function
    

    You can execute this script by calling cscript decompose.vbs . In case you omit the second parameter, it will create 'Source' folder where the database is located. Please note that destination folder will be wiped if it already exists.

    Include data in the exported tables

    Replace line 93: oApplication.ExportXML acExportTable, myObj.Name, , sExportpath & "\" & myObj.Name & ".table.txt"

    with line oApplication.ExportXML acExportTable, myObj.Name, sExportpath & "\" & myObj.Name & ".table.txt"

    Import into Create database file (compose.vbs)

    ' Usage:
    '  cscript compose.vbs  
    
    ' Reads all modules, classes, forms, macros, queries, tables and their relationships in a directory created by "decompose.vbs"
    ' and composes then into an Access Database file (.accdb).
    ' Requires Microsoft Access.
    Option Explicit
    
    Const acForm = 2
    Const acModule = 5
    Const acMacro = 4
    Const acReport = 3
    Const acQuery = 1
    Const acStructureOnly = 0   'change 0 to 1 if you want import StructureAndData instead of StructureOnly
    Const acCmdCompileAndSaveAllModules = &H7E
    
    Dim fso, relDoc, ACCDBFilename, sPath
    Set fso = CreateObject("Scripting.FileSystemObject")
    Set relDoc = CreateObject("Microsoft.XMLDOM")
    
    If (Wscript.Arguments.Count = 0) Then
     MsgBox "Please provide the .accdb database file", vbExclamation, "Error"
     Wscript.Quit()
    End If
    
    ACCDBFilename = fso.GetAbsolutePathName(Wscript.Arguments(0))
    If (Wscript.Arguments.Count = 1) Then
     sPath = ""
    Else
     sPath = Wscript.Arguments(1)
    End If
    
    
    importModulesTxt ACCDBFilename, sPath
    
    If (Err <> 0) And (Err.Description <> Null) Then
        MsgBox Err.Description, vbExclamation, "Error"
        Err.Clear
    End If
    
    
    Function importModulesTxt(ACCDBFilename, sImportpath)
        Dim myComponent, sModuleType, sTempname, sOutstring
    
        ' Build file and pathnames
        Dim myType, myName, myPath
        myType = fso.GetExtensionName(ACCDBFilename)
        myName = fso.GetBaseName(ACCDBFilename)
        myPath = fso.GetParentFolderName(ACCDBFilename)
    
        ' if no path was given as argument, use a relative directory
        If (sImportpath = "") Then
            sImportpath = myPath & "\Source\"
        End If
    
        ' check for existing file and ask to overwrite with the stub
        If fso.FileExists(ACCDBFilename) Then
         Wscript.StdOut.Write ACCDBFilename & " already exists. Overwrite? (y/n) "
         Dim sInput
         sInput = Wscript.StdIn.Read(1)
         If (sInput <> "y") Then
          Wscript.Quit
         Else
          If fso.FileExists(ACCDBFilename & ".bak") Then
           fso.DeleteFile (ACCDBFilename & ".bak")
          End If
          fso.MoveFile ACCDBFilename, ACCDBFilename & ".bak"
         End If
        End If
    
        Wscript.Echo "starting Access..."
        Dim oApplication
        Set oApplication = CreateObject("Access.Application")
        Wscript.Echo "Opening " & ACCDBFilename
        If (Right(ACCDBFilename, 4) = ".adp") Then
            oApplication.CreateAccessProject ACCDBFilename
        Else
            oApplication.NewCurrentDatabase ACCDBFilename
        End If
        oApplication.Visible = False
    
        Dim folder
        Set folder = fso.GetFolder(sImportpath)
    
        'load each file from the import path into the stub
        Dim myFile, objectname, objecttype
        For Each myFile In folder.Files
         objectname = fso.GetBaseName(myFile.Name)  'get rid of .txt extension
         objecttype = fso.GetExtensionName(objectname)
         objectname = fso.GetBaseName(objectname)
    
         Select Case objecttype
          Case "form"
           Wscript.Echo "Importing FORM from file " & myFile.Name
           oApplication.LoadFromText acForm, objectname, myFile.Path
          Case "module"
           Wscript.Echo "Importing MODULE from file " & myFile.Name
           oApplication.LoadFromText acModule, objectname, myFile.Path
          Case "macro"
           Wscript.Echo "Importing MACRO from file " & myFile.Name
           oApplication.LoadFromText acMacro, objectname, myFile.Path
          Case "report"
           Wscript.Echo "Importing REPORT from file " & myFile.Name
           oApplication.LoadFromText acReport, objectname, myFile.Path
          Case "query"
           Wscript.Echo "Importing QUERY from file " & myFile.Name
           oApplication.LoadFromText acQuery, objectname, myFile.Path
          Case "table"
           Wscript.Echo "Importing TABLE from file " & myFile.Name
           oApplication.ImportXml myFile.Path, acStructureOnly
          Case "rel"
           Wscript.Echo "Found RELATIONSHIPS file " & myFile.Name & " ... opening, it will be processed after everything else has been imported"
           relDoc.Load (myFile.Path)
         End Select
        Next
    
        If relDoc.readyState Then
         Wscript.Echo "Preparing to build table dependencies..."
         Dim xmlRel, xmlField, accessRel, relTable, relName, relFTable, relAttr, i
         For Each xmlRel In relDoc.SelectNodes("/Relations/Relation")   'loop through every Relation node inside .xml file
          relName = xmlRel.SelectSingleNode("Name").Text
          relTable = xmlRel.SelectSingleNode("Table").Text
          relFTable = xmlRel.SelectSingleNode("ForeignTable").Text
          relAttr = xmlRel.SelectSingleNode("Attributes").Text
    
          'remove any possible conflicting relations or indexes
          On Error Resume Next
          oApplication.CurrentDb.Relations.Delete (relName)
          oApplication.CurrentDb.TableDefs(relTable).Indexes.Delete (relName)
          oApplication.CurrentDb.TableDefs(relFTable).Indexes.Delete (relName)
          On Error GoTo 0
    
          Wscript.Echo "Creating relation " & relName & " between tables " & relTable & " -> " & relFTable
          Set accessRel = oApplication.CurrentDb.CreateRelation(relName, relTable, relFTable, relAttr)  'create the relationship object
    
          For Each xmlField In xmlRel.SelectNodes("Field")  'in case the relationship works with more fields
           accessRel.Fields.Append accessRel.CreateField(xmlField.SelectSingleNode("Name").Text)
           accessRel.Fields(xmlField.SelectSingleNode("Name").Text).ForeignName = xmlField.SelectSingleNode("ForeignName").Text
           Wscript.Echo "  Involving fields " & xmlField.SelectSingleNode("Name").Text & " -> " & xmlField.SelectSingleNode("ForeignName").Text
          Next
    
          oApplication.CurrentDb.Relations.Append accessRel 'append the newly created relationship to the database
          Wscript.Echo "  Relationship added"
         Next
        End If
    
        oApplication.RunCommand acCmdCompileAndSaveAllModules
        oApplication.Quit
    End Function
    

    You can execute this script by calling cscript compose.vbs . In case you omit the second parameter, it will look into 'Source' folder where the database should be created.

    Import data from text file

    Replace line 14: const acStructureOnly = 0 with const acStructureOnly = 1. This will work only if you have included the data in exported table.

    Things that are not covered

    1. I have tested this only with .accdb files, so with anything else there might be some bugs.
    2. Setting are not exported, I would recommend creating the Macro that will apply the setting at start of the database.
    3. Some unknown queries sometimes get exported that are preceded with '~'. I don't know if they are necessary.
    4. MSAccess object names can contain characters that are invalid for filenames - the script will fail when trying to write them. You may normalize all filenames, but then you cannot import them back.

    One of my other resources while working on this script was this answer, which helped me to figure out how to export relationships.

提交回复
热议问题