使用 System.Data.SQLite 和 C# 检索 BLOB

Posted

技术标签:

【中文标题】使用 System.Data.SQLite 和 C# 检索 BLOB【英文标题】:Retrieving a BLOB with System.Data.SQLite and C# 【发布时间】:2013-12-11 20:58:36 【问题描述】:

我是 SQLITE 的新手,我目前正在使用 System.Data.SQLite.dll 版本 1.0.89.0 strong>C# 项目。 我的数据库包含一个简单的表 'files',其中包含以下列:

[id] VARCHAR(50) NOT NULL [块] 整数非空 [内容] BLOB NOT NULL [大小] 整数非空 [date_ins] 日期时间不为空

主键(id,chunk)

我创建了一个类 (OfflineStorage) 来以 BLOBS 的形式在该表中添加和检索文件。 Save 方法工作正常,但 LoadGetStream 扩展方法上生成 InvalidCastException

public class OfflineStorage


    private static string l_strConnectionTemplate = "Data Source=0;Version=3;Password=\"1\";";

    private string l_strConnection;
    private int SQLITE_MAX_BLOB_LENGTH;

    private string l_strCreateTable = @"CREATE TABLE IF NOT EXISTS [files]" +
                                       "( " +
                                       "     [id] VARCHAR(50) NOT NULL, " +
                                       "     [chunk] INTEGER NOT NULL, " +
                                       "     [content] BLOB NOT NULL, " +
                                       "     [size] INTEGER NOT NULL, " +
                                       "     [date_ins] DATETIME NOT NULL, " +
                                       "     PRIMARY KEY(id,chunk) " +
                                       ")";

    private string l_strSelectQuery = @"SELECT chunk, content, size FROM files WHERE id = @id ORDER BY chunk";
    private string l_strUpdateQuery = @"UPDATE files SET content = content || @new_content, size = size + @size WHERE id = @id AND chunk = @chunk";
    private string l_strInsertQuery = @"INSERT INTO files(id, chunk, content, size, date_ins) VALUES(@id, @chunk, @new_content, @size, DATETIME('now'))";

    public OfflineStorage(string strFilename, string strPassword = "")
    
        SQLiteConnection l_objConnection = null;
        if (!File.Exists(strFilename))
        
            l_strConnection = string.Format(l_strConnectionTemplate, strFilename, "");
            SQLiteConnection.CreateFile(strFilename);
            l_objConnection = new SQLiteConnection(l_strConnection);
            l_objConnection.SetPassword(strPassword);
            l_objConnection.Close();
        

        l_strConnection = string.Format(l_strConnectionTemplate, strFilename, strPassword);
        l_objConnection = getConnection();
        using (SQLiteCommand l_objCommand = new SQLiteCommand(l_strCreateTable, l_objConnection))
        
            l_objCommand.ExecuteNonQuery();
        
        SQLITE_MAX_BLOB_LENGTH = 1000000;

        CloseConnection(l_objConnection);
    

    private SQLiteConnection getConnection()
    
        SQLiteConnection l_objConnection = null;

        try
        
            l_objConnection = new SQLiteConnection(l_strConnection);
            l_objConnection.Open();
            return l_objConnection;
        
        catch (Exception ex)
        
            CloseConnection(l_objConnection);

            throw new OfflineStorageException("Local Service open db error.", ex);
        
    

    private void CloseConnection(SQLiteConnection objConnection)
    
        if (objConnection != null)
        
            objConnection.Close();
            objConnection = null;
        
    

    public long Load(string strID, Stream objStream)
    
        if (!objStream.CanWrite)
            throw new NotSupportedException("Stream not writable.");

        SQLiteConnection l_objConnection = getConnection();

        // Columns Identifier (name of file)
        SQLiteParameter l_objID = new SQLiteParameter("@id", DbType.String);
        l_objID.Value = strID;

        SQLiteCommand l_objCommand = new SQLiteCommand(l_strSelectQuery, l_objConnection);
        l_objCommand.Parameters.Add(l_objID);

        // Load File Records
        SQLiteDataReader l_objReader;
        try
        
            l_objReader = l_objCommand.ExecuteReader();
        
        catch (Exception ex)
        
            CloseConnection(l_objConnection);
            throw new OfflineStorageException("SQLite exception.", ex);
        

        long l_lFileLength = 0;     // Complete file length
        int  l_iDBChunk = -1;       // Current chunk on database
        int  l_iChunk = 0;          // Current 'sub chunk'
        long l_lChunkLength = -1;   // Current 'sub chunk' length

        try
        
            // For each record of current file selected by identifier
            while (l_objReader.Read())
            
                l_iDBChunk = l_objReader.GetInt32(0);       // Chunk ID
                l_lChunkLength = l_objReader.GetInt64(2);   // Chunk size 
                Trace.Assert(l_iChunk == l_iDBChunk);       // Compare expected Chunck with Database ID Chunk
                l_lFileLength += l_objReader.GetStream(objStream, 1, l_lChunkLength); // Load chunk
                l_iChunk++;
            
        
        catch (Exception ex)
        
            string l_strMessage = string.Format("SQLite exception on file 0, DB chunk 1: \n2", strID, l_iDBChunk, ex.Message);
            throw new OfflineStorageException(l_strMessage, ex);
        
        finally
        
            l_objReader.Close();
            l_objCommand.Dispose();
            CloseConnection(l_objConnection);
        

        if (l_iChunk < 1)
        
            string l_strMessage = string.Format("File 0 not readed on db.", strID);
            throw new OfflineStorageException(l_strMessage);
        

        return l_lFileLength;
    

    public void Save(string strID, Stream objStream, bool bOverwrite = false)
    
        const int CHUNK_SIZE = 8 * 1024;

        if (!objStream.CanRead)
            throw new NotSupportedException("Stream not readable.");
        long l_lOldPosition = objStream.Position;

        SQLiteConnection l_objConnection = getConnection();
        byte[] lar_byBuffer = new byte[CHUNK_SIZE];

        SQLiteParameter l_objID = new SQLiteParameter("@id", DbType.String);
        l_objID.Value = strID;
        SQLiteParameter l_objContent = new SQLiteParameter("@new_content", DbType.Binary);
        l_objContent.Value = lar_byBuffer;
        SQLiteParameter l_objChunk = new SQLiteParameter("@chunk", DbType.Int32);
        SQLiteParameter l_objSize = new SQLiteParameter("@size", DbType.Int32);

        SQLiteCommand l_objCommand = new SQLiteCommand(l_strInsertQuery, l_objConnection);
        l_objCommand.Parameters.Add(l_objID);
        l_objCommand.Parameters.Add(l_objContent);
        l_objCommand.Parameters.Add(l_objChunk);
        l_objCommand.Parameters.Add(l_objSize);

        int  l_iReturn, l_lBytesRead;
        int  l_iChunk = 0;          // Current 'sub chunk'
        int  l_iDBChunk = 0;        // Current chunk on database
        long l_lDBChunkLength = 0;  // Current length of chunk
        l_objChunk.Value = l_iDBChunk;

        //Transaction
        using (SQLiteTransaction l_objTransaction = l_objConnection.BeginTransaction())
        
            // Read File from stream
            while ((l_lBytesRead = objStream.Read(lar_byBuffer, 0, lar_byBuffer.Length)) > 0)
            
                // Check for next Chunk
                if ((l_lDBChunkLength + l_lBytesRead) >= SQLITE_MAX_BLOB_LENGTH)
                
                    l_objCommand.CommandText = l_strInsertQuery;
                    l_iChunk = 0;       // reset 'sub chunk' counter
                    l_lDBChunkLength = 0; // reset chunk size
                    l_iDBChunk++;       // increase chunk ID
                    l_objChunk.Value = l_iDBChunk;
                

                l_lDBChunkLength += l_lBytesRead;   // Increase length of chunk
                l_objContent.Size = l_lBytesRead;   // Length of Content field
                l_objSize.Value = l_lBytesRead;     // Chunk lenght (write on 'size' column)

                #region WRITE
                try
                
                    l_iReturn = l_objCommand.ExecuteNonQuery();
                    if (l_iChunk == 0)
                    
                        l_objCommand.CommandText = l_strUpdateQuery;
                    
                
                catch (Exception ex)
                
                    l_objTransaction.Rollback();
                    CloseConnection(l_objConnection);
                    string l_strMessage = string.Format("SQLite exception on file 0, DB chunk 1, chunk 2: \n3", strID, l_iDBChunk, l_iChunk, ex.Message);
                    throw new OfflineStorageException(l_strMessage, ex);
                

                if (l_iReturn != 1)
                
                    l_objTransaction.Rollback();
                    CloseConnection(l_objConnection);
                    string l_strMessage = string.Format("DB chunk 1, chunk 2 of file 0 not inserted on db.", strID, l_iDBChunk, l_iChunk);
                    throw new OfflineStorageException(l_strMessage);
                
                #endregion WRITE

                l_iChunk++;
            

            l_objTransaction.Commit();
        
        l_objCommand.Dispose();
        CloseConnection(l_objConnection);
        objStream.Position = l_lOldPosition;
    

DB Data Reader 扩展类:

public static class DbDataReaderExtension

    public static long GetStream(this DbDataReader objReader, System.IO.Stream objStream, int iIndex = 0, long lFileLength = -1)
    
        const int CHUNK_SIZE = 7 * 1024;

        byte[] lar_byBuffer = new byte[CHUNK_SIZE]; // Buffer
        long l_lBytesRead;          // Bytes readed from SQLite database column
        long l_lFieldOffset = 0;    // Field offset on database column
        long l_lBytesRemainig = lFileLength;

        while ((l_lBytesRead = objReader.GetBytes(iIndex, l_lFieldOffset, lar_byBuffer, 0, lar_byBuffer.Length)) > 0)
        
            l_lFieldOffset += l_lBytesRead; // prepare next offset

            if (l_lBytesRemainig > 0)       // check if a FileLength was set
            
                l_lBytesRemainig -= l_lBytesRead; // remove readed bytes
                if (l_lBytesRemainig < 0) // Cut last bytes not valid if file is bigger than column size
                    l_lBytesRead += l_lBytesRemainig;
            

            // write only valid bytes 
            objStream.Write(lar_byBuffer, 0, (int)l_lBytesRead);
        

        return lFileLength < 0 ? l_lFieldOffset : lFileLength;
    

我发现这个异常是因为ReadBytes方法(SQLiteDataReader)调用了VerifyType而产生的

private TypeAffinity VerifyType(int i, DbType typ)

  CheckClosed();
  CheckValidRow();

  TypeAffinity affinity = GetSQLiteType(i).Affinity;

  switch (affinity)
  
    ...        
    case TypeAffinity.Text:
      if (typ == DbType.SByte) return affinity;
      if (typ == DbType.String) return affinity;
      if (typ == DbType.SByte) return affinity;
      if (typ == DbType.Guid) return affinity;
      if (typ == DbType.DateTime) return affinity;
      if (typ == DbType.Decimal) return affinity;
      break;
    case TypeAffinity.Blob:
      ...
  

  throw new InvalidCastException();

在这个函数中,不期望 typ 等于 DbType.Binary 并且 affinity 等于 TypeAffinity.Text。 谁能帮我理解这个问题? 谢谢

【问题讨论】:

【参考方案1】:

好的,这就是我添加和检索 blob 的方式。

我想这一切都取决于 byte[] 是否是可管理的大小。这适用于被序列化到数据库的小对象。

使用 GetDataTable 获取数据,然后在有问题的行上使用以下内容提取字节数组:

    public byte[] getByteArray(DataRow row, int offset)
    
        object blob = row[offset];
        if (blob == null) return null;
        byte[] arData = (byte[])blob;
        return arData;
    

这就是我添加它们的方式:

    private System.Object syncLock = new System.Object();

    public int ExecuteNonQueryWithBlob(string sql, string blobFieldName, byte[] blob)
    
        lock (syncLock)
        
            try
            
                using (var c = new SQLiteConnection(dbConnection))
                
                    using (var cmd = new SQLiteCommand(sql, c))
                    
                        cmd.Connection.Open();
                        cmd.Parameters.AddWithValue("@" + blobFieldName, blob);
                        return cmd.ExecuteNonQuery();
                    
                
            
            catch (Exception e)
            
                Console.WriteLine(e.Message);
                return 0;
            
        
    

    public DataTable GetDataTable(string sql)
    
        lock (syncLock)
        
            try
            
                DataTable dt = new DataTable();
                using (var c = new SQLiteConnection(dbConnection))
                
                    c.Open();
                    using (SQLiteCommand cmd = new SQLiteCommand(sql, c))
                    
                        using (SQLiteDataReader rdr = cmd.ExecuteReader())
                        
                            dt.Load(rdr);
                            return dt;
                        
                    
                
            
            catch (Exception e)
            
                Console.WriteLine(e.Message);
                return null;
            
        
    

【讨论】:

我尝试了您关于使用 DataTable 的建议,它工作正常!谢谢!

以上是关于使用 System.Data.SQLite 和 C# 检索 BLOB的主要内容,如果未能解决你的问题,请参考以下文章

C# System.Data.SQLite:并发冲突:UpdateCommand 影响了预期的 1 条记录中的 0 条

System.Data.SQLite数据库简介

microsoft visual c++2010 x86与x64的区别

使用 System.Data.SQLite 和 Entity Framework 6 的简单示例

无法在 System.Data.SQLite 中使用 REGEXP 运算符

C#EF操作数据库,问题求助