Friday, April 4, 2014

Использование паттерна Repository с BLToolkit. Часть вторая

В этой статье продолжим тему реализации паттерна репозиторий Repository с помощью компонентов BLToolkit. Вспомним, о чем шла речь в первой части. Мы реализовали базовый контейнер для хранения данных с базы данных. Также описали, почему мы использовали создание кастомного контейнера, который наследовали от написанного нами интерфейса IEntity и зачем мы добавили наследование от класса EditableObject. Также были описаны способы простой реализации данного контейнера и чем наш контейнер в данном случае может помочь. 
Приступим к реализации нашего репозитория. Для того чтобы у нас был доступ к нашему контейнеру для работы с базой данных, необходимо добавить в проект UsingGenericRepository.Repository ссылку на проект UsingGenericRepository.Model. После этого создадим интерфейс для нашего репозитория.
public interface IRepository<T>
       where T : Model.EntityBase
{
       Table<T> GetTable();

       T SelectById(long id);

       long Insert(T entity);

       long Insert(DbManager db, T entity);

       long InsertWithIdentity(T entity);

       long InsertWithIdentity(DbManager db, T entity);

       long Save(T entity);

       long Update(T entity);

       long Update(DbManager db, T entity);

       //Обновить одно поле в базе данных
       long UpdateField<TV>(long id, Expression<Func<T, TV>> extract, TV value);

       //Обновить одно поле в базе данных
       long UpdateField<TV>(DbManager db, long id, Expression<Func<T, TV>> extract, TV value);

       void Delete(T entity);

       void Delete(DbManager db, T entity);

       void DeleteById(long id);

       void DeleteById(DbManager db, long id);

       T CreateNewEntity();

       IQueryable<T> SelectBy(FilterType filterType, IEnumerable<KeyValuePair<string, object>> parameters);

       IQueryable<T> SelectBy(DbManager db, FilterType filterType, IEnumerable<KeyValuePair<string, object>> parameters);

       Type GetTypeEntity();
}


public interface IRepository
{
       object SelectById(long id);

       long Insert(object entity);

       long Insert(DbManager db, object entity);

       long InsertWithIdentity(object entity);

       long InsertWithIdentity(DbManager db, object entity);

       long Save(object entity);

       long Save(DbManager db, object entity);

       long Update(object entity);

       long Update(DbManager db, object entity);

       void Delete(object entity);

       void Delete(DbManager db, object entity);

       void DeleteById(long id);

       void DeleteById(DbManager db, long id);

       object CreateNewEntity();

       IQueryable SelectBy(FilterType filterType, IEnumerable<KeyValuePair<string, object>> parameters);

       IQueryable SelectBy(DbManager dbManager, FilterType filterType, IEnumerable<KeyValuePair<string, object>> parameters);

       IQueryable Select();

       Type GetTypeEntity();

       string GetPropertyName(string dbName);

       string GetDbPropertyName(string propertyName);
}
Интерфейс практически классический, за исключением некоторых выборок, которые сделаны для удобства. Также немного странным может показаться выборка с функцией SelectedBy, которая принимает в себя перечисление FilterType. Это перечисление выглядит следующим образом:
public enum FilterType
{
       Contains,
       Equals
}
Дело в том, что данный интерфейс затачивался под конкретную реализацию репозитория и спроектирован для удобства под BLToolkit. Для своей реализации вы можете избавиться от такого подхода, используя только Linq to SQL для выборки значений. Как вариант, можно воспользоваться подходом с использованием выражений (expressions).
IQueryable<T> Select(DbManager db, Expression<Func<T, bool>> predicate);
Изменяйте реализацию базового репозитория под ваши нужды. Давайте посмотрим, как будет выглядеть реализация базового репозитория и имплементация приведенного выше интерфейса.
/// <summary>
/// Базовый класс репозиториев
/// </summary>
public class Repository<T> : IRepository<T>, IRepository
       where T : EntityBase
{
       protected DbManager GetDbManager()
       {
             return new DbManager();
       }

       public Table<T> GetTable(DbManager dbManager)
       {
             return GetTableCore(dbManager, false);
       }

       private Table<T> GetTableCore(DbManager dbManager, bool closeConnection)
       {
             return dbManager.GetTable<T>(closeConnection);
       }

       public Table<T> GetTable()
       {
             try
             {
                    using (var db = GetDbManager())
                    {
                           return GetTableCore(db, true);
                    }

             }
             catch (Exception ex)
             {
                    throw new Exception(string.Format("Get table for {0} error", typeof(T).Name), ex);
             }
       }

       public virtual T SelectById(long id)
       {
             try
             {
                    return (from item in GetTable()
                                  where item.Id == id
                                  select item).FirstOrDefault();

             }
             catch (Exception ex)
             {
                    throw new Exception(string.Format("Select by id {0} error", typeof(T).Name), ex);
             }
       }

       public long Insert(object entity)
       {
             return Insert((T)entity);
       }

       public long Insert(DbManager db, object entity)
       {
             return Insert(db, (T)entity);
       }

       public long InsertWithIdentity(object entity)
       {
             return InsertWithIdentity((T)entity);
       }

       public long InsertWithIdentity(DbManager db, object entity)
       {
             return InsertWithIdentity(db, (T)entity);
       }

       public long Save(object entity)
       {
             return Save((T)entity);
       }

       public long Save(DbManager db, object entity)
       {
             return Save(db, (T)entity);
       }

       public long Update(object entity)
       {
             return Update((T)entity);
       }

       public long Update(DbManager db, object entity)
       {
             return Update(db, (T)entity);
       }

       public void Delete(object entity)
       {
             Delete((T)entity);
       }

       public void Delete(DbManager db, object entity)
       {
             Delete(db, (T)entity);
       }

       public virtual void Delete(IEnumerable<T> entity)
       {
             using (var db = GetDbManager())
             {
                    Delete(db, entity);
             }
       }

       public virtual long Insert(T entity)
       {
             using (var db = GetDbManager())
             {
                    var result = Insert(db, entity);
                    return result;
             }
       }

       public virtual long Insert(DbManager db, T entity)
       {
             entity.Validate();

             int result;

             try
             {
                    result = db.Insert(entity);

             }
             catch (Exception ex)
             {
                    throw new Exception(string.Format("Insert {0} error", typeof(T).Name), ex);
             }

             entity.AcceptChanges();

             return result;
       }

       public virtual long InsertWithIdentity(T entity)
       {
             using (var db = GetDbManager())
             {
                    var result = InsertWithIdentity(db, entity);
                    return result;
             }
       }

       public virtual long InsertWithIdentity(DbManager db, T entity)
       {
             entity.Validate();

             long result;

             try
             {
                    result = Convert.ToInt64(db.InsertWithIdentity(entity));

                    entity.Id = result;

             }
             catch (Exception ex)
             {
                    throw new Exception(string.Format("Insert with identity {0} error", typeof(T).Name), ex);
             }

             entity.AcceptChanges();

             return result;
       }

       public long Save(T entity)
       {
             return entity.IsNew
                                        ? InsertWithIdentity(entity)
                                        : Update(entity);
       }

       public long Save(DbManager db, T entity)
       {
             return entity.IsNew
                                        ? InsertWithIdentity(db, entity)
                                        : Update(db, entity);
       }

       public long UpdateField<TV>(long id, Expression<Func<T, TV>> extract, TV value)
       {
             using (var db = GetDbManager())
             {
                    var result = UpdateField(db, id, extract, value);
                    return result;
             }
       }

       public long UpdateField<TV>(DbManager db, long id, Expression<Func<T, TV>> extract, TV value)
       {
             var query = db.GetTable<T>()
                                        .Where(e => e.Id == id)
                                        .Set(extract, value);

             return query.Update();
       }

       public virtual long Update(T entity)
       {
             using (var db = GetDbManager())
             {
                    var result = Update(db, entity);
                    return result;
             }
       }

       public virtual long Update(DbManager db, T entity)
       {
             entity.Validate();

             int result;

             try
             {
                    result = db.Update(entity);
             }
             catch (Exception ex)
             {
                    throw new Exception(string.Format("Update {0} error", typeof(T).Name), ex);
             }

             entity.AcceptChanges();

             return result;
       }

       public virtual void Delete(T entity)
       {
             using (var db = GetDbManager())
             {
                    Delete(db, entity);
             }
       }

       public virtual void Delete(DbManager db, T entity)
       {
             try
             {
                    db.Delete(entity);
              }
             catch (Exception ex)
             {
                    throw new Exception(string.Format("Delete {0} error", typeof(T).Name), ex);
             }
       }

       public virtual void Delete(DbManager db, IEnumerable<T> entity)
       {
             try
             {
                    db.Delete(entity);
             }
             catch (Exception ex)
             {
                    throw new Exception(string.Format("Delete {0} error", typeof(T).Name), ex);
             }
       }

       object IRepository.SelectById(long id)
       {
             return SelectById(id);
       }

       public void DeleteById(long id)
       {
             using (var db = GetDbManager())
             {
                    DeleteById(db, id);
             }
       }

       public void DeleteById(DbManager db, long id)
       {
             try
             {
                    var entity = CreateNewEntity();
                    entity.Id = id;

                    db.Delete(entity);

             }
             catch (Exception ex)
             {
                    throw new Exception(string.Format("Delete by id {0} error", typeof(T).Name), ex);
             }
       }

       object IRepository.CreateNewEntity()
       {
             return CreateNewEntity();
       }     

       IQueryable IRepository.SelectBy(FilterType filterType, IEnumerable<KeyValuePair<string, object>> parameters)
       {
             return SelectBy(filterType, parameters);
       }

       IQueryable IRepository.SelectBy(DbManager db, FilterType filterType, IEnumerable<KeyValuePair<string, object>> parameters)
       {
             return SelectBy(db, filterType, parameters);
       }

       public IQueryable Select()
       {
             return GetTable();
       }

       public IQueryable<T> SelectBy(FilterType filterType, IEnumerable<KeyValuePair<string, object>> parameters)
       {
             using (var db = GetDbManager())
             {
                    var result = SelectBy(db, filterType, parameters);
                    return result;
             }
       }

       public IQueryable<T> SelectBy(DbManager db, FilterType filterType, IEnumerable<KeyValuePair<string, object>> parameters)
       {
             try
             {
                    var type = GetTypeEntity();

                    var fields = GetFields();

                    var query = GetTableCore(db, true) as IQueryable<T>;

                    foreach (var param in parameters)
                    {
                           if (!fields.Contains(param.Key))
                                  throw new Exception(string.Format("Сущность {0} не содержит поле {1}",
                                                                                                            GetType().Name,
                                                                                                            param.Key), null);


                           var propertyName = GetPropertyName(param.Key);
                           var property = type.GetProperty(propertyName);

                           var propertyType = property.PropertyType;

                           var expressionParam = Expression.Parameter(type, type.Name);
                           var right = Expression.Constant(Convert.ChangeType(param.Value, propertyType), propertyType);
                           var left = Expression.Property(expressionParam, property);

                           LambdaExpression predicate;

                           switch (filterType)
                           {
                                  case FilterType.Contains:
                                        {
                                               var filterContains = Expression.Call(left, MethodInfoHelpers.MethodContains, new Expression[] { right });
                                               predicate = Expression.Lambda(filterContains, expressionParam);
                                        }
                                        break;
                                  default:
                                        {
                                               var expr = Expression.Equal(left, right);
                                               predicate = Expression.Lambda(expr, expressionParam);
                                        }
                                        break;
                           }

                           var expression = Expression.Call(typeof(Queryable), "Where", new[] { query.ElementType },
                                               query.Expression,
                                               predicate);

                           query = query.Provider.CreateQuery<T>(expression);
                    }

                    return query;

             }
             catch (Exception ex)
             {
                    throw new Exception(string.Format("Select by {0} error", typeof(T).Name), ex);
             }
       }

       public T CreateNewEntity()
       {
             var entity = TypeAccessor<T>.CreateInstanceEx();
             entity.Id = -1;
             return entity;
       }

       public Type GetTypeEntity()
       {
             return typeof(T);
       }

       /// <summary>
       /// получение свойства сущности отвечающего за поле в бд
       /// </summary>
       /// <param name="dbName">имя поля в бд</param>
       /// <returns></returns>
       public string GetPropertyName(string dbName)
       {
             if (string.IsNullOrEmpty(dbName))
                    return dbName;

             var properties = GetTypeEntity()
                                               .GetProperties(BindingFlags.Instance | BindingFlags.Public);

             var property = (from p in properties
                                        let mapFieldAttr =
                                                      (MapFieldAttribute)Attribute.GetCustomAttribute(p, typeof(MapFieldAttribute))
                                        where mapFieldAttr != null
                                                      && mapFieldAttr.MapName.Equals(dbName, StringComparison.OrdinalIgnoreCase)
                                        select p)
                                        .FirstOrDefault();

             return property == null
                                  ? dbName
                                  : property.Name;
       }

       private static string GetDbPropertyName(PropertyInfo property)
       {
             var mapFieldAttribute = property.GetCustomAttributes(typeof(MapFieldAttribute), false)
                                                                                 .OfType<MapFieldAttribute>()
                                                                                 .FirstOrDefault();

             return mapFieldAttribute != null
                           ? string.IsNullOrEmpty(mapFieldAttribute.MapName)
                                  ? property.Name
                                  : mapFieldAttribute.MapName
                           : property.Name;
       }

       /// <summary>
       /// получение имени поля в бд
       /// </summary>
       /// <param name="propertyName">имя свойства сущности</param>
       /// <returns></returns>
       public string GetDbPropertyName(string propertyName)
       {
             if (string.IsNullOrEmpty(propertyName))
                    return propertyName;

             var propertyInfo = GetTypeEntity().GetProperty(propertyName);

             return GetDbPropertyName(propertyInfo);
       }

       private string[] GetFields()
       {
             var objMapper = Map.GetObjectMapper(GetTypeEntity());
             return objMapper.FieldNames;
       }
}
В приведенном примере больше всего места занимает реализация SelectedBy. Вы можете переписать реализацию следующим образом. Добавить в интерфейс IRepository<T> два метода:
IQueryable<T> Select(DbManager db, Expression<Func<T, bool>> predicate);

IQueryable<T> Select(Expression<Func<T, bool>> predicate);
После этого переходим в наш класс Repository<T> и реализуем логику для этих функций.
public IQueryable<T> Select(DbManager db, Expression<Func<T, bool>> predicate)
{
       return GetTableCore(db, true).Where(predicate).Select(x => x);
}

public IQueryable<T> Select(Expression<Func<T, bool>> predicate)
{
       using (var db = GetDbManager())
       {
             return Select(db, predicate);
       }
}
С чистой совестью мы можем удалить метод SelectedBy и оставить только что написанный. Напоследок остался  финальный штрих: как это использовать. Для этого перейдем в наш базовый консольный проект UsingGenericRepository и добавим в него ссылку на наш созданный репозиторий и модель данных. Для того чтобы продемонстрировать работу с базой данных, я создал базу данных в MS SQL Server 2008 и добавил в нее табличку AlcoProduct и Unit.
Пример создания такой таблицы с помощью SQL скрипта:
CREATE TABLE [dbo].[AlcoProduct](
      [Id] [int] IDENTITY(1,1) NOT NULL,
      [Code] [int] NULL,
      [Name] [nvarchar](255) NULL,
 CONSTRAINT [aaaaaAlcoProduct_PK] PRIMARY KEY NONCLUSTERED
(
      [Id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
После этого добавим классы для наших моделей в проект UsingGenericRepository.Model. Структура проекта приведена на рисунке ниже.
Реализация класса AlcoProductEntity имеет следующий вид:
[Description("Спиртосодержащая продукция")]
[TableName("AlcoProduct")]
[Serializable]
public abstract class AlcoProductEntity : EntityBase
{
       [Required]
       [MapField("Code")]
       [Description("Код")]
       public abstract int? Code { get; set; }

       [MaxLength(255), Required]
       [MapField("Name")]
       [Description("Название")]
       public abstract string Name { get; set; }
}
Реализация класса UnitDataEntity приведена ниже.
[Description("Единицы измерения")]
[TableName("Unit")]
[Serializable]
public abstract class UnitDataEntity : EntityBase
{
       [MaxLength(3)]
       [MapField("Code")]
       [Description("Код")]
       public abstract string Code { get; set; }

       [MaxLength(100), Required]
       [MapField("ShortName")]
       [Description("Условное обозначение")]
       public abstract string ShortName { get; set; }

       [MaxLength(255), Required]
       [MapField("Name")]
       [Description("Название")]
       public abstract string Name { get; set; }
}
Осталось посмотреть, как это работает на практике. Для этого перейдем в наш основной проект и в функцию Main добавим следующий код:
class Program
{
       static void Main(string[] args)
       {
             try
             {
                    var unitRepository = new Repository<UnitDataEntity>();
                    var names = unitRepository.GetTable().Select(x => x.Name).ToList();

                    var alcoRepository = new Repository<AlcoProductEntity>();
                    var unit = alcoRepository.Select(x => x.Code == 2).First();

                    var unitEntity = unitRepository.CreateNewEntity();
                    unitEntity.Code = "999";
                    unitEntity.Name = "test2345";
                    unitEntity.ShortName = "test2345";
                    unitRepository.Insert(unitEntity);

                    var findEntity = unitRepository.GetTable().First(x => x.Code == "999");
                    Console.WriteLine("EntityName = {0}", findEntity.Name);
             }
             catch (Exception ex)
             {
                    Console.WriteLine(ex.ToString());
             }
                   
             Console.ReadLine();
       }
}
Запуск приложения показан ниже на экране.

У многих читателей могут возникнуть вполне закономерные вопросы, на часть из которых постараюсь ответить в следующей части, посвященной паттерну репозиторий. 


No comments:

Post a Comment