Extracción de archivos de un campo Adjunto en una base de datos de Access

Estamos trabajando en un proyecto en el que tenemos que migrar los datos almacenados en una base de datos de Access a una base de datos de caché. La base de datos de Access contiene columnas con un tipo de datos de Attachment ; algunas de las tuplas contienen múltiples archivos adjuntos. Puedo obtener los nombres de archivo de estos archivos utilizando .FileName , pero no estoy seguro de cómo determinar cuándo termina un archivo y comienza otro en .FileData .

Estoy usando lo siguiente para obtener esta información:

 System.Data.OleDb.OleDbCommand command= new System.Data.OleDb.OleDbCommand(); command.CommandText = "select [Sheet1].[pdf].FileData,* from [Sheet1]"; command.Connection = conn; System.Data.OleDb.OleDbDataReader rdr = command.ExecuteReader(); 

(Mi respuesta original a esta pregunta fue engañosa. Funcionó bien para los archivos PDF que posteriormente se abrieron con Adobe Reader, pero no siempre funcionó correctamente para otros tipos de archivos. La siguiente es la versión corregida).

Lamentablemente, no podemos recuperar directamente el contenido de un archivo en un campo de Acceso de Accesos utilizando OleDb. El Motor de base de datos de Access incluye algunos metadatos a los contenidos binarios del archivo, y esos metadatos se incluyen si recuperamos .FileData través de OleDb.

Para ilustrar, un documento llamado “Document1.pdf” se guarda en un campo de Archivo adjunto utilizando la IU de acceso. El comienzo de ese archivo PDF se ve así:

Original.png

Si usamos el siguiente código para tratar de extraer el archivo PDF en el disco

 using (OleDbCommand cmd = new OleDbCommand()) { cmd.Connection = con; cmd.CommandText = "SELECT Attachments.FileData " + "FROM AttachTest " + "WHERE Attachments.FileName='Document1.pdf'"; using (OleDbDataReader rdr = cmd.ExecuteReader()) { rdr.Read(); byte[] fileData = (byte[])rdr[0]; using (var fs = new FileStream( @"C:\Users\Gord\Desktop\FromFileData.pdf", FileMode.Create, FileAccess.Write)) { fs.Write(fileData, 0, fileData.Length); fs.Close(); } } } 

entonces el archivo resultante incluirá los metadatos al principio del archivo (20 bytes en este caso)

FromFileData.png

Adobe Reader puede abrir este archivo porque es lo suficientemente robusto como para ignorar cualquier “basura” que pueda aparecer en el archivo antes de la firma ‘% PDF-1.4’. Lamentablemente, no todos los formatos de archivos y aplicaciones son tan indulgentes con los bytes extraños al principio del archivo.

La única forma de Official ™ de extraer archivos de un campo de .SaveToFile Attachment en Access es usar el método .SaveToFile de un objeto ACE DAO Field2 , de esta manera:

 // required COM reference: Microsoft Office 14.0 Access Database Engine Object Library // // using Microsoft.Office.Interop.Access.Dao; ... var dbe = new DBEngine(); Database db = dbe.OpenDatabase(@"C:\Users\Public\Database1.accdb"); Recordset rstMain = db.OpenRecordset( "SELECT Attachments FROM AttachTest WHERE ID=1", RecordsetTypeEnum.dbOpenSnapshot); Recordset2 rstAttach = rstMain.Fields["Attachments"].Value; while ((!"Document1.pdf".Equals(rstAttach.Fields["FileName"].Value)) && (!rstAttach.EOF)) { rstAttach.MoveNext(); } if (rstAttach.EOF) { Console.WriteLine("Not found."); } else { Field2 fld = (Field2)rstAttach.Fields["FileData"]; fld.SaveToFile(@"C:\Users\Gord\Desktop\FromSaveToFile.pdf"); } db.Close(); 

Tenga en cuenta que si intenta utilizar el .Value .Value del objeto Field2, obtendrá los metadatos al principio de la secuencia de bytes; el proceso .SaveToFile es lo que lo elimina.

//Hola,

// Me tomó un tiempo juntar información para recuperar un archivo almacenado desde el campo de adjuntos, así que pensé en compartirlo.

 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Data.OleDb; using System.IO; using System.Diagnostics; namespace AttachCheck { public partial class Form1 : Form { DataSet Set1 = new DataSet(); int ColId; public Form1() { InitializeComponent(); OleDbConnection connect = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source='db/Adb.accdb'"); //set up connection //CL_ID is a fk so attachments can be linked to users OleDbCommand sql = new OleDbCommand("SELECT at_ID, [at_Name].[FileData], [at_Name].[FileName], [at_Name].[FileType] FROM Attachments WHERE at_ID =1;", connect); //adding sql to addapter to be ran OleDbDataAdapter OleDA = new OleDbDataAdapter(sql); //attempting to open connection try { connect.Open(); } catch (Exception err) { System.Console.WriteLine(err); } OleDA.Fill(Set1); //create and fill dataset connect.Close();for (int i = 0; i < Set1.Tables[0].Rows.Count; i++) { System.Console.WriteLine(Set1.Tables[0].Rows[i]["at_Name.FileName"].ToString() + "This is the file name"); // by using a datagrid it allows you to display the attachments and select which to open, the open should be a button. dataGridView1.Rows.Add(new object[] { Set1.Tables[0].Rows[i]["at_ID"].ToString(), Set1.Tables[0].Rows[i]["at_Name.FileName"].ToString(), "Open" }); } } private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e) { DataGridViewCell cell = (DataGridViewCell) dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex]; System.Console.WriteLine(dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex]); string FullRow = dataGridView1.Rows[e.RowIndex].ToString(); //data retrieved from click on datagrid //need to sub string to cut away row index and leave number string SubRow = FullRow.Substring(24, 1); //cutting string down from position 24 for 1 character System.Console.WriteLine(SubRow + " This is Row"); // int RowId = int.Parse(SubRow); //turn row number from string into integer that can be used string FullRow2 = dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex].ToString(); //data retrieved from click on datagrid //need to sub string to cut away row index and leave number string SubRow2 = FullRow2.Substring(37, 1); //cutting string down from position 24 for 1 character System.Console.WriteLine(SubRow2 + " This is Column"); // int ColId = int.Parse(SubRow2); //turn row number from string into integer that can be used if (ColId == 2) { string fileName = Set1.Tables[0].Rows[RowId]["at_Name.FileName"].ToString(); //assign the file to variable //retrieving the file contents from the database as an array of bytes byte[] fileContents = (byte[])Set1.Tables[0].Rows[RowId]["at_Name.FileData"]; fileContents = GetFileContents(fileContents); //send filecontents array to be decrypted string fileType = Set1.Tables[0].Rows[RowId]["at_Name.FileType"].ToString(); DisplayTempFile(fileName, fileContents, fileType); //forward the file type to display file contents } } private const int CONTENT_START_INDEX_DATA_OFFSET = 0; //values used for decoding private const int UNKNOWN_DATA_OFFSET = 4; //the files private const int EXTENSION_LENGTH_DATA_OFFSET = 8; //storedw within the access database private const int EXTENSION_DATA_OFFSET = 12; //and this one private byte[] GetFileContents(byte[] fileContents) { int contentStartIndex = BitConverter.ToInt32(fileContents, CONTENT_START_INDEX_DATA_OFFSET); //'The next four bytes represent a value whose meaning is unknown at this stage, although it may represent a Boolean value indicating whether the data is compressed or not. int unknown = BitConverter.ToInt32(fileContents, UNKNOWN_DATA_OFFSET); //'The next four bytes contain the the length, in characters, of the file extension. int extensionLength = BitConverter.ToInt32(fileContents, EXTENSION_LENGTH_DATA_OFFSET); //'The next field in the header is the file extension, not including a dot but including a null terminator. //'Characters are Unicode so double the character count to get the byte count. string extension = Encoding.Unicode.GetString(fileContents, EXTENSION_DATA_OFFSET, extensionLength * 2); return fileContents.Skip(contentStartIndex).ToArray(); } private void DisplayTempFile(string fileName, byte[] fileContents, string fileType) { // System.Console.WriteLine(fileName + "File Name"); // System.Console.WriteLine(fileType + "File Type"); // System.Console.WriteLine(fileContents + "File Contents"); string tempFolderPath = Path.GetTempPath(); //creating a temperary path for file to be opened from string tempFilePath = Path.Combine(tempFolderPath, fileName); // assigning the file to the path if (!string.IsNullOrEmpty(tempFilePath)) //checking the temp file exists { tempFilePath = Path.Combine(tempFolderPath, //combines the strings 0 and 1 below String.Format("{0}{1}", Path.GetFileNameWithoutExtension(fileName), //0 Path.GetExtension(fileName))); //1 } //System.Console.WriteLine(tempFolderPath + " tempFolderPath"); //System.Console.WriteLine(tempFilePath + " tempFilePath"); //'Save the file and open it. File.WriteAllBytes(tempFilePath, fileContents); //creates new file, writes bytes array to it then closes the file //File.ReadAllBytes(tempFilePath); //'Open the file. System.Diagnostics.Process attachmentProcess = Process.Start(tempFilePath); //chooses the program to open the file if available on the computer } } 

}

//Espero que esto ayude a alguien

El siguiente código recorre todos los registros de la tabla de datos de la base de datos de Microsoft Access y asigna cada fila a un conjunto de registros. Pasa por todos los archivos adjuntos que se guardan en el campo “Documentos”. Luego extrae y guarda esos archivos en el disco. Este código es una extensión del código presentado por “Gord Thompson” arriba. Lo único que hice fue escribir el código para Visual Basic.NET.

 Imports Microsoft.Office.Interop.Access.Dao 

Ponga una referencia a Dao usando la línea de código anterior.

 'Visual Basic.NET Private Sub ReadAttachmentFiles() 'required COM reference: Microsoft Office 14.0 Access Database Engine Object Library 'define a new database engine and a new database Dim dbe = New DBEngine Dim db As Database = dbe.OpenDatabase("C:\Users\Meisam\Documents\Databases\myDatabase.accdb") 'define the main recordset object for each row Dim rstMain As Recordset = db.OpenRecordset( _ "SELECT * FROM Companies", _ RecordsetTypeEnum.dbOpenSnapshot) 'evaluate whether the recordset is empty of records If Not (rstMain.BOF And rstMain.EOF) Then 'if not empty, then move to the first record rstMain.MoveFirst() 'do until the end of recordset is not reached Do Until rstMain.EOF Dim myID As Integer = -1 ' ID is the name of primary field with uniqe values field myID = CInt(rstMain.Fields("ID").Value) 'define the secondary recordset object for the attachment field "Docs" Dim rstAttach As Recordset2 = rstMain.Fields("Docs").Value 'evaluate whether the recordset is empty of records If Not (rstAttach.BOF And rstAttach.EOF) Then 'if not empty, then move to the first record rstAttach.MoveFirst() 'do until the end of recordset is not reached Do Until rstAttach.EOF 'get the filename for each attachment in the field "Docs" Dim fileName As String = rstAttach.Fields("FileName").Value Dim fld As Field2 = rstAttach.Fields("FileData") fld.SaveToFile("C:\Users\Meisam\Documents\test\" & myID & "_" & fileName) rstAttach.MoveNext() Loop End If rstMain.MoveNext() Loop End If 'close the database db.Close() End Sub