Click here to Skip to main content
15,888,039 members
Articles / Programming Languages / C#
Article

Simple C# Wrapper for SQLite

Rate me:
Please Sign up or sign in to vote.
3.25/5 (20 votes)
16 Aug 2007 152.7K   6.1K   58   32
An article that describes lightweight C# wrapper for SQLite

Introduction

This article describes a very simple wrapper class for SQLite. This class provides only few simple functions: opening and closing database, returning the list of tables and executing queries. Although these are basic functions, they are enough for someone who needs only a storage engine for his/her program.

Background

While writing this class, I've been using ADO.NET Data Provider for SQLite as a reference, which I found here.

Using the Code

Using this class is very easy: add a reference to this DLL (Project->AddReference...) in your project and a line to your code:

C#
using SQLWrapper;

Now you can start using database functions in your program.

Example:

C#
// creates a new instance of SQLiteBase and opens database in file "test.db"
SQLiteBase db = new SQLiteBase("test.db");
// executes SELECT query and store results in new data table
DataTable table = db.ExecuteQuery("SELECT * FROM table1 WHERE id = 1;");
// closes the database
db.CloseDatabase();

Source Code

There are two constructors and and five public functions:

  • OpenDatabase
  • CloseDatabase
  • GetTables
  • ExecuteNonQuery
  • ExecuteQuery

which are all well-documented in the source file, so I don't see a point in explaining them again.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Junior)
Croatia Croatia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionPerfomance? Pin
Member 1157682716-Jan-16 9:10
Member 1157682716-Jan-16 9:10 
QuestionUpdates for .NET 4.5.1 ? Pin
kiquenet.com31-Jan-14 4:06
professionalkiquenet.com31-Jan-14 4:06 
QuestionProblem Pin
daghune19-Oct-13 0:29
daghune19-Oct-13 0:29 
GeneralThanks Pin
nikzorom9-Jan-13 10:32
nikzorom9-Jan-13 10:32 
QuestionOpen database with password Pin
moez.hielmy27-Aug-12 17:06
moez.hielmy27-Aug-12 17:06 
GeneralEtrow Exception Pin
Al Baikr16-Jul-10 21:30
Al Baikr16-Jul-10 21:30 
Rant[My vote of 2] Not reacting to malformed queries Pin
Robhol5-Jul-10 12:44
Robhol5-Jul-10 12:44 
GeneralUpdate for Net 4.0 [modified] Pin
anss1236-Jun-10 21:04
anss1236-Jun-10 21:04 
GeneralRe: Update for Net 4.0 Pin
upsfeup7-Jul-10 4:24
upsfeup7-Jul-10 4:24 
GeneralRe: Update for Net 4.0 Pin
anss12327-Aug-10 3:59
anss12327-Aug-10 3:59 
GeneralHaving Error: library routine called out of sequence Pin
upsfeup26-May-10 2:44
upsfeup26-May-10 2:44 
GeneralRe: Having Error: library routine called out of sequence Pin
anss1234-Jun-10 3:02
anss1234-Jun-10 3:02 
GeneralMy Sincerest THANKS!! Pin
SurlyCanuck15-May-10 16:12
professionalSurlyCanuck15-May-10 16:12 
GeneralA possible alternative; NFileStorage Pin
snip117-Mar-09 6:54
snip117-Mar-09 6:54 
Generalcreate a database Pin
Chaozzster16-Feb-09 6:28
Chaozzster16-Feb-09 6:28 
GeneralRe: create a database Pin
thk_sompi1-Mar-09 12:16
thk_sompi1-Mar-09 12:16 
GeneralRe: create a database Pin
Ken Eucker3-Jun-09 9:25
Ken Eucker3-Jun-09 9:25 
GeneralRe: create a database Pin
fbelic3-Jun-09 23:14
fbelic3-Jun-09 23:14 
GeneralRe: create a database Pin
Ken Eucker4-Jun-09 21:19
Ken Eucker4-Jun-09 21:19 
Generalout of memory Pin
saifsail24-Jan-09 8:32
saifsail24-Jan-09 8:32 
GeneralPossible memory leak Pin
Jacky__E24-Nov-08 3:34
Jacky__E24-Nov-08 3:34 
GeneralRe: Possible memory leak [modified] Pin
oren.shnitzer22-Dec-08 10:21
oren.shnitzer22-Dec-08 10:21 
(fixed a problem in the order of the free and execute query. sorry)
thanks for the article! seems like a really KISS solution that could help in simple cases (I am pretty stuck trying to add BLOBs using it Frown | :( , but it works nicely for simple tasks)

I went according to comment #1 and the above comment. seems like there are a few small leaks. I took the liberty of fixing both issues. here is my code (it's not throughly checked but it seems to work).
using System;
using System.Collections;
using System.Data;
using System.Runtime.InteropServices;
using System.Text;

namespace SQLiteWrapper
{
    /// <summary>
    /// SQLite wrapper with functions for opening, closing and executing queries.
    /// </summary>
    public class SQLiteBase
    {
        // imports system functions for work with pointers
        [DllImport("kernel32")]
        private extern static IntPtr HeapAlloc(IntPtr heap, UInt32 flags, UInt32 bytes);

		[DllImport("kernel32")]
		static extern bool HeapFree(IntPtr heap, UInt32 flags, IntPtr block);

        [DllImport("kernel32")]
        private extern static IntPtr GetProcessHeap();

        [DllImport("kernel32")]
        private extern static int lstrlen(IntPtr str);

        // imports SQLite functions
        [DllImport("sqlite3")]
        private static extern int sqlite3_open(IntPtr fileName, out IntPtr database);

        [DllImport("sqlite3")]
        private static extern int sqlite3_close(IntPtr database);

        [DllImport("sqlite3")]
        private static extern int sqlite3_exec(IntPtr database, IntPtr query, IntPtr callback, IntPtr arguments, out IntPtr error);

        [DllImport("sqlite3")]
        private static extern IntPtr sqlite3_errmsg(IntPtr database);

        [DllImport("sqlite3")]
        private static extern int sqlite3_prepare_v2(IntPtr database, IntPtr query, int length, out IntPtr statement, out IntPtr tail);

        [DllImport("sqlite3")]
        private static extern int sqlite3_step(IntPtr statement);

        [DllImport("sqlite3")]
        private static extern int sqlite3_column_count(IntPtr statement);

        [DllImport("sqlite3")]
        private static extern IntPtr sqlite3_column_name(IntPtr statement, int columnNumber);

        [DllImport("sqlite3")]
        private static extern int sqlite3_column_type(IntPtr statement, int columnNumber);

        [DllImport("sqlite3")]
        private static extern int sqlite3_column_int(IntPtr statement, int columnNumber);

        [DllImport("sqlite3")]
        private static extern double sqlite3_column_double(IntPtr statement, int columnNumber);

        [DllImport("sqlite3")]
        private static extern IntPtr sqlite3_column_text(IntPtr statement, int columnNumber);

        [DllImport("sqlite3")]
        private static extern IntPtr sqlite3_column_blob(IntPtr statement, int columnNumber);

        [DllImport("sqlite3")]
        private static extern IntPtr sqlite3_column_table_name(IntPtr statement, int columnNumber);

        [DllImport("sqlite3")]
        private static extern int sqlite3_finalize(IntPtr handle);

		[DllImport("sqlite3")]
		private static extern IntPtr sqlite3_free(IntPtr error);

        // SQLite constants 
        private const int SQL_OK = 0;
        private const int SQL_ROW = 100;
        private const int SQL_DONE = 101;

        /// <summary>
        /// SQLite data types.
        /// </summary>
        public enum SQLiteDataTypes { 
            /// <summary>
            /// Integer numbers.
            /// </summary>
            INT = 1, 
            /// <summary>
            /// Decimal numbers.
            /// </summary>
            FLOAT,
            /// <summary>
            /// All kinds of texts.
            /// </summary>
            TEXT, 
            /// <summary>
            /// Blob objects - binary large objects.
            /// </summary>
            BLOB, 
            /// <summary>
            /// Nothing.
            /// </summary>
            NULL };

        // pointer to database
        private IntPtr database;

        /// <summary>
        /// Creates new instance of SQLiteBase class with no database attached.
        /// </summary>
        public SQLiteBase()
        {
            database = IntPtr.Zero;
        }

        /// <summary>
        /// Creates new instance of SQLiteBase class and opens database with given name.
        /// </summary>
        /// <param name="baseName">Name (and path) to SQLite database file</param>
        public SQLiteBase(String baseName)
        {
            OpenDatabase(baseName);
        }

        /// <summary>
        /// Opens database. 
        /// </summary>
        /// <param name="baseName">Name of database file</param>
        public void OpenDatabase(String baseName)
        {
			IntPtr ptr = StringToPointer(baseName);
            // opens database 
            if (sqlite3_open(ptr, out database) != SQL_OK)
            {
                // if there is some error, database pointer is set to 0 and exception is throws
                database = IntPtr.Zero;
				HeapFree(GetProcessHeap(), 0, ptr);
                throw new Exception("Error with opening database " + baseName + "!");
            }
			HeapFree(GetProcessHeap(), 0, ptr);
		}

        /// <summary>
        /// Closes opened database.
        /// </summary>
        public void CloseDatabase()
        {
            // closes the database if there is one opened
            if (database != IntPtr.Zero)
            {
                sqlite3_close(database);
            }
        }

        /// <summary>
        /// Returns the list of tables in opened database.
        /// </summary>
        /// <returns></returns>
        public ArrayList GetTables()
        {
            // executes query that select names of all tables and views in master table of every database
            String query = "SELECT name FROM sqlite_master " +
                                        "WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%'" +
                                        "UNION ALL " +
                                        "SELECT name FROM sqlite_temp_master " +
                                        "WHERE type IN ('table','view') " +
                                        "ORDER BY 1";
            DataTable table = ExecuteQuery(query);

            // when table is generater, it writes all table names in list that is returned
            ArrayList list = new ArrayList();
            foreach (DataRow row in table.Rows)
            {
                list.Add(row.ItemArray[0].ToString());
            }
            return list;
        }

        /// <summary>
        /// Executes query that does not return anything (e.g. UPDATE, INSERT, DELETE).
        /// </summary>
        /// <param name="query"></param>
        public void ExecuteNonQuery(String query)
        {
            // calles SQLite function that executes non-query
            IntPtr error;
			IntPtr ptr = StringToPointer(query);
            sqlite3_exec(database, ptr, IntPtr.Zero, IntPtr.Zero, out error);
            // if there is error, excetion is thrown
            if (error != IntPtr.Zero) {
				HeapFree(GetProcessHeap(), 0, ptr);
				string errorMessage = PointerToString(sqlite3_errmsg(error));
				sqlite3_free(error);
				throw new SystemException("Error with executing non-query: \"" + query + "\"!\n" + errorMessage);
			}
        	HeapFree(GetProcessHeap(), 0, ptr);
        }

        /// <summary>
        /// Executes query that does return something (e.g. SELECT).
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        public DataTable ExecuteQuery(String query)
        {
            // processed query
            IntPtr statement;

            // excess data, it has no use
            IntPtr excessData;

            // process query and make statement
			IntPtr ptr = StringToPointer(query);
            sqlite3_prepare_v2(database, ptr, GetPointerLenght(ptr), out statement, out excessData);
            // table for result of function
            DataTable table = new DataTable();

            // reads first row - it is different from next rows because it also creates table columns
            // result - returns SLQ_ROW while there is next row
            int result = ReadFirstRow(statement, ref table);

            // reads rows
            while (result == SQL_ROW)
            {
                result = ReadNextRow(statement, ref table);
            }

            // finalize executing this query
            sqlite3_finalize(statement);

            HeapFree(GetProcessHeap(), 0, ptr);

            // returns table
            return table;
        }

        // private function for reading firs row and creating DataTable
        private int ReadFirstRow(IntPtr statement, ref DataTable table)
        {
            // create new instance of DataTable with name "resultTable"
            table = new DataTable("resultTable");

            // evaluates statement 
            int resultType = sqlite3_step(statement);

            // if result of statement is SQL_ROW, create new table and write row in it
            if (resultType == SQL_ROW)
            {
                // returns number of columns returned by statement
                int columnCount = sqlite3_column_count(statement);

                // declartaion of variables for reading first row
                String columnName = "";
                int columnType = 0;
                object[] columnValues = new object[columnCount];

                // reads columns one by one
                for (int i = 0; i < columnCount; i++)
                {
                    // returns the name of current column
                    columnName = PointerToString(sqlite3_column_name(statement, i));

                    // returns the type of current column
                    columnType = sqlite3_column_type(statement, i);

                    // checks type of columns - neccessary because different functions are required for different types
                    switch (columnType)
                    {
                            // in case of integer column
                        case (int)SQLiteDataTypes.INT:
                            {
                                // adds new integer column to table
                                table.Columns.Add(columnName, Type.GetType("System.Int32"));

                                // writes column value in object array
                                columnValues[i] = sqlite3_column_int(statement, i);
                                break;
                            }
                            // same as for integer, this one is for float
                        case (int)SQLiteDataTypes.FLOAT:
                            {
                                table.Columns.Add(columnName, Type.GetType("System.Single"));
                                columnValues[i] = sqlite3_column_double(statement, i);
                                break;
                            }
                            // ... for text
                        case (int)SQLiteDataTypes.TEXT:
                            {
                                table.Columns.Add(columnName, Type.GetType("System.String"));
                                columnValues[i] = PointerToString(sqlite3_column_text(statement, i));
                                break;
                            }
                            // ... for blob - blob are written in table as strings!!
                        case (int)SQLiteDataTypes.BLOB:
                            {
                                table.Columns.Add(columnName, Type.GetType("System.String"));
                                columnValues[i] = PointerToString(sqlite3_column_blob(statement, i));
                                break;
                            }
                            // in case of something other, value is read as string
                        default:
                            {
                                table.Columns.Add(columnName, Type.GetType("System.String"));
                                columnValues[i] = "";
                                break;
                            }
                    }
                }

                // writes column values to table
                table.Rows.Add(columnValues);
            }

            // evalute statemnet for next results
            return sqlite3_step(statement);
        }

        // private function for reading rows other than first
        // it' same like first row, only without creating table and columns
        private int ReadNextRow(IntPtr statement, ref DataTable table)
        {
            int columnCount = sqlite3_column_count(statement);

            int columnType = 0;
            object[] columnValues = new object[columnCount];

            for (int i = 0; i < columnCount; i++)
            {
                columnType = sqlite3_column_type(statement, i);

                switch (columnType)
                {
                    case (int)SQLiteDataTypes.INT:
                        {
                            columnValues[i] = sqlite3_column_int(statement, i);
                            break;
                        }
                    case (int)SQLiteDataTypes.FLOAT:
                        {
                            columnValues[i] = sqlite3_column_double(statement, i);
                            break;
                        }
                    case (int)SQLiteDataTypes.TEXT:
                        {
                            columnValues[i] = PointerToString(sqlite3_column_text(statement, i));
                            break;
                        }
                    case (int)SQLiteDataTypes.BLOB:
                        {
                            columnValues[i] = PointerToString(sqlite3_column_blob(statement, i));
                            break;
                        }
                    default:
                        {
                            columnValues[i] = "";
                            break;
                        }
                }
            }
            table.Rows.Add(columnValues);
            return sqlite3_step(statement);
        }

        // converts string to pointer
        private IntPtr StringToPointer(String str)
        {
            // if string is null, pointer is 0
            if (str == null)
            {
                return IntPtr.Zero;
            }
            else
            {
                // else, convert it to pointer
                Encoding encoding = Encoding.UTF8;
                Byte[] bytes = encoding.GetBytes(str);
                int length = bytes.Length + 1;
                IntPtr pointer = HeapAlloc(GetProcessHeap(), 0, (UInt32)length);
                Marshal.Copy(bytes, 0, pointer, bytes.Length);
                Marshal.WriteByte(pointer, bytes.Length, 0);
                return pointer;
            }
        }

        // convert pointer to string
        private String PointerToString(IntPtr ptr)
        {
            if (ptr == IntPtr.Zero)
                return null;

            Encoding encoding = Encoding.UTF8;

            int length = GetPointerLenght(ptr);
            Byte[] bytes = new Byte[length];
            Marshal.Copy(ptr, bytes, 0, length);
            return encoding.GetString(bytes, 0, length);
        }

        // returns length of pointer
        private int GetPointerLenght(IntPtr ptr)
        {
            if (ptr == IntPtr.Zero)
                return 0;
            return lstrlen(ptr);
        }
    }
}


modified on Wednesday, January 7, 2009 3:07 AM

GeneralRe: Possible memory leak Pin
Jacky__E23-Dec-08 3:16
Jacky__E23-Dec-08 3:16 
GeneralNo more memory leak Pin
Sten Hjelmqvist23-Sep-09 2:19
Sten Hjelmqvist23-Sep-09 2:19 
GeneralRe: Possible memory leak Pin
SurlyCanuck15-May-10 16:28
professionalSurlyCanuck15-May-10 16:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.