Здравствуйте, уважаемые читатели. В этой статье мы поговорим о таком замечательном
компоненте для работы с базами данных, как BLToolkit. Скачать этот продукт вы можете по ссылке. BLToolkit состоит из нескольких модулей, которые позволяют
избавиться от рутинных операций с базой данных. Данный тулкит позволяет
оптимизировать работу с БД, кешировать запросы, проводить валидацию через
атрибуты, поддержка CRUD операций, быстрый
мапинг объектов на базу данных и другие возможности. Есть небольшие недочеты
с поддержкой LINQ. Пока поддержка LINQ
немного хромает, но уже по уровню практически достигла Linq to SQL. Немного проблематичная работа с бинарными данными (Blob поля).
На некоторых форумах проскальзывает возмущение на тему о том, что в BLToolkit нет поддержки Dependency Injection (DI). Таких разработчиков хотелось бы спросить, зачем лепить в данный набор компонентов еще и DI. Подружить BLToolkit с каким-то популярным бесплатным IoC контейнером не составляет проблем. Например, для меня не представляло сложностей подружить данный тулкит с Ioc контейнером Autofac. Некое неудобство составляет отсутствие версионности. Удобно, что данный набор компонентов можно скачать через Package Manager Console (NuGet).
На некоторых форумах проскальзывает возмущение на тему о том, что в BLToolkit нет поддержки Dependency Injection (DI). Таких разработчиков хотелось бы спросить, зачем лепить в данный набор компонентов еще и DI. Подружить BLToolkit с каким-то популярным бесплатным IoC контейнером не составляет проблем. Например, для меня не представляло сложностей подружить данный тулкит с Ioc контейнером Autofac. Некое неудобство составляет отсутствие версионности. Удобно, что данный набор компонентов можно скачать через Package Manager Console (NuGet).
Цель данной статьи заключается не в том, чтобы показать, как просто работать с BLToolkit, для этого есть множество примеров на официальном сайте bltoolkit.net, а в том, чтобы продемонстрировать, как можно работать с BLToolkit с использованием
паттерна Repository с Domain Driven Design (DDD). Пример паттерна Repository рассматривается в книге Эрика Эванса "Предметно-ориентированное проектирование(DDD). Структуризация сложных программных систем".
Сама область разработки достаточно сложна и подразумевает наличие знаний у разработчика по проектированию программного обеспечения (ПО). Мы попробуем немного приоткрыть в этой статье этот вариант использования данного паттерна для реальных примеров. Также одна из особенностей данной статьи заключается в том, что для примера мы создадим GenericRepository. Многие критикуют использования generic репозитория. Пример, почему нельзя использовать данный паттерн, можно посмотреть в статье "If you're going to use repositories, don't have genericor base repositories...". Не буду вдаваться в полемику с автором приведенной выше статьи и соглашусь с его выводами. Но на каждое действие есть противодействие. Например, классический паттерн Singleton тоже считается антипаттерном, но он до сих пор используется в .NET Framework и позволяет решить множество проблем. Одна из проблем данного паттерна заключается в проблеме с тестированием. Это надуманная проблема. Если проблематично какую-то логику протестировать через Mock, то можно это сделать через фейки и стабы. Подробнее об этом можно посмотреть в блоге Сергея Теплякова. Поэтому мы рассмотрим, как GenericRepository позволяет решить 90% проблем с базой данных, а об остальных 10%, которых нужно запретить использовать, например, операцию delete, будет написан другой интерфейс. Если наше решение удовлетворяет поставленную задачу, почему бы его не использовать?
Отойдем от теории и приступим к практике. Для начала создадим тестовое консольное приложение UsingGenericRepository, в котором будем тестировать нашу работу с репозиторием.
Сама область разработки достаточно сложна и подразумевает наличие знаний у разработчика по проектированию программного обеспечения (ПО). Мы попробуем немного приоткрыть в этой статье этот вариант использования данного паттерна для реальных примеров. Также одна из особенностей данной статьи заключается в том, что для примера мы создадим GenericRepository. Многие критикуют использования generic репозитория. Пример, почему нельзя использовать данный паттерн, можно посмотреть в статье "If you're going to use repositories, don't have genericor base repositories...". Не буду вдаваться в полемику с автором приведенной выше статьи и соглашусь с его выводами. Но на каждое действие есть противодействие. Например, классический паттерн Singleton тоже считается антипаттерном, но он до сих пор используется в .NET Framework и позволяет решить множество проблем. Одна из проблем данного паттерна заключается в проблеме с тестированием. Это надуманная проблема. Если проблематично какую-то логику протестировать через Mock, то можно это сделать через фейки и стабы. Подробнее об этом можно посмотреть в блоге Сергея Теплякова. Поэтому мы рассмотрим, как GenericRepository позволяет решить 90% проблем с базой данных, а об остальных 10%, которых нужно запретить использовать, например, операцию delete, будет написан другой интерфейс. Если наше решение удовлетворяет поставленную задачу, почему бы его не использовать?
Отойдем от теории и приступим к практике. Для начала создадим тестовое консольное приложение UsingGenericRepository, в котором будем тестировать нашу работу с репозиторием.
Затем нужно создать заготовку для модели данных и для самого репозитория. Для
моделей мы добавим в наше приложение новый проект Class Library, который назовем UsingGenericRepository.Model. Задача
данной библиотеки будет заключаться в мапинге классов на базу данных как через
явный мапинг, так и через POCO модель.
Для репозитория добавим файл проекта Class Library и назовем его UsingGenericRepository.Repository.
Созданные
классы Class1 в данных библиотеках можно удалить. После создания ваша архитектура
будет выглядеть следующим образом:
Далее
для этих проектов необходимо через NuGet поставить BLToolkit. Как это сделать, можно посмотреть на первом рисунке в начале статьи либо вызвать Package Manager Console, как показано на
рисунке ниже.
Устанавливаем необходимый компонент, вызвав строку Install-Package BLToolkit.
Сначала перейдем в наш проект UsingGenericRepository.Model и добавим базовою реализацию
для классов, которые будут представлять модель классов нашей базы данных. Для
этого создадим базовый интерфейс IEntity, как показано ниже на
рисунке.
public interface IEntity
{
long Id { get; set; }
bool IsDirty
{ get; }
bool IsNew {
get; }
Dictionary<string, object> Parameters { get; }
IEntity CopyTo(IEntity entity);
object
Clone();
object
GetValue(string fieldName);
void
SetValue(string fieldName, object value);
}
Неудобства данной реализации заключаются в том, что для данного примера в каждой таблице должно быть поле Id, которое является автоинкрементным полем, а также первичным
ключом. Это проблема для более сложных данных, но и ее можно решить, например,
построив такой базовый репозиторий для тех таблиц, у которых есть первичный ключ
Id, а для остальных сделать реализацию через EditableObject<T> или EditableObject,
или, как вариант, через DataAccessor, как в примере с
официальной документации. В следующей статье, написанной с использованием паттерна репозиторий
под Entity Framework 6, я решил эту проблему по-другому. Если вам будет
интересно, вы сможете посмотреть альтернативный подход. В любом случае, эту
проблему с Id полем можно решить.
Далее создадим базовый контейнер EntityBase, который будет использоваться для хранения сущности с
базы данных.
/// <summary>
/// Базовый
контейнер для передачи в БД информации про сущность
/// </summary>
[Serializable]
public abstract class EntityBase : EditableObject, IEntity
{
/// <summary>
/// Свойство определяющее,обьект новый или нет
/// </summary>
[MapIgnore]
public bool IsNew
{
get
{
return Id
<= 0;
}
}
#region IEntity Members
/// <summary>
///
Идентификатор обьекта
/// </summary>
[PrimaryKey, NonUpdatable]
public abstract long Id { get; set; }
/// <summary>
///
Параметры обьекта
/// </summary>
[MapIgnore]
public virtual Dictionary<string, object> Parameters
{
get
{
var
objMapper = Map.GetObjectMapper(GetType());
var result
= (from fieldName in objMapper.FieldNames
select new
{
FieldName
= fieldName,
Value
= objMapper.GetValue(this, fieldName)
})
.ToDictionary(o
=> o.FieldName, o => o.Value);
return result;
}
}
/// <summary>
/// Извлечение значения из обьекта, по имени поля в бд
/// </summary>
/// <param
name="fieldName"></param>
/// <returns></returns>
public object
GetValue(string fieldName)
{
if (string.IsNullOrEmpty(fieldName))
throw new ArgumentNullException();
var
objMapper = Map.GetObjectMapper(GetType());
return
objMapper.GetValue(this, fieldName);
}
/// <summary>
/// Установка значения по имени поля в бд
/// </summary>
/// <param
name="fieldName"></param>
/// <param name="value"></param>
public void
SetValue(string fieldName, object value)
{
if (string.IsNullOrEmpty(fieldName))
throw new ArgumentNullException();
var
objMapper = Map.GetObjectMapper(GetType());
objMapper.SetValue(this,
fieldName, value);
}
/// <summary>
/// Копирование значений одного обьекта в другой
/// </summary>
/// <param name="entity">обьект в
который необходимо скопировать</param>
/// <returns></returns>
public IEntity CopyTo(IEntity entity)
{
return
CopyTo((EntityBase)entity);
}
/// <summary>
///
Клонирование обьекта
/// </summary>
/// <returns></returns>
object IEntity.Clone()
{
return
Clone();
}
#endregion
/// <summary>
/// Копирование
значений одного объекта в другой
/// </summary>
/// <param name="obj">обьект в который необходимо
скопировать</param>
/// <returns></returns>
public virtual EntityBase CopyTo(EntityBase obj)
{
var
properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var info in
properties)
{
if
(!info.CanRead)
continue;
var
propDest = obj.GetType().GetProperty(info.Name);
if
(propDest == null || !propDest.CanWrite)
continue;
if
(info.PropertyType != propDest.PropertyType)
continue;
var value =
info.GetValue(this, null);
propDest.SetValue(obj,
value, null);
}
return obj;
}
/// <summary>
///
Клонирование обьекта
/// </summary>
/// <returns></returns>
public virtual EntityBase Clone()
{
var
formatter = new BinaryFormatter();
using (var stream
= new MemoryStream())
{
formatter.Serialize(stream, this);
//
// Перемещаемся на начало, чтобы
выполнить десериализацию
//
stream.Position
= 0;
return
formatter.Deserialize(stream) as EntityBase;
}
}
public static T
Copy<T>(T entity)
where T : EntityBase
{
return TypeAccessor<T>.Copy(entity, TypeAccessor<T>.CreateInstance());
}
}
В BLToolkit уже имеется реализация классов EntityBase и EntityBase<T>, которые наследуются соответственно от классов EditableObject<T> или EditableObject,
но мы создали новый класс для того чтобы разрулить ситуацию с объектом, который
используется на данный момент, является он новым или он уже сохранен в БД. Этот подход мне не очень нравится, но если грамотно
построить работу с данным объектом, то данный подход имеет право на жизнь. Если
вам не нужны такие возможности, а в 90% так и будет, вам можно не
заморачиваться с такими сложностями, а сделать, как описано выше, способами решения через EditableObject. Продолжение
на странице второй.
No comments:
Post a Comment