Использование паттерна Repository в EntityFramework (Generic Repository)

Идея паттерна репозиторий заключается в создании уровня абстрации между уровнем доступа к данным и слоя бизнес логики. Это поможет изолировать приложение от изменений в хранилище данных и позволит создать тесты для некоторых блоков.

Для каждого сгенерированного класса EntityFramework мы создадим класс репозитория и соответствующий интерфейс (или я покажу как можно использовать generic репозиторий). Интерфейс нужен для абстрагирования реализации класса для контроллера. Когда контроллер обрабатывает запрос он получит репозиторий, реализующий соответствующий интерфейс, который может работать как с EF так и с другим хранилищем данным. 

Рисунок показыает отношение между SQL Server, EF, DbContext и приложением.

entity framework repository pattern

Операции CRUD наиболее часто используемые операции в плане доступа к данным. Хотелось бы один раз написать реализацию этих операций и переиспользовать в разных проектах. Для этого напишем один репозиторий (Generic) и интерфейс. Для начала определим какие операции хотелось бы всегда иметь под рукой.

/// <summary>
/// Defines interface for common data access functionality for entity.
/// </summary>
/// <typeparam name="T">Type of entity.</typeparam>
public interface IRepository<T>
{
    List<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
        string includeProperties = "");

    PagedList<T> GetWithPaging(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
        string includeProperties = "", int page = 1, int size = 50);

    void Add(T entity);

    void Update(T entity);

    void Delete(T entity);

    void Delete(Expression<Func<T, bool>> where);

    T GetById(int id);

    IEnumerable<T> GetAll();

    IEnumerable<T> GetMany(Expression<Func<T, bool>> where);

    T Get(Expression<Func<T, bool>> where);

    int Count(Expression<Func<T, bool>> where = null);

    bool IsExist(Expression<Func<T, bool>> where = null);

    void Save();
}

Отдельно хочется отметить метод PagedList<T> GetWithPaging. Хотя это операция и не является стандартной Read операцией, но использование постраничного вывода используется довольно часто. Имея Generic процедуру для получения постраничного вывода из базы данных сильно упрощает как разработку, так и поддержку решения.

Реализация Generic репозитория.

public class BaseRepository<T, C> : IRepository<T>
    where T : class
    where C : DbContext
{
    protected C _dataContext;
    private DbSet<T> _dbset;

    public BaseRepository(C context)
    {
        this._dataContext = context;
        this._dbset = context.Set<T>();
    }


    public virtual List<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "")
    {
        IQueryable<T> query = _dbset;

        if (filter != null)
        {
            query = query.Where(filter);
        }

        foreach (var includeProperty in includeProperties.Split
            (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
        {
            query = query.Include(includeProperty);
        }

        if (orderBy != null)
        {
            return orderBy(query).ToList();
        }
        else
        {
            return query.ToList();
        }
    }

    public PagedList<T> GetWithPaging(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "", int page = 1,
        int size = 50)
    {
        IQueryable<T> query = _dbset;

        if (filter != null)
        {
            query = query.Where(filter);
        }

        foreach (var includeProperty in includeProperties.Split
            (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
        {
            query = query.Include(includeProperty);
        }

        if (orderBy != null)
        {
            return new PagedList<T>(orderBy(query), page, size);
        }
        else
        {
            throw new Exception("Get With Paging query must be sorted");
        }
    }

    public virtual void Add(T entity)
    {
        _dbset.Add(entity);
    }

    public virtual void Update(T entity)
    {
        _dbset.Attach(entity);

        _dataContext.Entry(entity).State = EntityState.Modified;
    }

    public virtual void Delete(T entity)
    {
        _dbset.Remove(entity);
    }

    public virtual void Delete(Expression<Func<T, bool>> where)
    {
        IEnumerable<T> objects = _dbset.Where<T>(where).AsEnumerable();
        foreach (T obj in objects)
            _dbset.Remove(obj);
    }

    public virtual T GetById(int id)
    {
        return _dbset.Find(id);
    }

    public virtual IEnumerable<T> GetAll()
    {
        return _dbset.ToList();
    }

    public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where)
    {
        return _dbset.Where(where).ToList();
    }

    public T Get(Expression<Func<T, bool>> where)
    {
        return _dbset.Where(where).FirstOrDefault<T>();
    }

    public int Count(Expression<Func<T, bool>> where = null)
    {
        return _dbset.Count(where);
    }

    public bool IsExist(Expression<Func<T, bool>> where = null)
    {
        return _dbset.FirstOrDefault(where) != null ? true : false;
    }

    public void Save()
    {
        try
        {
            _dataContext.SaveChanges();
        }
#if DEBUG
        catch (DbEntityValidationException e)
        {
            foreach (var eve in e.EntityValidationErrors)
            {
                Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
                    eve.Entry.Entity.GetType().Name, eve.Entry.State);
                foreach (var ve in eve.ValidationErrors)
                {
                    Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
                        ve.PropertyName, ve.ErrorMessage);
                }
            }
            throw;
        }
#endif
        catch (Exception exception)
        {
            throw exception;
        }

    }
}

Во-первых данная реализация паттерна репозиторий позволяет включать зависимости (произвольное количество) через запятую. Например "Product,Product.Tags,Product.Images". Во-вторых в методе Save в DEBUG компиляции добавлен код, который выводит на консоль фактическую ошибку при сохранении EntityFramework. Чаще всего объект типа Exception содержит ничем не помогающее сообщение о том, что реальная ошибка представлена в объекте EntityValidationErrors.

Dependency Injection

Что касается Dependency Injection. Последние несколько проектов я использую structuremap (можно скачать через nuget). Тогда Registry для использования в IContainer будет выглядеть так:

public class DefaultRegistry : Registry
{
    #region Constructors and Destructors

    public DefaultRegistry()
    {
        Scan(
            scan =>
            {
                scan.TheCallingAssembly();
                scan.WithDefaultConventions();
            });     

        For<IRepository<VerificateObject>>().Use<BaseRepository<VerificateObject, LussbookDataContext>>();
        For<IRepository<UserProfileContact>>().Use<BaseRepository<UserProfileContact, LussbookDataContext>>();
        For<IRepository<UserProfile>>().Use<BaseRepository<UserProfile, LussbookDataContext>>();
        For<IRepository<UserUploadVersion>>().Use<BaseRepository<UserUploadVersion, LussbookDataContext>>();
        For<IRepository<UserUploadProfile>>().Use<BaseRepository<UserUploadProfile, LussbookDataContext>>();
        For<IRepository<UserUploadProfileContact>>().Use<BaseRepository<UserUploadProfileContact, LussbookDataContext>>();
    

    }

    #endregion
}

Класс Helper для работы с зависимостями может быть примерно такой:

public static class Resolver
{
    private static IContainer _container;


    public static IContainer Current
    {
        get
        {
            if (_container == null)
                _container = Initialize();
            return _container;
        }
    }

    private static IContainer Initialize()
    {
        return new Container(c => c.AddRegistry<DefaultRegistry>());
    }
}

 

Комментарии

comments powered by Disqus