Eloquera V
краткое описание / немного истории / установка / режимы работы базы: service, inmemory, desktop / восстановление базы
конфигурация / программная конфигурация / простейшие операции CRUD / insert / update / refreshMode / delete
построение запросов / joins / параметры / массивы / шаблонные контейнеры / order by / доступные функции / almos
хранимые процедуры
родные и динамические объекты / работа с массивами / контейнеры / фильтрация типов, свойств и полей / backup and restore / эволюция типов / fulltext search / индексация / под капотом
«Родные» и динамические объекты
Под родными объектами в данном случае подразумеваются любые типы принадлежащие CLR или созданные вами. Eloquera может на лету (в runtime) конвертировать динамические объекты в «родные» и обратно.
Внимание!
Для использования этого функционала должно быть открыто соединение к базе.
Основные методы для конвертации:
- ToNative<ConcretType>()
- FromNative(variable)
Пример для уже открытой базы по созданию динамического объекта:
Book book = new Book { DbID = 12345, Title = "Title Mitle" }
Dynamic dynObj = new Dynamic(book);
db.Store(dynObj);
Book newBook = dynObj.ToNative();
При обновлении данных применяются следующие схемы работы:
- Обновление через родной тип
0 Динамический тип переводим в «родной» тип
0 Изменяем «родной» тип
0 Обновляем (синхронизируем) динамический тип с помощью FromNative
- Обновление через динамический тип
0 Существует переменная конкретного типа с данными динамического
0 Изменяем динамический тип
0 Обновляем (синхронизируем) «родной» тип с помощью ToNative<T>(variable)
Пример на первую схему:
@do = db.Query().Where(x => x["Name"] == "John").First(); // переводим в «родной» тип var user = @do.ToNative(); // обновляем user.Name = "Frank"; // синхронизируем @do.FromNative(user); // сохраняем в базу db.Store(@do);
Пример на вторую схему:
var @do = new Dynamic(); @do["Name"] = "Frank"; db.Store(@do); // получаем объект обратно @do = db.Query().Where(x => x["Name"] == "Frank").First(); // обновляем существующую переменную user «родного» типа @do.ToNative(user);
Работа с массивами
Работа с массивами является одной из фич базы Eloquera.
Основная рекомендация, которую вы можете принять за правило, звучит так: сохранять и получать объекты следует с использованием Dynamic , всю остальную работу делать с оригинальными типами. Другой хорошей рекомендацией является указание от разработчиков использовать именно массивы, а не List<>, Dictionary<,> - так как это всего лишь синтаксический сахар.
Лучше всего это продемонстрировать на примерах. Сначала я покажу, как создавать и сохранять массивы, потом как добавлять новые элементы и обновлять существующие записи.
Создание массивов, никаких неожиданностей:
Dynamic datamix = new Dynamic();
datamix["Number"] = 46;
datamix["Int array"] = new int[] {1442, 7557, 9938, 3988, 16833, int.MaxValue, int.MinValue};
datamix["Object array"] = new Book[] {
new Book() {
Title = "Atilla",
Parent = new ParentBook() { Publisher ="Hun" } },
null,
new Book() {
Title = "Gengis Khan",
Parent = new ParentBook() { Publisher ="Europe"}
}
};
Перед каким бы то ни было использованием объектов их надо сохранить в базе. И не забываем выставлять режим обновления базы.
db.RefreshMode = ObjectRefreshMode.AlwaysReturnUpdatedValues; db.Store(datamix);
Как я уже упомянул, при работе с List<T> рекомендуется перевести его в массив перед сохранением.
List<Book> list = new List<Book>();
list.Add(new Book() { Title = "Unseen Academicals", Parent = new ParentBook() { Publisher = "Europe" } });
datamix["Generics"] = list.ToArray();
db.Store(datamix);
В реальных приложениях опять же постоянно приходится добавлять новые элементы в массивы. Вот как это можно проделать с динамическими типами, обратите внимание, что конкретный тип при создании оборачивается в динамический:
Dynamic[] array = datamix["Generics"];
// список используется для простоты работы
List nativeList = new List(array);
// добавляем новый элемент – внимание!
nativeList.Add(new Dynamic(new Book() { Title = "Atilla", Parent = new ParentBook() { Publisher = "Hun" } }));
// сохранение. Переводим список обратно в массив.
datamix["Generics"] = nativeList.ToArray();
db.Store(datamix);
Обновление существующих элементов производится по очень похожей схеме. Получаем список объектов, для простоты можем перевести в список, после чего желаемый элемент переводится в «родной» тип, редактируется, переводится обратно в динамический и сохраняется. Все как описывалось для работы с динамическими и «родными» типами выше.
array = datamix["Generics"]; nativeList = new List(array); Dynamic @do = nativeList[0]; // только для примера взят нулевой элемент // получаем «родной» объект Book book = @do.ToNative(); // меняем значение полей book.Title = "Joel on software"; book.Parent.Publisher = "Hun"; // переводим обратно в динамический тип @do.FromNative(book); // сохраняем db.Store(@do);
Так как массивы суть ссылочные объекты и структура массива не изменилась, то можно просто сохранить измененный объект. Eloquera корректно его опознает в своей структуре и обновит. Если одновременно с изменением были добавлены новые элементы, надо будет передать на сохранение весь массив.
Контейнеры
В базе данных есть поддержка полностью изолированных хранилищ для динамических объектов – контейнеры. Контейнеры позволяют хранить объекты по логическим группам, к примеру, «еда», «книги», «игры». Еще раз сделаю акцент на том, что контейнеры изолированы, т.е. если один и тот же объект поместить в 2 контейнера, то изменение в объекте в первом контейнере не повлечет за собой изменения объекта во втором контейнере, он останется неизменным.
Сохранение вложенных объектов так же будет сделано по контейнерам и они будут независимыми.
Пример:
const string db_name = "Test";
DB db = new DB("server=(local);password=pwd;options=none;");
db.DeleteDatabase(db_name, true);
db.CreateDatabase(db_name);
db.OpenDatabase(db_name);
db.RefreshMode = ObjectRefreshMode.AlwaysReturnUpdatedValues;
db.Containers.CreateContainer("Electronics");
var electronics = db.Containers["Electronics"];
Dynamic tv = new Dynamic();
tv["Price"] = 649;//dollars
tv["Model"] = "Samsung LA40C650";
tv["Screen"] = 6.40;//inch
electronics.Store(tv);
db.Containers.CreateContainer("Furniture");
var furniture = db.Containers["Furniture"];
Dynamic bed = new Dynamic();
bed["Price"] = 700;//dollars
bed["Type"] = "King";
furniture.Store(bed);
var beds = furniture.ExecuteQuery("SELECT Dynamic WHERE Price > 600");
var tvs = from Dynamic d in electronics where d["Model"] == "Samsung LA40C650" select d;
Естественно, что присутствует возможность получить все контейнеры, созданные в системе. Метод для получения этого списка соответствует принципу наименьшего удивления и носит имя ListContainers.
string[] containerList = db.Containers.ListContainers();
Extras
Движок Eloquera индексирует все поля динамических объектов, однако порой в объекте хранятся сырые данные представляющие собой картинки, зашифрованные данные, сериализованные данные. Эти объекты с вероятностью 99,9% никогда не будут участвовать в запросах. В таких случаях массивы могут быть объявлены как дополнения – они не будут индексированы для увеличения скорости обработки (сохранения) данных.
Для создания дополнения надо добавить знак решетки перед именем.
message = new Dynamic(); message["Type"] = "string"; message["ExpireTime"] = DateTime.UtcNow; byte[] payload = new byte[512]; r.NextBytes(payload); message["#Payload"] = payload;//array-attachment
Дополнительные возможности
Фильтрация типов
Движок базы данных позволяет настраивать фильтры для добавления информации в базу при сохранении типов. Т.е. с помощью метода IgnoreType можно указать какие типы игнорировать при сохранении комплексных типов. Все поля, свойства или объекты, помеченные для игнорирования, не сохранятся в базе. Игнорируемые поля и свойства будут восстановлены со значениями по умолчанию.
Пример использования:
DB db = new DB(connectionString);
db.TypeRules.IgnoreType(typeof(DebugBook)); // Этот тип будет проигнорирован. Используйте до создания базы.
db.CreateDatabase("Sample");
db.OpenDatabase("Sample");
db.RegisterType(typeof(Book));
db.Close();
На сайте проекта можно прочитать списоктипов, которые автоматически добавляются в список для игнорирования.
Важно!
Правила для игнорирования типов надо объявлять до создания базы, иначе вам этот код надо будет прописывать всегда.
Фильтрация свойств и полей
Кроме того, что вы можете указать типы для игнорирования, существует возможность указать конкретные поля, которые не будут сохраняться в базу данных. Свойства указываются в виде лямбда-выражений вида ()=>myClassInstance.MyPropertyToBeIgnored, в качестве параметра для метода IgnoreProperty. После этого все проверки будут проводиться движком базы.
DB db = new DB("server=(local);password=;");
TestTypeDefsOnly test = null;
// Create all rules to be saved before calling CreateDatabase.
db.TypeRules
.IgnoreProperty(() => test.IntProperty)
.IgnoreProperty(() => test.DecimalProperty)
.IgnoreProperty(() => 1 == 0); // Так делать не стоит, хотя исключений и не будет.
db.CreateDatabase("IgnorePropertyPersistence");
Тестовый класс может выглядеть так:
internal sealed class TestTypeDefsOnly
{
// Эти свойства будут проигнорированы
public int IntProperty { get { Assert.Fail(); throw new NotImplementedException(); } }
public DateTime DateProperty { get { Assert.Fail(); throw newNotImplementedException(); } }
// Эти свойства будут сохранены
public string Name { get; set; }
public int Age { get; set; }
}
Backup and restore
Функция резервного сохранения позволяет сделать копию базы без выключения сервера, так же с помощью данной функции можно перемещать данные между разными версиями базы данных. Данные методы актуальны для работы базы Eloquera в серверном режиме.
Пример создания резервной копии:
db = new DB("server=localhost:43963;password=pwd;options=none;");
db.Backup("Backup");
И пример восстановления данных:
db = new DB("server=localhost:43963;password=pwd;options=none;");
//create a new database
db.CreateDatabase("Restored");
//open it
db.OpenDatabase("Restored");
//restore from backup file
db.Restore("Backup");
Регистрирование типов
При работе в режиме настольной базы, заблаговременное регистрирование типов может использоваться для увеличения скорости сохранения данных. Идея состоит в том, чтобы создать базу данных в памяти, с возможностью сохранения на диск, зарегистрировать нужные типы, после чего заново открыть базу в стандартном режиме. Данный подход может дать прирост производительности в 5-20 раз.
// создаем соединение. Обращаем внимание на options
DB db = new DB("server=(local);password=pwd;options=inmemory,persist;");
// создание базы
db.DeleteDatabase(DBName, true);
db.CreateDatabase(DBName);
db.OpenDatabase(DBName);
// регистрация типов
db.RegisterType(typeof(A1));
db.RegisterType(typeof(A2));
db.RegisterType(typeof(A3));
db.RegisterType(typeof(A4));
db.RegisterType(typeof(A5));
db.RegisterType(typeof(A6));
db.RegisterType(typeof(A7));
//...
db.Close();
// открытие созданной базы в нормальном режиме
db = new DB("server=(local);password=pwd;options=;");
db.OpenDatabase(DBName);
var listOfObjects = db.ExecuteQuery("SELECT A1 WHERE value = 0").Cast().ToList();
Эволюция типов
При применении итеративной разработки и поставки продукта не редкостью является ситуация, когда меняются основные типы данных. Такой сценарий часто представляется головной болью для разработчиков и не добавляет радости при работе.
В описанной ситуации на помощь могут прийти динамические объекты. Перевести «родные» объекты в динамические в промежуточной базе. Затем составить в новый тип и записать обратно. Модификация данных в одной и той же базе может быть слишком сложной и громоздкой.
Как пример обновления типов:
class User1{
public string Username { get; set; }
}
class Content1{
public string Text { get; set; }
public User1 Author { get; set; }
public long AuthorId { get; set; }
}
class User2{
public string Username { get; set; }
public string JobTitle { get; set; }
public int Age { get; set; }
}
class Content2{
public string Text { get; set; }
public User2 Author { get; set; }
public long AuthorId { get; set; }
}
db.DeleteDatabase(dbName, true);
db.CreateDatabase(dbName);
db.OpenDatabase(dbName);
var u1 = new User1 { Username = "Marcus" };
var c1 = new Content1 { Text = "Hello, world", Author = u1, AuthorId = 1 };
var @do = new Dynamic();
@do.FromNative(c1); //сохраняем топовый объект
db.Store(@do);//объект @do должен быть сохранен в базе для правильной работы
var c2 = @do.ToNative<Content2&rt;(); //получение нового типа
Assert.IsNotNull(c2);
Assert.IsNotNull(c2.Author);
Assert.AreEqual("Marcus", c2.Author.Username);
Full text search
Данная возможность может рассматриваться как немного магии над свойствами объекта.
Идея в том, что можно делать множество трансформаций в свойствах объекта и полученный результат использовать в запросе. Допустим, у нас есть куча товаров, которые описываются километрами текста. По всем товарам можно запустить полнотекстовый поиск следующим образом:
internal string description;
public string[] Words {
get {
return description.Split();
}
}
Запрос:
SELECT Products WHERE Words CONTAINS 'Jam'
В результате найдутся те продукты, в которых есть именно слово Jam, а не те, в которые оно входит. Данная задача и фича вытекла непосредственным образом из самой первой задачи, для которой была построена база, для поискового движка, так что должно работать быстро.
Еще пример на работу со свойствами:
double[] prices;
double min_price{get {...}}
select Foo where min_price < 10
Увеличение производительности
Индексация
Как и при работе с реляционными базами данных, для ускорения выполнения запросов может потребоваться настроить индексацию данных.
Для того чтобы свойство или поле стало индексируемым необходимо применить к нему атрибут Index.
public class Movie{
[Index]
public string Title {get; set;}
[Index]
public string StudioName {get; set;}
[Index]
public DateTime DateReleased {get; set; }
public double Budget{get; set;}
}
Если скорости индексов кажется мало, то их можно настроить более детально. Для строковых полей можно указать чувствительность к регистру, культуру, максимальный размер ключа.
[Index(IsCaseSensitive = true, Culture = "en-US", MaxKeySize = 20)]
public string Title {get; set;}
Если указать в качестве культуры null, то можно добиться еще прироста в производительности.
Индексация будет применяться только к вновь сохраненным объектам.
Для динамических объектов индексы создаются всегда для всех полей, кроме тех, которые помечены как «дополнения» (начинаются с решетки).
message = new Dynamic(); message["Type"] = "string"; message["ExpireTime"] = DateTime.UtcNow; byte[] payload = new byte[512]; r.NextBytes(payload); message["#Payload"] = payload;// не будет проиндексировано
Настройки в конфигурации
Перед работой базы, исходя из специфики проекта и железа, можно более тонко настроить производительность базы данных. Вот примеры использования и что надо сделать для ускорения работы с базой:
- Множество строковых полей в объекте – рекомендуется увеличить в конфигурации параметр VarSectorSize
- Множество индексируемых полей – увеличить значение поля IndexCacheSize, попробовать поиграть с настройками IndexNodeSize в сторону уменьшения значения.
- Быстрый жесткий диск – попробуйте уменьшить MemoryFootPrint. Значение более 100 будет считаться за размер в KB, менее 100 будет считаться за проценты.
- Множество запросов к непроиндексировнным данным – будет полезно увеличить ShallowReadAhead и уменьшить значение ShallowReadThreshold
Под капотом
На данный момент база данных Eloquera свободно распространяемый продукт с закрытым исходным кодом. Дизайн базы изначально проектировался для корпоративного (Enterprise) использования, т.е. для баз от 100ГБ и более. Это явно не для встроенного использования, что не отменяет локального использования конечно.
Внутреннее устройство, API, и прочие детали не раскрываются из-за коммерческой тайны, однако общее внутреннее устройство базы известно и показано ниже. Однако следует помнить о том, что это лишь отображение общей идеи, в то время как база данных очень сложный продукт и невозможно указать на диаграмме связи и значимые типы так, чтобы сохранить ясность и хоть сколько-нибудь свободного места на листе формата А3.
Тем не менее, вот как все выглядит внутри:
После примера на эволюцию данных у вас может сложиться впечатление, что внутренне представление всех типов будет Dynamic с какой-то более строгой проверкой типов, однако это не так. Для хранения «родных» объектов используется специальная мета-таблица для работы с данными.
Это весьма сложная таблица, так как она описывать зависимости между типами, наследие, типы полей, их layout в специальных записях для доступа и т.д.
Мета также используется для сборки и разборки объектов (сборка и разборка написана на MSIL, стандартная сериализация не используется по причине крайней медленности).
Когда новый тип сохраняется, клиент генерирует MSIL код на лету, компилирует его и использует откомпилированный код для сборки и разборки объектов. Получается в сотни раз быстрее, чем стандартная сериализация в .NET. Вот что фактически происходит внутри.
То есть, клиент сам себя наращивает кодом для работы с данным типом.
Нет обратных ссылок на эту запись.
