如何在 Access 开发中使用版本控制?

Posted

技术标签:

【中文标题】如何在 Access 开发中使用版本控制?【英文标题】:How do you use version control with Access development? 【发布时间】:2008-10-09 14:25:29 【问题描述】:

我正在参与更新 Access 解决方案。它有大量的 VBA、大量的查询、少量的表格以及一些用于数据输入和报告生成的表格。它是 Access 的理想选择。

我想对表格设计、VBA、查询和表单进行更改。如何使用版本控制跟踪我的更改? (我们使用 Subversion,但这适用于任何风格)我可以将整个 mdb 粘贴到 subversion 中,但这将存储一个二进制文件,我无法判断我只是更改了一行 VBA 代码。

我曾考虑将 VBA 代码复制到单独的文件中并保存它们,但我可以看到这些代码很快与数据库中的内容不同步。

【问题讨论】:

将this solution 转至导出Access db schema 的相关问题。 Access 支持 SCC 接口,因此任何与该接口兼容的版本控制都可以用于 Access。免责声明:我为plasticscm.com 工作,我们有几个客户将其与Access 一起使用。 试试这个 vba 模块github.com/timabell/msaccess-vcs-integration @EricG 您交叉发布的解决方案仍然适用于 Windows 10 上的 Access 365 我提交了一个功能请求,以在 Access 中添加对此的官方支持。你可以vote on it here。 【参考方案1】:

我们用 VBScript 编写了自己的脚本,它使用 Access 中未记录的 Application.SaveAsText() 来导出所有代码、表单、宏和报表模块。在这里,它应该给你一些指示。 (请注意:有些消息是德语的,但您可以轻松更改它。)

编辑: 总结以下各种cmet: 我们的项目采用 .adp 文件。为了使用 .mdb/.accdb 进行这项工作,您必须将 OpenAccessProject() 更改为 OpenCurrentDatabase()。 (更新为使用OpenAccessProject(),如果它看到.adp 扩展名,否则使用OpenCurrentDatabase()。)

分解.vbs:

' Usage:
'  CScript decompose.vbs <input file> <path>

' Converts all modules, classes, forms and macros from an Access Project file (.adp) <input file> to
' text and saves the results in separate files to <path>.  Requires Microsoft Access.
'

Option Explicit

const acForm = 2
const acModule = 5
const acMacro = 4
const acReport = 3

' BEGIN CODE
Dim fso
Set fso = CreateObject("Scripting.FileSystemObject")

dim sADPFilename
If (WScript.Arguments.Count = 0) then
    MsgBox "Bitte den Dateinamen angeben!", vbExclamation, "Error"
    Wscript.Quit()
End if
sADPFilename = fso.GetAbsolutePathName(WScript.Arguments(0))

Dim sExportpath
If (WScript.Arguments.Count = 1) then
    sExportpath = ""
else
    sExportpath = WScript.Arguments(1)
End If


exportModulesTxt sADPFilename, sExportpath

If (Err <> 0) and (Err.Description <> NULL) Then
    MsgBox Err.Description, vbExclamation, "Error"
    Err.Clear
End If

Function exportModulesTxt(sADPFilename, sExportpath)
    Dim myComponent
    Dim sModuleType
    Dim sTempname
    Dim sOutstring

    dim myType, myName, myPath, sStubADPFilename
    myType = fso.GetExtensionName(sADPFilename)
    myName = fso.GetBaseName(sADPFilename)
    myPath = fso.GetParentFolderName(sADPFilename)

    If (sExportpath = "") then
        sExportpath = myPath & "\Source\"
    End If
    sStubADPFilename = sExportpath & myName & "_stub." & myType

    WScript.Echo "copy stub to " & sStubADPFilename & "..."
    On Error Resume Next
        fso.CreateFolder(sExportpath)
    On Error Goto 0
    fso.CopyFile sADPFilename, sStubADPFilename

    WScript.Echo "starting Access..."
    Dim oApplication
    Set oApplication = CreateObject("Access.Application")
    WScript.Echo "opening " & sStubADPFilename & " ..."
    If (Right(sStubADPFilename,4) = ".adp") Then
        oApplication.OpenAccessProject sStubADPFilename
    Else
        oApplication.OpenCurrentDatabase sStubADPFilename
    End If

    oApplication.Visible = false

    dim dctDelete
    Set dctDelete = CreateObject("Scripting.Dictionary")
    WScript.Echo "exporting..."
    Dim myObj
    For Each myObj In oApplication.CurrentProject.AllForms
        WScript.Echo "  " & myObj.fullname
        oApplication.SaveAsText acForm, myObj.fullname, sExportpath & "\" & myObj.fullname & ".form"
        oApplication.DoCmd.Close acForm, myObj.fullname
        dctDelete.Add "FO" & myObj.fullname, acForm
    Next
    For Each myObj In oApplication.CurrentProject.AllModules
        WScript.Echo "  " & myObj.fullname
        oApplication.SaveAsText acModule, myObj.fullname, sExportpath & "\" & myObj.fullname & ".bas"
        dctDelete.Add "MO" & myObj.fullname, acModule
    Next
    For Each myObj In oApplication.CurrentProject.AllMacros
        WScript.Echo "  " & myObj.fullname
        oApplication.SaveAsText acMacro, myObj.fullname, sExportpath & "\" & myObj.fullname & ".mac"
        dctDelete.Add "MA" & myObj.fullname, acMacro
    Next
    For Each myObj In oApplication.CurrentProject.AllReports
        WScript.Echo "  " & myObj.fullname
        oApplication.SaveAsText acReport, myObj.fullname, sExportpath & "\" & myObj.fullname & ".report"
        dctDelete.Add "RE" & myObj.fullname, acReport
    Next

    WScript.Echo "deleting..."
    dim sObjectname
    For Each sObjectname In dctDelete
        WScript.Echo "  " & Mid(sObjectname, 3)
        oApplication.DoCmd.DeleteObject dctDelete(sObjectname), Mid(sObjectname, 3)
    Next

    oApplication.CloseCurrentDatabase
    oApplication.CompactRepair sStubADPFilename, sStubADPFilename & "_"
    oApplication.Quit

    fso.CopyFile sStubADPFilename & "_", sStubADPFilename
    fso.DeleteFile sStubADPFilename & "_"


End Function

Public Function getErr()
    Dim strError
    strError = vbCrLf & "----------------------------------------------------------------------------------------------------------------------------------------" & vbCrLf & _
               "From " & Err.source & ":" & vbCrLf & _
               "    Description: " & Err.Description & vbCrLf & _
               "    Code: " & Err.Number & vbCrLf
    getErr = strError
End Function

如果你需要一个可点击的命令,而不是使用命令行,创建一个名为“decompose.cmd”的文件

cscript decompose.vbs youraccessapplication.adp

默认情况下,所有导出的文件都进入 Access 应用程序的“脚本”子文件夹。 .adp/mdb 文件也被复制到此位置(带有“存根”后缀)并去除所有导出的模块,使其非常小。

您必须使用源文件签入此存根,因为大多数访问设置和自定义菜单栏无法以任何其他方式导出。如果您确实更改了某些设置或菜单,请务必仅提交对此文件的更改。

注意:如果您的应用程序中定义了任何 Autoexec-Makros,则在调用分解时可能必须按住 Shift 键以防止它执行和干扰导出!

当然,也有逆向脚本,从“源”目录构建应用程序:

compose.vbs:

' Usage:
'  WScript compose.vbs <file> <path>

' Converts all modules, classes, forms and macros in a directory created by "decompose.vbs"
' and composes then into an Access Project file (.adp). This overwrites any existing Modules with the
' same names without warning!!!
' Requires Microsoft Access.

Option Explicit

const acForm = 2
const acModule = 5
const acMacro = 4
const acReport = 3

Const acCmdCompileAndSaveAllModules = &H7E

' BEGIN CODE
Dim fso
Set fso = CreateObject("Scripting.FileSystemObject")

dim sADPFilename
If (WScript.Arguments.Count = 0) then
    MsgBox "Please enter the file name!", vbExclamation, "Error"
    Wscript.Quit()
End if
sADPFilename = fso.GetAbsolutePathName(WScript.Arguments(0))

Dim sPath
If (WScript.Arguments.Count = 1) then
    sPath = ""
else
    sPath = WScript.Arguments(1)
End If


importModulesTxt sADPFilename, sPath

If (Err <> 0) and (Err.Description <> NULL) Then
    MsgBox Err.Description, vbExclamation, "Error"
    Err.Clear
End If

Function importModulesTxt(sADPFilename, sImportpath)
    Dim myComponent
    Dim sModuleType
    Dim sTempname
    Dim sOutstring

    ' Build file and pathnames
    dim myType, myName, myPath, sStubADPFilename
    myType = fso.GetExtensionName(sADPFilename)
    myName = fso.GetBaseName(sADPFilename)
    myPath = fso.GetParentFolderName(sADPFilename)

    ' if no path was given as argument, use a relative directory
    If (sImportpath = "") then
        sImportpath = myPath & "\Source\"
    End If
    sStubADPFilename = sImportpath & myName & "_stub." & myType

    ' check for existing file and ask to overwrite with the stub
    if (fso.FileExists(sADPFilename)) Then
        WScript.StdOut.Write sADPFilename & " exists. Overwrite? (y/n) "
        dim sInput
        sInput = WScript.StdIn.Read(1)
        if (sInput <> "y") Then
            WScript.Quit
        end if

        fso.CopyFile sADPFilename, sADPFilename & ".bak"
    end if

    fso.CopyFile sStubADPFilename, sADPFilename

    ' launch MSAccess
    WScript.Echo "starting Access..."
    Dim oApplication
    Set oApplication = CreateObject("Access.Application")
    WScript.Echo "opening " & sADPFilename & " ..."
    If (Right(sStubADPFilename,4) = ".adp") Then
        oApplication.OpenAccessProject sADPFilename
    Else
        oApplication.OpenCurrentDatabase sADPFilename
    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
        objecttype = fso.GetExtensionName(myFile.Name)
        objectname = fso.GetBaseName(myFile.Name)
        WScript.Echo "  " & objectname & " (" & objecttype & ")"

        if (objecttype = "form") then
            oApplication.LoadFromText acForm, objectname, myFile.Path
        elseif (objecttype = "bas") then
            oApplication.LoadFromText acModule, objectname, myFile.Path
        elseif (objecttype = "mac") then
            oApplication.LoadFromText acMacro, objectname, myFile.Path
        elseif (objecttype = "report") then
            oApplication.LoadFromText acReport, objectname, myFile.Path
        end if

    next

    oApplication.RunCommand acCmdCompileAndSaveAllModules
    oApplication.Quit
End Function

Public Function getErr()
    Dim strError
    strError = vbCrLf & "----------------------------------------------------------------------------------------------------------------------------------------" & vbCrLf & _
               "From " & Err.source & ":" & vbCrLf & _
               "    Description: " & Err.Description & vbCrLf & _
               "    Code: " & Err.Number & vbCrLf
    getErr = strError
End Function

同样,这与伴随的“compose.cmd”一起包含:

cscript compose.vbs youraccessapplication.adp

它会要求您确认覆盖当前应用程序并首先创建一个备份(如果您这样做了)。然后它收集 Source-Directory 中的所有源文件并将它们重新插入到存根中。

玩得开心!

【讨论】:

我喜欢这段代码。我发现 oApplication.OpenAccessProject 不适用于 .accdb 文件(或者它可能是 Access 2007 的东西),我不得不改用 oApplication.OpenCurrentDatabase。 我正在做类似的事情(SaveAsText,但在 VBA 中使用 MDB 文件而不是 ADP),但我还有一个大问题:每次导出后,Subversion 会识别大约 100 个文件已更改(即使我只改变了一两个)。当我查看更改时,我看到一些变量名称或控件名称已经更改了它们的大写/小写拼写。例如:曾经包含“OrderNumber”的每个文件现在在导出中都包含“Ordernumber”,因此被标记为“已更改”(至少由 SVN,尚未尝试其他 SCM)。知道如何避免这种情况吗?非常感谢! 是的,这也是我们项目中经常遇到的问题。据我们确定,问题在于您项目中的变量与控件具有相同的名称,只是在不同的情况下(上/下)。现在,根据组成模块的顺序,Access 似乎采用一种拼写并“纠正”所有其他拼写,因为 VBA 应该不区分大小写。 Access 会执行此操作,即使控件位于不同的表单上!如果您甚至在不同的表单上在不同的情况下拥有多个同名的控件,问题就会变得更大。 唯一的解决方案是查找每个变量/控件名称并将拼写更改为通用形式。导出并提交更改后,名称应该是稳定的。使用它们的类型为控件名称添加前缀通过命名约定几乎可以确保名称不会与变量冲突。 (例如,txtTitle 用于包含标题字段的文本框或 cmbUsers 用于组合框等) 忘了添加,为了使用 mdb 进行这项工作,我不得不将 OpenAccessProject 更改为 OpenCurrentDatabase【参考方案2】:

这似乎在 Access 中相当可用:

这个来自 msdn 的 link 解释了如何为 Microsoft Access 安装源代码管理插件。它作为 Access 2007 的 Access Developer Extensions 的一部分免费下载,并作为 Access 2003 的单独免费插件提供。

很高兴你问了这个问题,我花时间查了一下,因为我也想要这个能力。上面的链接包含有关此内容的更多信息以及指向加载项的链接。

更新: 我为 Access 2003 安装了插件。它只适用于 VSS,但它确实允许我将 Access 对象(表单、查询、表、模块等)放入存储库。当你去编辑仓库中的任何项目时,你会被要求检查它,但你不必这样做。接下来,我将检查它如何处理在没有加载项的系统上打开和更改的情况。我不是 VSS 的粉丝,但我真的很喜欢将访问对象存储在 repo 中的想法。

更新2: 没有加载项的机器无法对数据库结构进行任何更改(添加表字段、查询参数等)。起初我认为如果有人需要这可能是一个问题,因为如果 Access 没有加载加载项,则没有明显的方法可以从源代码管理中删除 Access 数据库。

我发现运行“压缩和修复”数据库会提示您是否要从源代码管理中删除该数据库。我选择了是,并且能够在没有加载项的情况下编辑数据库。上面link 中的文章还提供了设置Access 2003 和2007 以使用Team System 的说明。如果您可以找到 SVN 的 MSSCCI 提供程序,那么您很有可能可以让它发挥作用。

【讨论】:

请注意,如果不止一个人编辑了 ADP,我们会遇到很多问题,即无法从 VSS 签出 ADP。我们最终不得不为此准备一个单独的备份! 我玩过这种方法(使用 Vault,因为我知道 SVN 没有免费的 MSSCCI 提供程序...TortoiseSVNSCC 未维护,对我不起作用,其他两个或三个选项是商业的)。它有效,但它迫使您使用古老的排他锁定方法进行源代码控制,因此我打算放弃它并使用@Oliver 的解决方案。【参考方案3】:

Oliver 发布的 compose/decompose 解决方案很棒,但是有一些问题:

文件被编码为 UCS-2 (UTF-16),这可能导致版本控制系统/工具将文件视为二进制文件。 这些文件包含许多经常更改的杂乱无章的内容 - 校验和、打印机信息等等。如果您想要干净的差异或需要在项目上合作,这是一个严重的问题。

我本来打算自己解决这个问题,但发现已经有一个很好的解决方案可用:GitHub 上的timabell/msaccess-vcs-integration。我已经测试了 msaccess-vcs-integration 并且效果很好。

2015 年 3 月 3 日更新:该项目最初由 Github 上的 bkidwell 维护/拥有,但它是 transferred to timabell - 上面的项目链接已相应更新。 bkidwell 的原始项目中有一些分支,例如 by ArminBra 和 by matonb,不应该使用 AFAICT。

与 Olivers 的分解解决方案相比,使用 msaccess-vcs-integration 的缺点:

速度明显变慢。我确信速度问题可以解决,但我不需要经常将我的项目导出为文本...... 它不会创建一个已删除导出内容的存根 Access 项目。这也可以修复(通过采用分解脚本中的代码),但同样 - 不是那么重要。

无论如何,我的明确建议是 msaccess-vcs-integration。它解决了我在导出文件上使用 Git 时遇到的所有问题。

【讨论】:

看起来ArminBra fork 现在领先了(通过查看network graph 得出)。 Matonb 没有回复the only pull request,所以我猜他们至少现在已经放弃了。 现在还有我的 fork github.com/timabell/msaccess-vcs-integration - 修复复合键表导出损坏。另外两个看起来有点被遗弃了,所以我很高兴在我的 fork 上接受 pull requests 错误报告等。 我会礼貌地建议编辑此答案以指向我的 fork,因为这是现在维护最积极的版本。 @TimAbell:我已经更新了我的答案,以反映项目已转移给您的事实。 PS!我希望我们能获得一些支持,因为我认为这是最好的解决方案。 不错,在 github 项目的分支中导航似乎是我们为自己发明的最新问题 :-)【参考方案4】:

Olivers 回答很糟糕,但 CurrentProject 参考对我不起作用。基于Arvin Meyer 的类似解决方案,我最终从他的出口中间撕下胆量并用它替换它。如果您使用的是 mdb 而不是 adp,则具有导出查询的优势。

' Writes database componenets to a series of text files
' @author  Arvin Meyer
' @date    June 02, 1999
Function DocDatabase(oApp)
    Dim dbs 
    Dim cnt 
    Dim doc 
    Dim i
    Dim prefix
    Dim dctDelete
    Dim docName

    Const acQuery = 1

    Set dctDelete = CreateObject("Scripting.Dictionary")

    Set dbs = oApp.CurrentDb() ' use CurrentDb() to refresh Collections
    Set cnt = dbs.Containers("Forms")
    prefix = oApp.CurrentProject.Path & "\"
    For Each doc In cnt.Documents
        oApp.SaveAsText acForm, doc.Name, prefix & doc.Name & ".frm"
        dctDelete.Add "frm_" & doc.Name, acForm
    Next

    Set cnt = dbs.Containers("Reports")
    For Each doc In cnt.Documents
        oApp.SaveAsText acReport, doc.Name, prefix & doc.Name & ".rpt"
        dctDelete.Add "rpt_" & doc.Name, acReport
    Next

    Set cnt = dbs.Containers("Scripts")
    For Each doc In cnt.Documents
        oApp.SaveAsText acMacro, doc.Name, prefix & doc.Name & ".vbs"
        dctDelete.Add "vbs_" & doc.Name, acMacro
    Next

    Set cnt = dbs.Containers("Modules")
    For Each doc In cnt.Documents
        oApp.SaveAsText acModule, doc.Name, prefix & doc.Name & ".bas"
        dctDelete.Add "bas_" & doc.Name, acModule
    Next

    For i = 0 To dbs.QueryDefs.Count - 1
        oApp.SaveAsText acQuery, dbs.QueryDefs(i).Name, prefix & dbs.QueryDefs(i).Name & ".txt"
        dctDelete.Add "qry_" & dbs.QueryDefs(i).Name, acQuery
    Next

    WScript.Echo "deleting " & dctDelete.Count & " objects."
    For Each docName In dctDelete
        WScript.Echo "  " & Mid(docName, 5)
        oApp.DoCmd.DeleteObject dctDelete(docName), Mid(docName, 5)
    Next

    Set doc = Nothing
    Set cnt = Nothing
    Set dbs = Nothing
    Set dctDelete = Nothing

End Function

【讨论】:

+1 用于包含查询。现在只需要包含表模式。 批准的答案不适用于 Access 97,但这个答案帮助我修改它以供我自己使用。感谢您发布此信息! 我强烈建议将查询保存放在表单保存之前,以便稍后更改删除顺序。当我尝试删除已经自动删除的查询时,我在最后一个 For Each 语句中遇到了一些问题,而这些查询之前已经删除了相应的表单。此外,如果您在启动时打开了一些表单并且不想按住 F11(或将其停用),只需在运行后插入 oApp.DoCmd.Close acForm, "formName" cnt.Documents @Cunso 请您发布与Access 97兼容的代码。所以我不需要重新开发它。 我该如何使用它?从潜艇调用它?【参考方案5】:

我们开发了自己的内部工具,其中:

    模块:导出为 txt 文件,然后与“文件比较工具”(免费软件)进行比较 表单:通过 undocument application.saveAsText 命令导出。然后可以看到 2 个不同版本之间的差异(再次“文件比较工具”)。 宏:我们没有可比较的宏,因为我们只有“autoexec”宏,其中一行启动 VBA 主程序 查询:只是存储在表格中的文本字符串:见下文 表:我们编写了自己的表比较器,列出了记录和表结构的差异。

整个系统足够智能,可以让我们生成 Access 应用程序的“运行时”版本,从 txt 文件(模块和使用 undocument application.loadFromText 命令重新创建的表单)和 mdb 文件(表)自动生成。

这听起来可能很奇怪,但确实有效。

【讨论】:

希望看到这个工具开源! 将这些导出的文本文件上传到 GitHub 上会不会是个好主意?【参考方案6】:

根据这篇文章的想法和一些博客中的类似条目,我编写了一个适用于 mdb 和 adp 文件格式的应用程序。它将所有数据库对象(包括表、引用、关系和数据库属性)导入/导出到纯文本文件。 使用这些文件,您可以使用任何源版本控制。下一个版本将允许将纯文本文件导入回数据库。还有一个命令行工具

您可以从以下地址下载应用程序或源代码:http://accesssvn.codeplex.com/

问候

【讨论】:

我们已经使用它快两年了,它很棒。谢谢!【参考方案7】:

复活一个旧线程,但这是一个很好的线程。我已经为自己的项目实现了两个脚本(compose.vbs / decompose.vbs),但遇到了旧 .mdb 文件的问题:

当它到达一个包含代码的表单时它会停止:

NoSaveCTIWhenDisabled =1

Access 说它有问题,这就是故事的结局。我进行了一些测试并尝试解决这个问题,并在最后发现这个线程有一个解决方法:

Can't create database

基本上(以防线程死机),您使用 .mdb 并对新的 .accdb 格式执行“另存为”。然后源安全或组合/分解的东西将起作用。我还必须玩 10 分钟才能获得正确的命令行语法,以便 (de)compose 脚本正常工作,所以这也是该信息:

编写(假设你的东西位于 C:\SControl 中(创建一个名为 Source 的子文件夹来存储提取的文件):

'(to extract for importing to source control)
cscript compose.vbs database.accdb     

'(to rebuild from extracted files saved from an earlier date)
cscript decompose.vbs database.accdb C:\SControl\Source\

就是这样!

我遇到上述问题的 Access 版本包括 Access 2000-2003 ".mdb" 数据库,并通过在运行 compose/decompose 脚本之前将它们保存为 2007-2010 ".accdb" 格式来解决问题.转换后脚本工作正常!

【讨论】:

您能否对其进行编辑以包含您遇到此问题的 Access 版本? 没问题,你还在做访问开发Nathan吗?如果是这样,将其与版本控制集成成功了吗? 我不再做 Access 开发了。当我问这个问题时,我有一个项目,我在回来的路上使用它,并且从来没有用它做任何其他事情。 酷,我认为大多数企业都使用某种专用的 SQL 服务器。我现在所处的情况是 MS SQL Server、Oracle 和一堆 Access 数据库的混合体,这些数据库将数据从服务器拉到本地表并导出到 excel。这是一个相当复杂的混合物。我想我会提出一个新问题,就建立一个新项目的一些建议,我很快就会开始,看看人们可以提出什么建议来降低复杂性【参考方案8】:

纯文本文件解决方案(包括查询、表格和关系)

我更改了 Oliver 的脚本对,以便它们导出/导入关系、表和查询,以及模块、类、表单和宏。 所有内容都保存到纯文本文件中,因此没有创建数据库文件与版本控制中的文本文件一起存储。

导出为文本文件(decompose.vbs)

' Usage:
'  cscript decompose.vbs <input file> <path>

' Converts all modules, classes, forms and macros from an Access Project file (.adp) <input file> to
' text and saves the results in separate files to <path>.  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

您可以通过调用cscript decompose.vbs &lt;path to file to decompose&gt; &lt;folder to store text files&gt; 来执行此脚本。如果您省略第二个参数,它将创建数据库所在的“源”文件夹。请注意,如果目标文件夹已经存在,它将被擦除。

在导出的表格中包含数据

替换第 93 行:oApplication.ExportXML acExportTable, myObj.Name, , sExportpath &amp; "\" &amp; myObj.Name &amp; ".table.txt"

与线oApplication.ExportXML acExportTable, myObj.Name, sExportpath &amp; "\" &amp; myObj.Name &amp; ".table.txt"

导入创建数据库文件(compose.vbs)

' Usage:
'  cscript compose.vbs <file> <path>

' 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

您可以通过调用cscript compose.vbs &lt;path to file which should be created&gt; &lt;folder with text files&gt; 来执行此脚本。如果您省略第二个参数,它将查看应创建数据库的“源”文件夹。

从文本文件导入数据

将第 14 行:const acStructureOnly = 0 替换为 const acStructureOnly = 1。仅当您已将数据包含在导出的表中时,这才有效。

未涵盖的内容

    我仅使用 .accdb 文件对此进行了测试,因此其他任何内容都可能存在一些错误。 设置未导出,我建议创建将在数据库启动时应用设置的宏。 有时会导出一些以“~”开头的未知查询。不知道有没有必要。 MSAccess 对象名称可以包含文件名无效的字符 - 尝试写入它们时脚本会失败。你可以normalize all filenames,但是你不能将它们导入回来。

在编写此脚本时,我的其他资源之一是 this answer,它帮助我弄清楚如何导出关系。

【讨论】:

这似乎有效,但不理解链接表【参考方案9】:

有一个问题 - VSS 6.0 只能在一定数量的对象下接受使用插件的 MDB,其中包括所有本地表、查询、模块和表单。不知道确切的对象限制。

为了构建我们已有 10 年历史的大型 prod floor 应用程序,我们不得不将 SS 中的 3 或 4 个单独的 MDB 合并到一个 MDB 中,这使得自动化构建变得复杂,以至于我们不会浪费时间去做。

我想我会尝试使用上面的脚本将此 MDb 导入 SVN 并为每个人简化构建。

【讨论】:

【参考方案10】:

对于使用 Access 2010 的用户,SaveAsText 在 Intellisense 中不是可见的方法,但它似乎是一种有效的方法,因为 Arvin Meyer 的脚本 mentioned earlier 对我来说效果很好。

有趣的是,SaveAsAXL 是 2010 的新成员,具有与 SaveAsText 相同的签名,但它似乎仅适用于需要 SharePoint Server 2010 的 Web 数据库。

【讨论】:

SaveAsText 在 A2003 中也不可见,除非您在对象浏览器中打开了显示隐藏成员。关于 SaveAsAXL 的好信息。【参考方案11】:

不久前我们遇到了同样的问题。

我们的第一个尝试是第三方工具,它提供了用于 Subversion 的 SourceSafe API 代理,可与 MS Access 和 VB 6 一起使用。该工具可以在 here 找到。

由于我们对该工具不太满意,我们改用 Visual SourceSafe 和 VSS Acces 插件。

【讨论】:

【参考方案12】:

我正在使用 Oasis-Svn http://dev2dev.de/

我只能说它至少救了我一次。我的 mdb 增长到超过 2 GB 并且打破了它。我可以回到旧版本并导入表单,而只是失去了一天左右的工作。

【讨论】:

【参考方案13】:

我在 SourceForge 上找到了这个工具:http://sourceforge.net/projects/avc/

我没用过,但对你来说可能是个开始。可能还有其他一些与 VSS 或 SVN 集成的第三方工具可以满足您的需求。

就我个人而言,我只是在手边保留一个纯文本文件来保存更改日志。当我提交二进制 MDB 时,我使用更改日志中的条目作为我的提交注释。

【讨论】:

有下载链接吗?我瞎了吗?我好像没找到。 sourceforge.net/project/showfiles.php?group_id=115226 未定义文件包。耶。【参考方案14】:

为了完整性...

总是有“适用于 Microsoft Office 系统的 Visual Studio [YEAR] 工具” (http://msdn.microsoft.com/en-us/vs2005/aa718673.aspx) 但这似乎需要 VSS。对我来说,VSS(自动损坏)比我在超级备份网络共享上的 347 个保存点更糟糕。

【讨论】:

【参考方案15】:

我正在使用Access 2003 Add-in: Source Code Control。它工作正常。一个问题是无效字符,如“:”。

我正在办理入住和退房。在内部,加载项与上面的代码执行相同的操作,但具有更多工具支持。我可以查看对象是否已签出并刷新对象。

【讨论】:

【参考方案16】:

您还可以将您的 MS Access 连接到 Team Foundation Server。还有一个免费的 Express 变体,最多可供 5 位开发人员使用。效果很好!

English guide Team Foundation Server 2012 Express

编辑:固定链接

【讨论】:

【参考方案17】:

奥利弗的回答很有效。请在下面找到我的扩展版本,它增加了对 Access 查询的支持。

(请see answer from Oliver了解更多信息/用法)

分解.vbs:

' Usage:
'  CScript decompose.vbs <input file> <path>

' Converts all modules, classes, forms and macros from an Access Project file (.adp) <input file> to
' text and saves the results in separate files to <path>.  Requires Microsoft Access.
'
Option Explicit

const acForm = 2
const acModule = 5
const acMacro = 4
const acReport = 3
const acQuery = 1

' BEGIN CODE
Dim fso
Set fso = CreateObject("Scripting.FileSystemObject")

dim sADPFilename
If (WScript.Arguments.Count = 0) then
    MsgBox "Bitte den Dateinamen angeben!", vbExclamation, "Error"
    Wscript.Quit()
End if
sADPFilename = fso.GetAbsolutePathName(WScript.Arguments(0))

Dim sExportpath
If (WScript.Arguments.Count = 1) then
    sExportpath = ""
else
    sExportpath = WScript.Arguments(1)
End If


exportModulesTxt sADPFilename, sExportpath

If (Err <> 0) and (Err.Description <> NULL) Then
    MsgBox Err.Description, vbExclamation, "Error"
    Err.Clear
End If

Function exportModulesTxt(sADPFilename, sExportpath)
    Dim myComponent
    Dim sModuleType
    Dim sTempname
    Dim sOutstring

    dim myType, myName, myPath, sStubADPFilename
    myType = fso.GetExtensionName(sADPFilename)
    myName = fso.GetBaseName(sADPFilename)
    myPath = fso.GetParentFolderName(sADPFilename)

    If (sExportpath = "") then
        sExportpath = myPath & "\Source\"
    End If
    sStubADPFilename = sExportpath & myName & "_stub." & myType

    WScript.Echo "copy stub to " & sStubADPFilename & "..."
    On Error Resume Next
        fso.CreateFolder(sExportpath)
    On Error Goto 0
    fso.CopyFile sADPFilename, sStubADPFilename

    WScript.Echo "starting Access..."
    Dim oApplication
    Set oApplication = CreateObject("Access.Application")
    WScript.Echo "opening " & sStubADPFilename & " ..."
    If (Right(sStubADPFilename,4) = ".adp") Then
        oApplication.OpenAccessProject sStubADPFilename
    Else
        oApplication.OpenCurrentDatabase sStubADPFilename
    End If

    oApplication.Visible = false

    dim dctDelete
    Set dctDelete = CreateObject("Scripting.Dictionary")
    WScript.Echo "exporting..."
    Dim myObj

    For Each myObj In oApplication.CurrentProject.AllForms
        WScript.Echo "  " & myObj.fullname
        oApplication.SaveAsText acForm, myObj.fullname, sExportpath & "\" & myObj.fullname & ".form"
        oApplication.DoCmd.Close acForm, myObj.fullname
        dctDelete.Add "FO" & myObj.fullname, acForm
    Next
    For Each myObj In oApplication.CurrentProject.AllModules
        WScript.Echo "  " & myObj.fullname
        oApplication.SaveAsText acModule, myObj.fullname, sExportpath & "\" & myObj.fullname & ".bas"
        dctDelete.Add "MO" & myObj.fullname, acModule
    Next
    For Each myObj In oApplication.CurrentProject.AllMacros
        WScript.Echo "  " & myObj.fullname
        oApplication.SaveAsText acMacro, myObj.fullname, sExportpath & "\" & myObj.fullname & ".mac"
        dctDelete.Add "MA" & myObj.fullname, acMacro
    Next
    For Each myObj In oApplication.CurrentProject.AllReports
        WScript.Echo "  " & myObj.fullname
        oApplication.SaveAsText acReport, myObj.fullname, sExportpath & "\" & myObj.fullname & ".report"
        dctDelete.Add "RE" & myObj.fullname, acReport
    Next
    For Each myObj In oApplication.CurrentDb.QueryDefs
        if not left(myObj.name,3) = "~sq" then 'exclude queries defined by the forms. Already included in the form itself
            WScript.Echo "  " & myObj.name
            oApplication.SaveAsText acQuery, myObj.name, sExportpath & "\" & myObj.name & ".query"
            oApplication.DoCmd.Close acQuery, myObj.name
            dctDelete.Add "FO" & myObj.name, acQuery
        end if
    Next

    WScript.Echo "deleting..."
    dim sObjectname
    For Each sObjectname In dctDelete
        WScript.Echo "  " & Mid(sObjectname, 3)
        oApplication.DoCmd.DeleteObject dctDelete(sObjectname), Mid(sObjectname, 3)
    Next

    oApplication.CloseCurrentDatabase
    oApplication.CompactRepair sStubADPFilename, sStubADPFilename & "_"
    oApplication.Quit

    fso.CopyFile sStubADPFilename & "_", sStubADPFilename
    fso.DeleteFile sStubADPFilename & "_"


End Function

Public Function getErr()
    Dim strError
    strError = vbCrLf & "----------------------------------------------------------------------------------------------------------------------------------------" & vbCrLf & _
               "From " & Err.source & ":" & vbCrLf & _
               "    Description: " & Err.Description & vbCrLf & _
               "    Code: " & Err.Number & vbCrLf
    getErr = strError
End Function

compose.vbs:

' Usage:
'  WScript compose.vbs <file> <path>

' Converts all modules, classes, forms and macros in a directory created by "decompose.vbs"
' and composes then into an Access Project file (.adp). This overwrites any existing Modules with the
' same names without warning!!!
' Requires Microsoft Access.

Option Explicit

const acForm = 2
const acModule = 5
const acMacro = 4
const acReport = 3
const acQuery = 1

Const acCmdCompileAndSaveAllModules = &H7E

' BEGIN CODE
Dim fso
Set fso = CreateObject("Scripting.FileSystemObject")

dim sADPFilename
If (WScript.Arguments.Count = 0) then
    MsgBox "Bitte den Dateinamen angeben!", vbExclamation, "Error"
    Wscript.Quit()
End if
sADPFilename = fso.GetAbsolutePathName(WScript.Arguments(0))

Dim sPath
If (WScript.Arguments.Count = 1) then
    sPath = ""
else
    sPath = WScript.Arguments(1)
End If


importModulesTxt sADPFilename, sPath

If (Err <> 0) and (Err.Description <> NULL) Then
    MsgBox Err.Description, vbExclamation, "Error"
    Err.Clear
End If

Function importModulesTxt(sADPFilename, sImportpath)
    Dim myComponent
    Dim sModuleType
    Dim sTempname
    Dim sOutstring

    ' Build file and pathnames
    dim myType, myName, myPath, sStubADPFilename
    myType = fso.GetExtensionName(sADPFilename)
    myName = fso.GetBaseName(sADPFilename)
    myPath = fso.GetParentFolderName(sADPFilename)

    ' if no path was given as argument, use a relative directory
    If (sImportpath = "") then
        sImportpath = myPath & "\Source\"
    End If
    sStubADPFilename = sImportpath & myName & "_stub." & myType

    ' check for existing file and ask to overwrite with the stub
    if (fso.FileExists(sADPFilename)) Then
        WScript.StdOut.Write sADPFilename & " existiert bereits. Überschreiben? (j/n) "
        dim sInput
        sInput = WScript.StdIn.Read(1)
        if (sInput <> "j") Then
            WScript.Quit
        end if

        fso.CopyFile sADPFilename, sADPFilename & ".bak"
    end if

    fso.CopyFile sStubADPFilename, sADPFilename

    ' launch MSAccess
    WScript.Echo "starting Access..."
    Dim oApplication
    Set oApplication = CreateObject("Access.Application")
    WScript.Echo "opening " & sADPFilename & " ..."
    If (Right(sStubADPFilename,4) = ".adp") Then
        oApplication.OpenAccessProject sADPFilename
    Else
        oApplication.OpenCurrentDatabase sADPFilename
    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
        objecttype = fso.GetExtensionName(myFile.Name)
        objectname = fso.GetBaseName(myFile.Name)
        WScript.Echo "  " & objectname & " (" & objecttype & ")"

        if (objecttype = "form") then
            oApplication.LoadFromText acForm, objectname, myFile.Path
        elseif (objecttype = "bas") then
            oApplication.LoadFromText acModule, objectname, myFile.Path
        elseif (objecttype = "mac") then
            oApplication.LoadFromText acMacro, objectname, myFile.Path
        elseif (objecttype = "report") then
            oApplication.LoadFromText acReport, objectname, myFile.Path
        elseif (objecttype = "query") then
           oApplication.LoadFromText acQuery, objectname, myFile.Path
        end if

    next

    oApplication.RunCommand acCmdCompileAndSaveAllModules
    oApplication.Quit
End Function

Public Function getErr()
    Dim strError
    strError = vbCrLf & "----------------------------------------------------------------------------------------------------------------------------------------" & vbCrLf & _
               "From " & Err.source & ":" & vbCrLf & _
               "    Description: " & Err.Description & vbCrLf & _
               "    Code: " & Err.Number & vbCrLf
    getErr = strError
End Function

【讨论】:

【参考方案18】:

我试图通过在访问数据库中添加查询的导出选项来帮助他回答。 (在other SO answers 的大力帮助下)

Dim def
Set stream = fso.CreateTextFile(sExportpath & "\" & myName & ".queries.txt")
  For Each def In oApplication.CurrentDb.QueryDefs

    WScript.Echo "  Exporting Queries to Text..."
    stream.WriteLine("Name: " & def.Name)
    stream.WriteLine(def.SQL)
    stream.writeline "--------------------------"
    stream.writeline " "

  Next
stream.Close

无法将其重新用于“撰写”功能,但这不是我现在需要它做的事情。

注意:我还在 decompose.vbs 中为每个导出的文件名添加了“.txt”,以便源代码管理会立即显示文件差异。

希望对您有所帮助!


【讨论】:

【参考方案19】:

此条目描述的方法与其他条目完全不同,可能不是您想要的。因此,如果您忽略这一点,我不会生气。但至少它是值得深思的。

在一些专业的商业软件开发环境中,软件可交付成果的配置管理 (CM) 通常不在软件应用程序本身或软件项目本身中进行。通过将软件保存在一个特殊的 CM 文件夹中,对最终交付的产品施加 CM,该文件及其文件夹都标有版本标识。 例如,Clearcase 允许数据管理员“签入”软件文件,为其分配“分支”,为其分配“气泡”,并应用“标签”。 当你想查看和下载文件时,你必须配置你的“配置规范”以指向你想要的版本,然后 cd 进入文件夹就可以了。

只是一个想法。

【讨论】:

【参考方案20】:

对于坚持使用 Access 97 的任何人,我都无法获得其他答案。结合使用Oliver's 和DaveParillo's 出色的答案并进行一些修改,我能够使脚本与我们的Access 97 数据库一起使用。它也更加用户友好,因为它会询问放置文件的文件夹。

AccessExport.vbs:

' Converts all modules, classes, forms and macros from an Access file (.mdb) <input file> to
' text and saves the results in separate files to <path>.  Requires Microsoft Access.
Option Explicit

Const acQuery = 1
Const acForm = 2
Const acModule = 5
Const acMacro = 4
Const acReport = 3
Const acCmdCompactDatabase = 4
Const TemporaryFolder = 2

Dim strMDBFileName : strMDBFileName = SelectDatabaseFile
Dim strExportPath : strExportPath = SelectExportFolder
CreateExportFolders(strExportPath)
Dim objProgressWindow
Dim strOverallProgress
CreateProgressWindow objProgressWindow
Dim strTempMDBFileName
CopyToTempDatabase strMDBFileName, strTempMDBFileName, strOverallProgress
Dim objAccess
Dim objDatabase
OpenAccessDatabase objAccess, objDatabase, strTempMDBFileName, strOverallProgress
ExportQueries objAccess, objDatabase, objProgressWindow, strExportPath, strOverallProgress
ExportForms objAccess, objDatabase, objProgressWindow, strExportPath, strOverallProgress
ExportReports objAccess, objDatabase, objProgressWindow, strExportPath, strOverallProgress
ExportMacros objAccess, objDatabase, objProgressWindow, strExportPath, strOverallProgress
ExportModules objAccess, objDatabase, objProgressWindow, strExportPath, strOverallProgress
objAccess.CloseCurrentDatabase
objAccess.Quit
DeleteTempDatabase strTempMDBFileName, strOverallProgress
objProgressWindow.Quit
MsgBox "Successfully exported database."

Private Function SelectDatabaseFile()
    MsgBox "Please select the Access database to export."
    Dim objFileOpen : Set objFileOpen = CreateObject("SAFRCFileDlg.FileOpen")
    If objFileOpen.OpenFileOpenDlg Then
        SelectDatabaseFile = objFileOpen.FileName
    Else
        WScript.Quit()
    End If
End Function

Private Function SelectExportFolder()
    Dim objShell : Set objShell = CreateObject("Shell.Application")
    SelectExportFolder = objShell.BrowseForFolder(0, "Select folder to export the database to:", 0, "").self.path & "\"
End Function

Private Sub CreateExportFolders(strExportPath)
    Dim objFileSystem : Set objFileSystem = CreateObject("Scripting.FileSystemObject")
    MsgBox "Existing folders from a previous Access export under " & strExportPath & " will be deleted!"
    If objFileSystem.FolderExists(strExportPath & "Queries\") Then
        objFileSystem.DeleteFolder strExportPath & "Queries", true
    End If
    objFileSystem.CreateFolder(strExportPath & "Queries\")
    If objFileSystem.FolderExists(strExportPath & "Forms\") Then
        objFileSystem.DeleteFolder strExportPath & "Forms", true
    End If
    objFileSystem.CreateFolder(strExportPath & "Forms\")
    If objFileSystem.FolderExists(strExportPath & "Reports\") Then
        objFileSystem.DeleteFolder strExportPath & "Reports", true
    End If
    objFileSystem.CreateFolder(strExportPath & "Reports\")
    If objFileSystem.FolderExists(strExportPath & "Macros\") Then
        objFileSystem.DeleteFolder strExportPath & "Macros", true
    End If
    objFileSystem.CreateFolder(strExportPath & "Macros\")
    If objFileSystem.FolderExists(strExportPath & "Modules\") Then
        objFileSystem.DeleteFolder strExportPath & "Modules", true
    End If
    objFileSystem.CreateFolder(strExportPath & "Modules\")
End Sub

Private Sub CreateProgressWindow(objProgressWindow)
    Set objProgressWindow = CreateObject ("InternetExplorer.Application")
    objProgressWindow.Navigate "about:blank"
    objProgressWindow.ToolBar = 0
    objProgressWindow.StatusBar = 0
    objProgressWindow.Width = 320
    objProgressWindow.Height = 240
    objProgressWindow.Visible = 1
    objProgressWindow.Document.Title = "Access export in progress"
End Sub

Private Sub CopyToTempDatabase(strMDBFileName, strTempMDBFileName, strOverallProgress)
    strOverallProgress = strOverallProgress & "Copying to temporary database...<br/>"
    Dim objFileSystem : Set objFileSystem = CreateObject("Scripting.FileSystemObject")
    strTempMDBFileName = objFileSystem.GetSpecialFolder(TemporaryFolder) & "\" & objFileSystem.GetBaseName(strMDBFileName) & "_temp.mdb"
    objFileSystem.CopyFile strMDBFileName, strTempMDBFileName
End Sub

Private Sub OpenAccessDatabase(objAccess, objDatabase, strTempMDBFileName, strOverallProgress)
    strOverallProgress = strOverallProgress & "Compacting temporary database...<br/>"
    Set objAccess = CreateObject("Access.Application")
    objAccess.Visible = false
    CompactAccessDatabase objAccess, strTempMDBFileName
    strOverallProgress = strOverallProgress & "Opening temporary database...<br/>"
    objAccess.OpenCurrentDatabase strTempMDBFileName
    Set objDatabase = objAccess.CurrentDb
End Sub

' Sometimes the Compact Database command errors out, and it's not serious if the database isn't compacted first.
Private Sub CompactAccessDatabase(objAccess, strTempMDBFileName)
    On Error Resume Next
    Dim objFileSystem : Set objFileSystem = CreateObject("Scripting.FileSystemObject")
    objAccess.DbEngine.CompactDatabase strTempMDBFileName, strTempMDBFileName & "_"
    objFileSystem.CopyFile strTempMDBFileName & "_", strTempMDBFileName
    objFileSystem.DeleteFile strTempMDBFileName & "_"
End Sub

Private Sub ExportQueries(objAccess, objDatabase, objProgressWindow, strExportPath, strOverallProgress)
    strOverallProgress = strOverallProgress & "Exporting Queries (Step 1 of 5)...<br/>"
    Dim counter
    For counter = 0 To objDatabase.QueryDefs.Count - 1
        objProgressWindow.Document.Body.Innerhtml = strOverallProgress & counter + 1 & " of " & objDatabase.QueryDefs.Count
        objAccess.SaveAsText acQuery, objDatabase.QueryDefs(counter).Name, strExportPath & "Queries\" & Clean(objDatabase.QueryDefs(counter).Name) & ".sql"
    Next
End Sub

Private Sub ExportForms(objAccess, objDatabase, objProgressWindow, strExportPath, strOverallProgress)
    strOverallProgress = strOverallProgress & "Exporting Forms (Step 2 of 5)...<br/>"
    Dim counter : counter = 1
    Dim objContainer : Set objContainer = objDatabase.Containers("Forms")
    Dim objDocument
    For Each objDocument In objContainer.Documents
        objProgressWindow.Document.Body.InnerHTML = strOverallProgress & counter & " of " & objContainer.Documents.Count
        counter = counter + 1
        objAccess.SaveAsText acForm, objDocument.Name, strExportPath & "Forms\" & Clean(objDocument.Name) & ".form"
        objAccess.DoCmd.Close acForm, objDocument.Name
    Next
End Sub

Private Sub ExportReports(objAccess, objDatabase, objProgressWindow, strExportPath, strOverallProgress)
    strOverallProgress = strOverallProgress & "Exporting Reports (Step 3 of 5)...<br/>"
    Dim counter : counter = 1
    Dim objContainer : Set objContainer = objDatabase.Containers("Reports")
    Dim objDocument
    For Each objDocument In objContainer.Documents
        objProgressWindow.Document.Body.InnerHTML = strOverallProgress & counter & " of " & objContainer.Documents.Count
        counter = counter + 1
        objAccess.SaveAsText acReport, objDocument.Name, strExportPath & "Reports\" & Clean(objDocument.Name) & ".report"
    Next
End Sub

Private Sub ExportMacros(objAccess, objDatabase, objProgressWindow, strExportPath, strOverallProgress)
    strOverallProgress = strOverallProgress & "Exporting Macros (Step 4 of 5)...<br/>"
    Dim counter : counter = 1
    Dim objContainer : Set objContainer = objDatabase.Containers("Scripts")
    Dim objDocument
    For Each objDocument In objContainer.Documents
        objProgressWindow.Document.Body.InnerHTML = strOverallProgress & counter & " of " & objContainer.Documents.Count
        counter = counter + 1
        objAccess.SaveAsText acMacro, objDocument.Name, strExportPath & "Macros\" & Clean(objDocument.Name) & ".macro"
    Next
End Sub

Private Sub ExportModules(objAccess, objDatabase, objProgressWindow, strExportPath, strOverallProgress)
    strOverallProgress = strOverallProgress & "Exporting Modules (Step 5 of 5)...<br/>"
    Dim counter : counter = 1
    Dim objContainer : Set objContainer = objDatabase.Containers("Modules")
    Dim objDocument
    For Each objDocument In objContainer.Documents
        objProgressWindow.Document.Body.InnerHTML = strOverallProgress & counter & " of " & objContainer.Documents.Count
        counter = counter + 1
        objAccess.SaveAsText acModule, objDocument.Name, strExportPath & "Modules\" & Clean(objDocument.Name) & ".module"
    Next
End Sub

Private Sub DeleteTempDatabase(strTempMDBFileName, strOverallProgress)
    On Error Resume Next
    strOverallProgress = strOverallProgress & "Deleting temporary database...<br/>"
    Dim objFileSystem : Set objFileSystem = CreateObject("Scripting.FileSystemObject")
    objFileSystem.DeleteFile strTempMDBFileName, true
End Sub

' Windows doesn't like certain characters, so we have to filter those out of the name when exporting
Private Function Clean(strInput)
    Dim objRegexp : Set objRegexp = New RegExp
    objRegexp.IgnoreCase = True
    objRegexp.Global = True
    objRegexp.Pattern = "[\\/:*?""<>|]"
    Dim strOutput
    If objRegexp.Test(strInput) Then
        strOutput = objRegexp.Replace(strInput, "")
        MsgBox strInput & " is being exported as " & strOutput
    Else
        strOutput = strInput
    End If
    Clean = strOutput
End Function

对于将文件导入数据库,您是否需要从头开始重新创建数据库,或者出于某种原因希望在 Access 之外修改文件。

AccessImport.vbs:

' Imports all of the queries, forms, reports, macros, and modules from text
' files to an Access file (.mdb).  Requires Microsoft Access.
Option Explicit

const acQuery = 1
const acForm = 2
const acModule = 5
const acMacro = 4
const acReport = 3
const acCmdCompileAndSaveAllModules = &H7E

Dim strMDBFilename : strMDBFilename = SelectDatabaseFile
CreateBackup strMDBFilename
Dim strImportPath : strImportPath = SelectImportFolder
Dim objAccess
Dim objDatabase
OpenAccessDatabase objAccess, objDatabase, strMDBFilename
Dim objProgressWindow
Dim strOverallProgress
CreateProgressWindow objProgressWindow
ImportQueries objAccess, objDatabase, objProgressWindow, strImportPath, strOverallProgress
ImportForms objAccess, objDatabase, objProgressWindow, strImportPath, strOverallProgress
ImportReports objAccess, objDatabase, objProgressWindow, strImportPath, strOverallProgress
ImportMacros objAccess, objDatabase, objProgressWindow, strImportPath, strOverallProgress
ImportModules objAccess, objDatabase, objProgressWindow, strImportPath, strOverallProgress
objAccess.CloseCurrentDatabase
objAccess.Quit
objProgressWindow.Quit
MsgBox "Successfully imported objects into the database."

Private Function SelectDatabaseFile()
    MsgBox "Please select the Access database to import the objects from.  ALL EXISTING OBJECTS WITH THE SAME NAME WILL BE OVERWRITTEN!"
    Dim objFileOpen : Set objFileOpen = CreateObject( "SAFRCFileDlg.FileOpen" )
    If objFileOpen.OpenFileOpenDlg Then
        SelectDatabaseFile = objFileOpen.FileName
    Else
        WScript.Quit()
    End If
End Function

Private Function SelectImportFolder()
    Dim objShell : Set objShell = WScript.CreateObject("Shell.Application")
    SelectImportFolder = objShell.BrowseForFolder(0, "Select folder to import the database objects from:", 0, "").self.path & "\"
End Function

Private Sub CreateBackup(strMDBFilename)
    Dim objFileSystem : Set objFileSystem = CreateObject("Scripting.FileSystemObject")
    objFileSystem.CopyFile strMDBFilename, strMDBFilename & ".bak"
End Sub

Private Sub OpenAccessDatabase(objAccess, objDatabase, strMDBFileName)
    Set objAccess = CreateObject("Access.Application")
    objAccess.OpenCurrentDatabase strMDBFilename
    objAccess.Visible = false
    Set objDatabase = objAccess.CurrentDb
End Sub

Private Sub CreateProgressWindow(ByRef objProgressWindow)
    Set objProgressWindow = CreateObject ("InternetExplorer.Application")
    objProgressWindow.Navigate "about:blank"
    objProgressWindow.ToolBar = 0
    objProgressWindow.StatusBar = 0
    objProgressWindow.Width = 320
    objProgressWindow.Height = 240
    objProgressWindow.Visible = 1
    objProgressWindow.Document.Title = "Access import in progress"
End Sub

Private Sub ImportQueries(objAccess, objDatabase, objProgressWindow, strImportPath, strOverallProgress)
    strOverallProgress = "Importing Queries (Step 1 of 5)...<br/>"
    Dim counter : counter = 0
    Dim folder : Set folder = objFileSystem.GetFolder(strImportPath & "Queries\")
    Dim objFileSystem : Set objFileSystem = CreateObject("Scripting.FileSystemObject")
    Dim file
    Dim strQueryName
    For Each file in folder.Files
        objProgressWindow.Document.Body.InnerHTML = strOverallProgress & counter + 1 & " of " & folder.Files.Count
        strQueryName = objFileSystem.GetBaseName(file.Name)
        objAccess.LoadFromText acQuery, strQueryName, file.Path
        counter = counter + 1
    Next
End Sub

Private Sub ImportForms(objAccess, objDatabase, objProgressWindow, strImportPath, strOverallProgress)
    strOverallProgress = strOverallProgress & "Importing Forms (Step 2 of 5)...<br/>"
    Dim counter : counter = 0
    Dim folder : Set folder = objFileSystem.GetFolder(strImportPath & "Forms\")
    Dim objFileSystem : Set objFileSystem = CreateObject("Scripting.FileSystemObject")
    Dim file
    Dim strFormName
    For Each file in folder.Files
        objProgressWindow.Document.Body.InnerHTML = strOverallProgress & counter + 1 & " of " & folder.Files.Count
        strFormName = objFileSystem.GetBaseName(file.Name)
        objAccess.LoadFromText acForm, strFormName, file.Path
        counter = counter + 1
    Next
End Sub

Private Sub ImportReports(objAccess, objDatabase, objProgressWindow, strImportPath, strOverallProgress)
    strOverallProgress = strOverallProgress & "Importing Reports (Step 3 of 5)...<br/>"
    Dim counter : counter = 0
    Dim folder : Set folder = objFileSystem.GetFolder(strImportPath & "Reports\")
    Dim objFileSystem : Set objFileSystem = CreateObject("Scripting.FileSystemObject")
    Dim file
    Dim strReportName
    For Each file in folder.Files
        objProgressWindow.Document.Body.InnerHTML = strOverallProgress & counter + 1 & " of " & folder.Files.Count
        strReportName = objFileSystem.GetBaseName(file.Name)
        objAccess.LoadFromText acReport, strReportName, file.Path
        counter = counter + 1
    Next
End Sub

Private Sub ImportMacros(objAccess, objDatabase, objProgressWindow, strImportPath, strOverallProgress)
    strOverallProgress = strOverallProgress & "Importing Macros (Step 4 of 5)...<br/>"
    Dim counter : counter = 0
    Dim folder : Set folder = objFileSystem.GetFolder(strImportPath & "Macros\")
    Dim objFileSystem : Set objFileSystem = CreateObject("Scripting.FileSystemObject")
    Dim file
    Dim strMacroName
    For Each file in folder.Files
        objProgressWindow.Document.Body.InnerHTML = strOverallProgress & counter + 1 & " of " & folder.Files.Count
        strMacroName = objFileSystem.GetBaseName(file.Name)
        objAccess.LoadFromText acMacro, strMacroName, file.Path
        counter = counter + 1
    Next
End Sub

Private Sub ImportModules(objAccess, objDatabase, objProgressWindow, strImportPath, strOverallProgress)
    strOverallProgress = strOverallProgress & "Importing Modules (Step 5 of 5)...<br/>"
    Dim counter : counter = 0
    Dim folder : Set folder = objFileSystem.GetFolder(strImportPath & "Modules\")
    Dim objFileSystem : Set objFileSystem = CreateObject("Scripting.FileSystemObject")
    Dim file
    Dim strModuleName
    For Each file in folder.Files
        objProgressWindow.Document.Body.InnerHTML = strOverallProgress & counter + 1 & " of " & folder.Files.Count
        strModuleName = objFileSystem.GetBaseName(file.Name)
        objAccess.LoadFromText acModule, strModuleName, file.Path
        counter = counter + 1
    Next

    ' We need to compile the database whenever any module code changes.
    If Not objAccess.IsCompiled Then
        objAccess.RunCommand acCmdCompileAndSaveAllModules
    End If
End Sub

【讨论】:

以上是关于如何在 Access 开发中使用版本控制?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Access 开发中使用版本控制?

如何在 Access 2003 中实现版本控制?

Microsoft Access 开发的版本控制?

如何避免在版本控制中存储密码?

在Xcode中使用Git进行源码版本控制

在Xcode中使用Git进行源码版本控制