C# библиотека доступа к базе данных SQLite. Часть 1
Предисловие
Итак, после долгих мучений с различными базами данных, было решено написать что-то такое, что могло упростить разработку приложения, работающего с какой-либо базой данных. Я заметил, что в каждом моем проекте приходится писать одно и тоже, когда это касается получения, вставки и т.д. в базу. Разбирая ZEND FRAMEWORK наткнулся на готовый класс, который значительно упрощал доступ к данным, хранящихся в базе. Решение, конечно, не для кул-хацкеров, но для написания повседневных программ «для себя» и «для других» подходит. Это первая часть в цикле статей, в которых, шаг за шагом будем создавать класс, призванный помочь в решении поставленной задачи.
Инструментарий
Для первой части я создал простой проект. Скажу сразу: в роли подопытной базы данных будем использовать SQLITE. Ей не нужен сервер, а для теста нашего класса ее хватит. Если вам понадобиться использовать класс для работы с MSSQL или MYSQL, то можно будет очень просто и быстро произвести нужные замены в классе.
Библиотеку SQLITE я приложил к проекту. Она 32-битная. 64-битную версию ищите здесь.
Реализация
Ядро нашего проекта — это файл dbFacade.cs. В нем будет описываться вся работа с базой данных. Этот файлы вы можете копировать в другой проект и пользоваться им как родным.
Давайте теперь рассмотрим структуру файла. Первая версия будет только создавать новый файл со структурой базы и читать его.
Основные настройки. Задаем сразу, чтобы потому не морочить себе голову.
//путь к файлу базы
public static string filename = Path.Combine(Application.StartupPath, "working.db");
//строка подключения
string ConnectionString = string.Format("data source={0};New=True;UseUTF16Encoding=True", filename);
[/sharp]
Первая важная функция создает файл со структурой.
[csharp]
public void CreateDatabase()
{
//SQL-запрос для таблицы
string sql_test = @"CREATE TABLE 'Test'(
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
'title' TEXT,
'status' tinyint,
'topic_id' INTEGER,
'testdate' datetime NOT null DEFAULT '2009-10-07')";
//SQL-запрос для индексации поля testdate
string sql_createindex = "CREATE UNIQUE INDEX testdate_indx ON Test (testdate)";
ConnectionState previousConnectionState = ConnectionState.Closed;
using (SQLiteConnection connect = new SQLiteConnection(ConnectionString))
{
try
{
//проверяем предыдущее состояние
previousConnectionState = connect.State;
if (connect.State == ConnectionState.Closed)
{
//открываем соединение
connect.Open();
}
//создаем новую таблицу
SQLiteCommand command = new SQLiteCommand(sql_test, connect);
command.ExecuteNonQuery();
//создаем индексацию
command.CommandText = sql_createindex;
command.ExecuteNonQuery();
}
catch { }
finally
{
//закрываем соединение, если оно было закрыто перед открытием
if (previousConnectionState == ConnectionState.Closed)
{
connect.Close();
}
}
}
}
Смотрите, SQLiteConnection, если его просто переименовать в SQLConnection, то это будет уже работа с MSSQL. Также и SQLiteCommand меняется на SQLCommand. И ВСЕ! После этих смен, эта функция уже будет работать с MSSQL. Вот так все просто… пока просто ![]()
Далее… вторая функция. Простая функция. Достает все данные из всех столбцов. Можно с условиями и различными параметрами. Т.е. простой запрос, без всяких там связываний с другими таблицами и т.д.
public DataTable FetchAll(string databasename, string where, string etc)
{
DataTable dt = new DataTable();
//создаем строку запроса
string sql = string.Format("SELECT * FROM {0} {1} {2}", databasename, where, etc);
ConnectionState previousConnectionState = ConnectionState.Closed;
using (SQLiteConnection connect = new SQLiteConnection(ConnectionString))
{
try
{
previousConnectionState = connect.State;
if (connect.State == ConnectionState.Closed)
{
connect.Open();
}
SQLiteCommand command = new SQLiteCommand(sql, connect);
SQLiteDataAdapter adapter = new SQLiteDataAdapter(command);
//заполняем таблицу
adapter.Fill(dt);
}
catch (Exception error) {
System.Windows.Forms.MessageBox.Show(error.Message, "Ошибка при получении данных из базы", MessageBoxButtons.OK, MessageBoxIcon.Error);
return null; }
finally
{
if (previousConnectionState == ConnectionState.Closed)
{
connect.Close();
}
}
}
//возвращаем таблицу
return dt;
}
Это была простая функция, для простого запроса. То, что она большая, так это просто куча проверок различных.
И еще одна функция для получения данных из избранных колонок:
public DataTable FetchByColumn(string databasename, string[] columns, string where, string etc)
{
DataTable dt = new DataTable();
string textofcolumns = string.Empty;
if (columns == null || columns.Length == 0)
textofcolumns = "*";
else
{
bool ifFirst = true;
//собираем все названия колонок в строку
foreach (string col in columns)
{
if (ifFirst)
{
textofcolumns = col;
ifFirst = false;
}
else
textofcolumns += "," + col;
}
}
string sql = string.Format("SELECT {0} FROM {1} {2} {3}", textofcolumns, databasename, where, etc);
ConnectionState previousConnectionState = ConnectionState.Closed;
using (SQLiteConnection connect = new SQLiteConnection(ConnectionString))
{
try
{
previousConnectionState = connect.State;
if (connect.State == ConnectionState.Closed)
{
connect.Open();
}
SQLiteCommand command = new SQLiteCommand(sql, connect);
SQLiteDataAdapter adapter = new SQLiteDataAdapter(command);
adapter.Fill(dt);
}
catch (Exception error)
{
System.Windows.Forms.MessageBox.Show(error.Message, "Ошибка при получении данных из базы", MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
finally
{
if (previousConnectionState == ConnectionState.Closed)
{
connect.Close();
}
}
}
return dt;
}
В эту функция мы передаем массив из колонок. Разбираем его, создаем запрос, выполняем. Отличается от предыдущей функции только передачей массива колонок.
Дополнительно описаны функции без передачи некоторых параметров. Типа:
public DataTable FetchAll(string databasename)
{
return FetchAll(databasename, "", "");
}
и
public DataTable FetchAll(string databasename, string where)
{
return FetchAll(databasename, where, "");
}
Заключение
Вот и разобрали первую версию чудо-библиотеки.
К проекту приложена простенькая заполненная база данных working.db. Просто для наглядности.
Файлы проекта целиком>>>
Файл dbFacade.cs последней версии.
Если у вас возникли вопросы, вы можете оставить их в комментариях


Доброе время суток =)
Зачем вообще необходима индексация?
и правильно ли написан этот запрос ?
//SQL-запрос для индексации поля testdate
20. string sql_createindex = «CREATE UNIQUE INDEX testdate_indx ON Test (testdate)»;
ай тьфу туплю))) давно на втором курсе ж было, кажется это уникальное поле определяющее уникальность данных в таблице.
Индексация это не уникальное поле. Индексация нужна только для больших таблиц. Для увеличения скорости работы с таблицей. Индексированных полей может быть несколько. Но этим нельзя злоупотреблять. Иначе вместо хорошей скорости, получим тормознутость. Для маленьких таблиц просто не имеет смысла.
По теме индексации http://ru.wikipedia.org/wiki/%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81_%28%D0%B1%D0%B0%D0%B7%D1%8B_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%29
да, поторопился я) потом уже почитал, что обычно индексируют кок раз первичные ключи и часто запрашиваемые поля..
бред, на индексации построена реляционная концепция базы данных, весь смысл в уменьшении объема БД. Скорость, это спорный вопрос, зависит от конкретной ситуации.
Почему бред? Из той же википедии: «Кроме того индексы занимают дополнительный объем памяти, поэтому перед созданием индекса следует убедиться, что планируемый выигрыш в производительности запросов превысит дополнительную затрату ресурсов компьютера на сопровождение индекса.» Если у вас в таблице миллион записей и 20 полей, вы назначите всем полям индексацию, то только потеряете в производительности.
Спасибо! Оч помогло)!
Индексация нужна и точка… есть ситуации, когда необходимость в индексах стремиться к нулю… но даже на нулю эта необходимость все равно есть… просто она (необходимость) равна нулю. На небольших базах в несколько сотен или даже тысяч записей индекс просу будет просто сложно затупить ваше приложение, а на приложениях с десятками,сотнями тысяч и более записей мне сложно представляются задачи без (как минимум) нескольких таблиц с запросами на них с «JOIN»-ами и прочими прелестями, которые без индексов затупят ваше приложение даже на десятке или сотне тысяч выдачи. Из сего можно сделать вывод, что индексы Базам Данных нужны ровно как бег по утрам… либо это будет просто полезным (в контексте БД — прокачает Ваш опыт) либо спасет от инсульта (заказчик ПО не скалабсирует)…
Ну что все статьи начинаются с уже использования классов SQLlite???
Тут народ шарится по всему инету чтобы понять как вообще появляется «using System.Data.SQLite;». На этом sqllite.org вообще ничего не понятно
Ссылка к нужной библиотеке в абзаце Инструментарий.
На sqllite.org лежит нативная библиотека и к проекту она не подключается.
Добрый вечер!
Долгое время работала с С++. Теперь осваиваю С# в связке с SQLite3.
Возникла проблема, казалось бы распространенная, кучу информации перерыла, но так и не нашла ответа.
Подскажите, пожалуйста, кодировка символов в базе cp1251 (Windows ANSI), база готовая, изменять нельзя. Проблема с отображение кириллицы, присловутые вопросики.
Как можно поменять кодировку на utf-8 перед тем, как выгружать базу в DataSet?
Перекодировка ерез строку подключения не работает почему-то.
Вообще если автоматически генерировать подкючение, то все норм работает. А если программно прописывать, то базу вроде открывает, но таблиц не находит.
Да, я не там ищу. Поясните пожалуйста, строку подключения как корректно прописать. База db_skv38.well уже существующая. Лежит в корне
не могли бы показать вашу реализацию функции LIKE для УТФ8, для символов выходящих за пределов АСКИЙ? Может почерпну что-то новое для себя.
Он и так работает с UTF если база изначально создается в UTF. Лучше скажите, что у вас не получается?
Для национальных символов, например, кириллицы, не работают LIKE, LOWER, UPPER, ORDER BY. Поэтому их приходится переопределять.
Вот, например, моя реализация ORDER BY. Просто пишу свой COLLATION.
[SQLiteFunction(FuncType = FunctionType.Collation, Name = "UTF8CI")]
public class SQLiteCaseInsensitiveCollation : SQLiteFunction
{
public override int Compare(string x, string y)
{
return string.Compare(x, y, true);
}
///
/// Initializes the case insensitive UTF8 collation UTF8CI
///
public static void InitCollation()
{
SQLiteFunction.RegisterFunction(typeof(SQLiteCaseInsensitiveCollation));
}
}
Таким же образом переопределяются UPPER и LOWER.
Единственная засада у меня LIKE. Не очень силен в регулярках, поэтому запрос тормозит безбожно. Приходится в запросе приводить поля к нижнему регистру.
Вот и хотел посмотреть, может у Вас есть более удачное решение.
блин, я что-то совсем не врубился…..
зареференсил длл ки, подрубил сам класс в проект, дал ему namespace такой же как namespace моего проекта…. а что дальше? В настройках класса нужно что-то прописывать?
Нет ли у вас простяцкого проэктика… типа примера…..
все, допер:
DatabaseLib.DbFacadeSQLite MyDbFacadeSQLite = new DbFacadeSQLite(Path.Combine(Application.StartupPath, «my.sqlite»));
респект и уважение, отличная либа, завелось все с пол пинка.
Вам случайно не доводилось с твиттером работать из под шарпа?
Дело в том, что это уже старая статья и в новой библиотеке другие возможности и, соответственно, по другому описываются некоторые действия. Под последнюю версию я еще не писал статей.
С твиттером пока не работал.
Как вы думаете я правильно сделал?
public void CreateDatabase()
{
//SQL-запрос для таблицы
string sql_menu = @»CREATE TABLE ‘menu’ (
‘id’ INTEGER PRIMARY KEY AUTOINCREMENT,
‘min’ TEXT(100),
‘price’ REAL,
‘capacity’ TEXT(100),
‘percent’ INTEGER,
‘add_day’ INTEGER
)»;
string sql_order = @»CREATE TABLE ‘order’ (
‘id’ INTEGER PRIMARY KEY AUTOINCREMENT,
‘product’ TEXT(100),
‘sum’ INTEGER,
‘calculation’ INTEGER DEFAULT 0,
‘table’ TEXT(30),
‘date’ TEXT
)»;
string sql_tables = @»CREATE TABLE ‘tables’ (
‘id’ INTEGER PRIMARY KEY AUTOINCREMENT,
‘table_name’ TEXT(30),
‘active’ INTEGER,
‘table_client’ TEXT(30)
)»;
string sql_workers = @»CREATE TABLE ‘workers’ (
‘id’ INTEGER PRIMARY KEY AUTOINCREMENT,
‘worker’ TEXT(60),
‘reg_day’ TEXT,
‘all_orders’ INTEGER
)»;
//SQL-запрос для индексации поля testdate
string sql_createindex = «CREATE UNIQUE INDEX indx ON Test (id)»;
ConnectionState previousConnectionState = ConnectionState.Closed;
using (SQLiteConnection connect = new SQLiteConnection(ConnectionString))
{
try
{
//проверяем предыдущее состояние
previousConnectionState = connect.State;
if (connect.State == ConnectionState.Closed)
{
//открываем соединение
connect.Open();
}
//создаем новую таблицу
SQLiteCommand command = new SQLiteCommand(sql_menu, connect);
SQLiteCommand command2 = new SQLiteCommand(sql_order, connect);
SQLiteCommand command3 = new SQLiteCommand(sql_tables, connect);
SQLiteCommand command4 = new SQLiteCommand(sql_workers, connect);
command.ExecuteNonQuery(); command2.ExecuteNonQuery();
command3.ExecuteNonQuery(); command4.ExecuteNonQuery();
//создаем индексацию
command.CommandText = sql_createindex;
command.ExecuteNonQuery();
command2.CommandText = sql_createindex;
command2.ExecuteNonQuery();
command3.CommandText = sql_createindex;
command3.ExecuteNonQuery();
command4.CommandText = sql_createindex;
command4.ExecuteNonQuery();
}
catch { }
finally
{
//закрываем соединение, если оно было закрыто перед открытием
if (previousConnectionState == ConnectionState.Closed)
{
connect.Close();
}
}
}
}
при беглом анализе я ничего такого не заметил. Все вроде правильно. Что вас смущает?