Импорт/экспорт отношений в MS Access
У меня есть несколько файлов mdb с точной структурой таблицы. Я должен изменить первичный ключ основной таблицы от autonumber до номера во всех из них, что означает, что я должен:
- Отбросьте все отношения, которые имеет главная таблица
- Измените основную таблицу
- Создайте отношения снова,... для всех таблиц.
Есть ли способ экспортировать отношения из одного файла и импортировать их ко всем остальным?
Я уверен, что это можно сделать с помощью некоторого кода макро /vb. Кто-нибудь имеет пример, который я мог бы использовать?
Спасибо.
Ответы
Ответ 1
Не полное решение, но это может заставить вас идти...
Следующая функция распечатает метаданные для всех отношений. Измените это, чтобы сохранить файл в любом формате, который вы предпочитаете (CSV, разделитель табуляции, XML и т.д.):
Function PrintRelationships()
For Each rel In CurrentDb.Relations
With rel
Debug.Print "Name: " & .Name
Debug.Print "Attributes: " & .Attributes
Debug.Print "Table: " & .Table
Debug.Print "ForeignTable: " & .ForeignTable
Debug.Print "Fields:"
For Each fld In .Fields
Debug.Print "Field: " & fld.Name
Next
End With
Next
End Function
Эта функция отключит все отношения в базе данных:
Function DropRelationships()
With CurrentDb
For Each rel In .Relations
.Relations.Delete Name:=rel.Name
Next
End With
End Function
Эта функция создаст отношения. Вам придется перебирать файл сохраненных данных отношений.
Function CreateRelationships()
With CurrentDb
Set rel = .CreateRelation(Name:="[rel.Name]", Table:="[rel.Table]", ForeignTable:="[rel.FireignTable]", Attributes:=[rel.Attributes])
rel.Fields.Append rel.CreateField("[fld.Name for relation]")
rel.Fields("[fld.Name for relation]").ForeignName = "[fld.Name for relation]"
.Relations.Append rel
End With
End Function
Обработка ошибок и IO опущены из-за ограничений по времени (нужно положить детей в постель).
Надеюсь, что это поможет.
Ответ 2
Мне кажется, что вы можете использовать резервную копию файла, созданного до любых изменений, для восстановления индексов и отношений. Вот несколько заметок.
Sub RunExamples()
Dim strCopyMDB As String
Dim fs As FileSystemObject
Dim blnFound As Boolean
Dim i
' This code is not intended for general users, it is sample code built '
' around the OP '
'You will need a reference to the Microsoft DAO 3.x Object Library '
'This line causes an error, but it will run '
'It is not suitable for anything other than saving a little time '
'when setting up a new database '
Application.References.AddFromFile ("C:\Program Files\Common Files\Microsoft Shared\DAO\dao360.dll")
'You must first create a back-up copy '
Set fs = CreateObject("Scripting.FileSystemObject")
strCopyMDB = CurrentProject.Path & "\c.mdb"
blnFound = fs.FileExists(strCopyMDB)
i = 0
Do While blnFound
strCopyMDB = CurrentProject.Path & "\c" & i & ".mdb"
blnFound = fs.FileExists(strCopyMDB)
Loop
fs.CopyFile CurrentProject.FullName, strCopyMDB
ChangeTables
AddIndexesFromBU strCopyMDB
AddRelationsFromBU strCopyMDB
End Sub
Sub ChangeTables()
Dim db As Database
Dim tdf As DAO.TableDef
Dim rel As DAO.Relation
Dim fld As DAO.Field
Dim ndx As DAO.Index
Dim i
Set db = CurrentDb
'In order to programmatically change an autonumber, '
'it is necessary to delete any relationships that '
'depend on it. '
'When deleting from a collection, it is best '
'to iterate backwards. '
For i = db.Relations.Count - 1 To 0 Step -1
db.Relations.Delete db.Relations(i).Name
Next
'The indexes must also be deleted or the '
'number cannot be changed. '
For Each tdf In db.TableDefs
If Left(tdf.Name, 4) <> "Msys" Then
For i = tdf.Indexes.Count - 1 To 0 Step -1
tdf.Indexes.Delete tdf.Indexes(i).Name
Next
tdf.Indexes.Refresh
For Each fld In tdf.Fields
'If the field is an autonumber, '
'use code supplied by MS to change the type '
If (fld.Attributes And dbAutoIncrField) Then
AlterFieldType tdf.Name, fld.Name, "Long"
End If
Next
End If
Next
End Sub
Sub AddIndexesFromBU(MDBBU)
Dim db As Database
Dim dbBU As Database
Dim tdf As DAO.TableDef
Dim tdfBU As DAO.TableDef
Dim ndx As DAO.Index
Dim ndxBU As DAO.Index
Dim i
Set db = CurrentDb
'This is the back-up made before starting '
Set dbBU = OpenDatabase(MDBBU)
For Each tdfBU In dbBU.TableDefs
'Skip system tables '
If Left(tdfBU.Name, 4) <> "Msys" Then
For i = tdfBU.Indexes.Count - 1 To 0 Step -1
'Get each index from the back-up '
Set ndxBU = tdfBU.Indexes(i)
Set tdf = db.TableDefs(tdfBU.Name)
Set ndx = tdf.CreateIndex(ndxBU.Name)
ndx.Fields = ndxBU.Fields
ndx.IgnoreNulls = ndxBU.IgnoreNulls
ndx.Primary = ndxBU.Primary
ndx.Required = ndxBU.Required
ndx.Unique = ndxBU.Unique
' and add it to the current db '
tdf.Indexes.Append ndx
Next
tdf.Indexes.Refresh
End If
Next
End Sub
Sub AddRelationsFromBU(MDBBU)
Dim db As Database
Dim dbBU As Database
Dim rel As DAO.Relation
Dim fld As DAO.Field
Dim relBU As DAO.Relation
Dim i, j, f
On Error GoTo ErrTrap
Set db = CurrentDb
'The back-up again '
Set dbBU = OpenDatabase(MDBBU)
For i = dbBU.Relations.Count - 1 To 0 Step -1
'Get each relationship from bu '
Set relBU = dbBU.Relations(i)
Debug.Print relBU.Name
Set rel = db.CreateRelation(relBU.Name, relBU.Table, relBU.ForeignTable, relBU.Attributes)
For j = 0 To relBU.Fields.Count - 1
f = relBU.Fields(j).Name
rel.Fields.Append rel.CreateField(f)
rel.Fields(f).ForeignName = relBU.Fields(j).ForeignName
Next
'For some relationships, I am getting error'
'3284 Index already exists, which I will try'
'and track down tomorrow, I hope'
'EDIT: Apparently this is due to Access creating hidden indexes
'and tracking these down would take quite a bit of effort
'more information can be found in this link:
'http://groups.google.ie/group/microsoft.public.access/browse_thread/thread/ca58ce291bdc62df?hl=en&ie=UTF-8&q=create+relation+3284+Index+already+exists
'It is an occasional problem, so I've added an error trap
'Add the relationship to the current db'
db.Relations.Append rel
Next
ExitHere:
Exit Sub
ErrTrap:
If Err.Number = 3284 Then
Debug.Print relBU.Name, relBU.Table, relBU.ForeignTable, relBU.Attributes
Resume Next
Else
'this is not a user sub, so may as well ... '
Stop
End If
End Sub
Sub AlterFieldType(TblName As String, FieldName As String, _
NewDataType As String)
'http://support.microsoft.com/kb/128016'
Dim db As Database
Dim qdf As QueryDef
Set db = CurrentDb()
' Create a dummy QueryDef object.'
Set qdf = db.CreateQueryDef("", "Select * from PROD1")
' Add a temporary field to the table.'
qdf.SQL = "ALTER TABLE [" & TblName & "] ADD COLUMN AlterTempField " & NewDataType
qdf.Execute
' Copy the data from old field into the new field.'
qdf.SQL = "UPDATE DISTINCTROW [" & TblName _
& "] SET AlterTempField = [" & FieldName & "]"
qdf.Execute
' Delete the old field.'
qdf.SQL = "ALTER TABLE [" & TblName & "] DROP COLUMN [" _
& FieldName & "]"
qdf.Execute
' Rename the temporary field to the old field name.'
db.TableDefs("[" & TblName & "]").Fields("AlterTempField").Name = FieldName
End Sub
Ответ 3
Основываясь на ответе @Patrick Cuff, я создал пару скриптов: один экспортирует в xml, другой читает этот xml и анализирует его в базе данных
VBScript для экспорта отношений из MsAccess в XML
'supply the Access Application object into this function and path to file to which the output should be written
Function ExportRelationships(oApplication, sExportpath)
Dim relDoc, myObj
Set relDoc = CreateObject("Microsoft.XMLDOM")
relDoc.appendChild relDoc.createElement("Relations") 'create root xml element
'loop though all the relations
For Each myObj In oApplication.CurrentDb.Relations
If Not Left(myObj.Name, 4) = "MSys" Then 'exclude system relations
Dim relName, relAttrib, relTable, relFoTable, fld
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
'in case the relationship works with more fields
For Each fld In myObj.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
Next
End If
Next
relDoc.insertBefore relDoc.createProcessingInstruction("xml","version='1.0'"), relDoc.childNodes(0)
relDoc.Save sExportpath
End Function
VBScript для импорта отношений в MsAccess из XML
'supply the Access Application object into this function and path to file from which the input should be read
Function ImportRelationships(oApplication, sImportpath)
Dim relDoc, myObj
Set relDoc = CreateObject("Microsoft.XMLDOM")
relDoc.Load(sImportpath)
Dim xmlRel, xmlField, accessRel, relTable, relName, relFTable, relAttr, i
'loop through every Relation node inside .xml file
For Each xmlRel in relDoc.selectNodes("/Relations/Relation")
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
'create the relationship object
Set accessRel = oApplication.CurrentDb.CreateRelation(relName, relTable, relFTable, relAttr)
'in case the relationship works with more fields
For Each xmlField In xmlRel.selectNodes("Field")
accessRel.Fields.Append accessRel.CreateField(xmlField.selectSingleNode("Name").Text)
accessRel.Fields(xmlField.selectSingleNode("Name").Text).ForeignName = xmlField.selectSingleNode("ForeignName").Text
Next
'and finally append the newly created relationship to the database
oApplication.CurrentDb.Relations.Append accessRel
Next
End Function
Примечания
Просто чтобы прояснить, что ожидается, будет передано в параметр oApplication
Set oApplication = CreateObject("Access.Application")
oApplication.NewCurrentDatabase path 'new database
oApplication.OpenCurrentDatabase path 'existing database
В случае, если вы используете это из VBA вместо VBScript, вы можете удалить параметр и только обычный объект приложения всюду в коде, где используется oApplication.
Я начал работать над этим кодом, так как мне нужно было реализовать элемент управления версиями в очень сложном проекте MsAccess. Этот пост заставил меня двигаться, есть также хорошие советы о том, как экспортировать/импортировать другие части проекта MsAccess.
Ответ 4
Спасибо за фрагмент кода.
чтобы избавиться от вашей ошибки 3284, я изменил несколько вещей.
Если вы скопируете все индексы из образца mdb, а затем попытаетесь установить отношения, он выдает исключение, поскольку он не ожидает никаких идексов для отношений, когда вы ставите отношения, он ставит свои собственные индексы.
Последующие шаги (предположим, target.mdb и source.mdb):
- Запустите этот код в target.mdb, чтобы удалить все индексы и отношения
frmo
target.mdb
, вызывая ChangeTables
- Вызвать
AddIndexesFromBU
source.mdb и использовать условие
Если ndxBU.Unique
Затем tdf.Indexes.Append ndx
Конец Если это приведет только к уникальному индексу
- вызвать AddRelationsFromBU
source.mdb
и поставить все отношения
- Еще раз вызовите AddIndexesFromBU source.mdb и измените условие на If
не
ndxBU.Unique
Затем
Я также добавил ловушку ошибок, такую же, как AddRelationsFromBU в AddIndexesFromBU, и возобновить следующую, если ans else
Это сработало для меня.