Извлечение файлов из поля вложения в базе данных Access

мы работаем над проектом, где нам нужно перенести данные, хранящиеся в базе данных Access, в базу данных кэша. База данных Access содержит столбцы с типом данных Attachment; некоторые кортежи содержат несколько вложений. Я могу получить имена файлов этих файлов с помощью .FileName, но я не уверен, как определить, когда заканчивается один файл и начинается другой в .FileData.

Я использую следующее Для получения этих данных:

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();

3 ответов


(мой первоначальный ответ на этот вопрос вводил в заблуждение. Он работал нормально для PDF-файлов, которые впоследствии были открыты с помощью Adobe Reader, но он не всегда работал должным образом для других типов файлов. Ниже приводится исправленная версия.)

к сожалению, мы не можем напрямую получить содержимое файла в доступа Attachment поле с использованием OleDb. Компонент Access Database Engine добавляет некоторые метаданные к двоичному содержимому файла, и эти метаданные если мы извлечем .FileData через OleDb.

чтобы проиллюстрировать, документ с именем " Document1.pdf " сохраняется в поле вложения с помощью пользовательского интерфейса Access. Начало этого файла PDF выглядит следующим образом:

Original.png

если мы используем следующий код, чтобы попытаться извлечь файл PDF на диске

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();
        }
    }
}

затем полученный файл будет содержать метаданные в начале файла (20 байт в этом дело)

FromFileData.png

Adobe Reader может открыть этот файл, потому что он достаточно надежен, чтобы игнорировать любой "мусор", который может появиться в файле перед подписью "%PDF-1.4". К сожалению, не все форматы файлов и приложения настолько прощают посторонние байты в начале файла.

на только официальный™ способ извлечения файлов из Attachment поле в Access, чтобы использовать .SaveToFile метод ACE DAO


//Привет,

/ / Мне потребовалось некоторое время, чтобы собрать информацию, чтобы получить файл, хранящийся в поле вложения, поэтому я просто подумал, что id разделяет его.

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

    }
}

}

//надеюсь, это поможет кто-то


следующий код проходит через все записи таблицы данных базы данных Microsoft Access и назначает каждую строку набору записей. Проходит через все вложения, которые сохраняются в поле "документы". Затем извлекает и сохраняет эти файлы на диск. Этот код является расширением кода, введенного "Gord Thompson" выше. Единственное, что я сделал, это написал код для Visual Basic.NET.

Imports Microsoft.Office.Interop.Access.Dao

поместите ссылку на Dao, используя приведенную выше строку код.

'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