Опыт внедрения SQLite в. NET приложение

Опыт внедрения SQLite в. NET приложение

SQLite — легкая встраиваемая база данных, отлично подходит для хранения небольших объемов информации. В этой статье я опишу свой опыт внедрения этой замечательной библиотеки в свое dotnet/C# приложение. В основном все прошло достаточно гладко, однако были и некоторые неожиданные особенности.

В первых двух подразделах Вы вряд ли найдете что-нибудь интересное, они включены скорее для полноты картины. Если вы хорошо знаете ADO. NET, можете мельком просмотреть их, переходить сразу к последнему, третьему подразделу, где описываются те самые неожиданные особенности, о которых упоминалось.

Качаем и линкуем

Скачать SQLite можно на официальном сайте. Интересно, что есть еще один официальный сайт, где можно скачать не-dotnet'овские версии, а также документацию. И это еще не все, с былых времен осталась еще одна страница посвященная System. Data. SQLite. С лицензией у библиотеки все в порядке, можно свободно использовать для коммерческих целей. Выложены там и исходники (в виде студийных проектов), но я качал прекомпиленные бинарники. Разработчики рекомендуют именно их, так как при сборке они там используют какие-то невероятные оптимизации.

Скачанная папка была размещена в директории солюшена, и на сборку System. Data. SQLite. dll добавлена ссылка обычным образом (в Visual C# — правый щелчок по проекту, пункт Add reference, таб Browse). Далее добавляем using на System. Data. SQLite вот и все приготовления.

Создание и использование БД

Единственное, что стоит тут упомянуть, это статический метод SQLiteConnection. CreateFile("MyDir/myDataBaseFile. db3"). Все остальное делается через стандартные интерфейсы ADO. NET, разве что ко всем классам нужно приделать приставку SQLite. Тем не менее, для полноты картины, ниже представлен полный код по созданию базы данных runtime (SQL-скрипт создания я выделил в отдельный файл, и добавил как Content в проект).

Файл Resources\DataBaseCreationScript. sql:

CREATE TABLE Documents(

NUMBER TEXT NOT NULL,

TYPE TEXT NOT NULL,

Name TEXT,

);

CREATE TABLE Payments(

PPNumber TEXT NOT NULL,

Summ INTEGER NOT NULL,

);

Здесь уже можно упомянуть пару особенностей, но оставляю это для следующего подраздела. А вот и сам код: using System; using System. Data. SQLite; using System. IO; public static class DataManager

{ public static void RegisterOrganization(string name)

{

// Creating data base. File name as name of organization + .vpatent string file = GetOrganizationDataBaseFilePath(name); if (File. Exists(file))

{ throw new OrganizationWithSuchNameAlreadyRegistered();

}

SQLiteConnection. CreateFile(file);

// Connect

SQLiteConnection con = new SQLiteConnection();

SQLiteConnectionStringBuilder conString = new SQLiteConnectionStringBuilder(); conString. DataSource = file; con. ConnectionString = conString. ToString(); using (con)

{ try

{ con. Open();

} catch (Exception e)

{

Logger. Log("Can't connect to new data base. Reason: " + e. Message); throw;

}

// Create data base structure

SQLiteCommand createDataBase = con. CreateCommand(); // Useful method createDataBase. CommandText = File. ReadAllText("Resources\DataBaseCreationScript. sql"); createDataBase. ExecuteNonQuery();

}

}

}

База данных готова, теперь добавим в нее что-нибудь эдакое, а заодно и прочитаем кое-что SELECT'ом: private SQLiteConnection Connect()

{

SQLiteConnection connection = new SQLiteConnection();

SQLiteConnectionStringBuilder cs = new SQLiteConnectionStringBuilder(); cs. DataSource = dataBaseFile; // Field in class, initializes in constructor connection. ConnectionString = cs. ToString(); connection. Open(); return connection;

} public void AddDocument(Dictionary<string, object> info)

{ using (SQLiteConnection connect = Connect())

{

// Check, if document with such number already exists

SQLiteCommand check = connect. CreateCommand(); check. CommandText = "SELECT COUNT(*) FROM Documents WHERE Number = @ParamNumber"; check. Parameters. AddWithValue("@ParamNumber", info["Number"] as string); // Using parameters is cool!

Long docsWithSuchNumberCount = (long)check. ExecuteScalar(); if (docsWithSuchNumberCount > 0)

{ throw new DocumentWithSuchNumberAlreadyExists();

}

// Insert new info

SQLiteCommand insert = FormInsertQuery(info. Keys, info, "Documents", connect); int rowsAffected = insert. ExecuteNonQuery(); if (rowsAffected == 0)

{ throw new Exception("Error during insert value to Documents. Record does not inserted.");

}

}

}

// Some kind of complex, but it is really usefull private SQLiteCommand FormInsertQuery(

IEnumerable<string> fields, // Column names

Dictionary<string, object> values, // Values to insert, where keys — column names string table,

SQLiteConnection connection)

{

// Make somethig like

// "INSET INTO Documents(Number, Type, Name) VALUES (@ParamNumber, @ParamType, @ParamName);"

SQLiteCommand command = connection. CreateCommand(); string insert = "INSERT INTO " + table + "("; foreach (string field in fields)

{ insert += field + ", ";

} insert = insert. Substring(0, insert. Length - 2); // Remove last ", " insert += ") VALUES("; foreach (string field in fields) // There may be one foreach, I know

{ insert += "@Param" + field + ", ";

} insert = insert. Substring(0, insert. Length - 2); // Remove last ", " again insert += ");"; command. CommandText = insert;

// And add real data as parameters values foreach (string field in fields)

{ if (values. ContainsKey(field))

{ object value = values[field]; command. Parameters. AddWithValue("@Param" + field, value);

} else

{ command. Parameters. AddWithValue("@Param" + field, null);

}

} return command;

}

Если вы пролистали не читая этот листинг, ничего страшного. Особо интересного кода там нет.

Особенности SQLite

Unable to load DLL 'SQLite. Interop. DLL'. Самая каверзная штука, с которой мне пришлось прободаться, это сбивающее с толку исключение DllNotFoundException, возникающее выборочно на некоторых машинах (обычно как раз на машине разработчика все нормально).

System. DllNotFoundException: Unable to load DLL 'SQLite. Interop. DLL': The specified module could not be found. (Exception from HRESULT: 0x8007007E)

Первое что приходит в голову, это прицепить к проекту эту злополучную SQLite. Interop. DLL, однако это совсем необязательно (хотя я все же встроил ее в проект, и без этого запускать не пробовал). Эта библиотека из дебажной версии SQLite, и в release-сборку она встроена по умолчанию. Проблема как оказалось здесь вовсе не в SQLite, а в отсутствии некоторой библиотеки в Windows. Проблема устраняется с помощью распространяемого пакета Visual C++ 2010, скачать который можно здесь. Я, например, встроил этот файл в проект, и запускаю его при установке программы.

Скрипт создания БД. Здесь всего две вещи, на которые я хотел бы обратить внимание. Первое, это то, что для использования внешних ключей, их необходимо сначала включить.

PRAGMA foreign_keys = ON;

CREATE TABLE Payments(

PPNumber TEXT NOT NULL,

Summ INTEGER NOT NULL,

DocumentNumber TEXT NOT NULL,

FOREIGN KEY(DocumentNumber) REFERENCES Documents(NUMBER)

);

И второе, это то, что по умолчанию все поля могут принимать NULL-значения, и указывать явно это не нужно. То есть явно можно написать только NOT NULL, в противном случае писать ничего не надо.

CREATE TABLE Documents(

NUMBER TEXT NOT NULL,

TYPE TEXT NOT NULL,

Name TEXT,

);

Типы данных. Одно из основных преимуществ SQLite — легкость и компактность. Однако, за это приходиться платить. В частности тем, что в нашем распоряжении всего 5 типов данных. Ниже приведен их список, и как они соотносятся с типами в. NET.

NULL -> null

INTEGER -> long (System. Int64)

REAL -> float (System. Single)

TEXT -> string (System. String)

BLOB -> byte[] (System. Byte[])

Первое, на что стоит обратить внимание, это то, что тип INTEGER соответствует long, а не int как можно было бы подумать. Кроме того, как вы можете заметить, типа DateTime здесь нет. Поэтому, чтобы сохранить дату, придется извращаться. Я, например, сделал так: переводил все даты в строки вида "%datetime%:" + date. ToString(). Учтя все это, я получил такой метод: private object GetValue(object p)

{ if (p is long) return (int)(long)p;

// dateTimeInditifier = "%datetime%:" else if (p is string && (p as string).StartsWith(dateTimeInditifier))

{ return DateTime. Parse((p as string).Replace(dateTimeInditifier, ""));

} return p;

}

Заключение

Вот так все и было. Работает SQLite в моей программе отлично, не жалуюсь. Рекомендую еще почитать хабрастатью о System. Data. SQLite. Я лично, когда писал код, использовал именно ее как отправную точку. На этом все, удачи!


Карта сайта


Информационный сайт Webavtocat.ru