В
этой статье продолжим тему реализации паттерна репозиторий Repository с помощью
компонентов BLToolkit. Вспомним, о чем шла речь в первой части. Мы реализовали базовый
контейнер для хранения данных с базы данных. Также описали, почему мы
использовали создание кастомного контейнера, который наследовали от написанного
нами интерфейса IEntity и зачем мы добавили
наследование от класса EditableObject. Также были описаны способы простой реализации данного контейнера и чем наш контейнер в данном случае
может помочь.
Приступим к реализации нашего репозитория. Для того чтобы у нас был доступ к нашему контейнеру для работы с базой данных, необходимо добавить в проект UsingGenericRepository.Repository ссылку на проект UsingGenericRepository.Model. После этого создадим интерфейс для нашего репозитория.
Приступим к реализации нашего репозитория. Для того чтобы у нас был доступ к нашему контейнеру для работы с базой данных, необходимо добавить в проект 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).
Изменяйте реализацию базового репозитория под ваши нужды. Давайте
посмотрим, как будет выглядеть реализация базового репозитория и имплементация
приведенного выше интерфейса.
/// <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