Лучший шаблон репозитория для ASP.NET MVC

Недавно я изучил ASP.NET MVC (мне это нравится). Я работаю с компанией, которая использует инъекцию зависимости для загрузки экземпляра репозитория в каждом запросе, и я знаком с использованием этого репозитория.

Но теперь я пишу пару приложений MVC самостоятельно. Я не совсем понимаю, какие хаки и какие репозитории использует моя компания, и я пытаюсь решить наилучший подход к реализации доступа к данным.

Я использую C # и Entity Framework (со всеми последними версиями).

Я вижу три общих подхода к обработке доступа к данным.

  1. Обычный контекст БД в операторе using каждый раз, когда я обращаюсь к данным. Это просто, и все работает нормально. Однако, если в двух местах необходимо прочитать одни и те же данные в одном запросе, данные должны быть прочитаны дважды. (С одним репозиторием на запрос один и тот же экземпляр будет использоваться в обоих местах, и я понимаю, что второе чтение просто вернет данные из первого чтения).

  2. Типичный шаблон репозитория . По причинам, которые я не понимаю, этот типичный шаблон включает создание класса-оболочки для каждой таблицы, используемой в базе данных. Это кажется мне неправильным. Фактически, поскольку они реализованы также как интерфейсы, я бы технически создавал два класса-оболочки для каждой таблицы. EF создает таблицы для меня. Я не считаю, что этот подход имеет смысл.

  3. Существует также общий шаблон репозитория, в котором для каждого объекта объекта создается один класс репозитория. Это имеет для меня гораздо больше смысла. Но имеет ли это смысл для других? Является ли ссылка выше наилучшего подхода?

Я хотел бы получить некоторые материалы от других по этой теме. Вы пишете свой собственный репозиторий, используя один из приведенных выше, или делаете что-то совсем другое. Поделись, пожалуйста.

c#,asp.net-mvc,repository,repository-pattern,

60

Ответов: 4


34 принят

Я использовал смесь # 2 и # 3, но, по возможности, предпочитаю строгий общий репозиторий (более строгий, чем даже предлагаемый в ссылке для № 3). # 1 не годится, потому что он плохо работает с модульным тестированием.

Если у вас есть более мелкий домен или вам нужно сузить, какие сущности, которые ваш домен разрешает запрашивать, я полагаю, что # 2- или # 3, который определяет интерфейсы репозитория, специфичные для сущности, которые сами реализуют общий репозиторий, имеет смысл. Однако я удаляю его изнурительным и ненужным для написания интерфейса и конкретной реализации для каждого объекта, который я хочу запросить. Что хорошего Get(опять же, если мне не нужно ограничивать разработчиков набором разрешенных совокупных корней)?

Я просто определить свой общий интерфейс хранилища, с GetDeferred, Count, Get, Find, Count, и IQueryableметоды (Найти , возвращает Findинтерфейс , позволяющий LINQ), создать конкретную общую реализацию, и назвать его в день. Я сильно полагаюсь на FindLINQ. Если мне нужно использовать конкретный запрос несколько раз, я использую методы расширения и записываю запрос с помощью LINQ.

Это покрывает 95% моих потребностей в персистентности. Если мне нужно выполнить какое-то действие настойчивости, которое невозможно сделать в общем, я использую самодельный ICommandAPI. Например, скажем, что я работаю с NHibernate, и мне нужно выполнить сложный запрос как часть моего домена, или, возможно, мне нужно выполнить команду bulk. API выглядит примерно так:

// marker interface, mainly used as a generic constraint
public interface ICommand
{
}

// commands that return no result, or a non-query
public interface ICommandNoResult : ICommand
{
   void Execute();
}

// commands that return a result, either a scalar value or record set
public interface ICommandWithResult<TResult> : ICommand
{
   TResult Execute();
}

// a query command that executes a record set and returns the resulting entities as an enumeration.
public interface IQuery<TEntity> : ICommandWithResult<IEnumerable<TEntity>>
{
    int Count();
}

// used to create commands at runtime, looking up registered commands in an IoC container or service locator
public interface ICommandFactory
{
   TCommand Create<TCommand>() where TCommand : ICommand;
}

Теперь я могу создать интерфейс для представления конкретной команды.

public interface IAccountsWithBalanceQuery : IQuery<AccountWithBalance>
{
    Decimal MinimumBalance { get; set; }
}

Я могу создать конкретную реализацию и использовать необработанный SQL, NHibernate HQL, независимо от того, и зарегистрировать его с помощью моего локатора сервисов.

Теперь в моей бизнес-логике я могу сделать что-то вроде этого:

var query = factory.Create<IAccountsWithBalanceQuery>();
query.MinimumBalance = 100.0;

var overdueAccounts = query.Execute();

Вы также можете использовать шаблон спецификации IQueryдля создания значимых запросов, управляемых пользователем, вместо того, чтобы иметь интерфейс с миллионами запутывающих свойств, но предполагается, что вы не обнаруживаете, что шаблон спецификации запутывается сам по себе;).

Один последний фрагмент головоломки - это когда вашему репозиторию необходимо выполнить конкретную операцию репозитория pre-and-post. Теперь вы можете очень легко создать реализацию своего общего репозитория для определенного объекта, затем переопределить соответствующий метод (ы) и выполнить то, что вам нужно сделать, и обновить регистрацию IoC или локатора службы и выполнить с ней.

Однако иногда эта логика является сквозной и неудобной для реализации путем переопределения метода репозитория. Поэтому я создал IRepositoryBehavior, что в основном является потоком событий. (Ниже всего лишь грубое определение с головы)

public interface IRepositoryBehavior
{
    void OnAdding(CancellableBehaviorContext context);
    void OnAdd(BehaviorContext context);

    void OnGetting(CancellableBehaviorContext context);
    void OnGet(BehaviorContext context);

    void OnRemoving(CancellableBehaviorContext context);
    void OnRemove(BehaviorContext context);

    void OnFinding(CancellableBehaviorContext context);
    void OnFind(BehaviorContext context);

    bool AppliesToEntityType(Type entityType);
}

Теперь это поведение может быть чем угодно. Аудит, проверка безопасности, soft-delete, принудительное использование ограничений домена, проверка и т. Д. Я создаю поведение, регистрирую его с помощью IoC или локатора сервисов и модифицирую свой общий репозиторий для сбора коллекции зарегистрированных IRepositoryBehaviors и проверяю каждое поведение против текущий тип репозитория и завершите операцию в обработчиках pre / post для каждого применимого поведения.

Вот пример поведения мягкого удаления (soft-delete означает, что когда кто-то просит удалить объект, мы просто отмечаем его как удаленный, чтобы он не возвращался снова, но на самом деле его физически не удаляли).

public SoftDeleteBehavior : IRepositoryBehavior
{
   // omitted

   public bool AppliesToEntityType(Type entityType)
   {
       // check to see if type supports soft deleting
       return true;
   }

   public void OnRemoving(CancellableBehaviorContext context)
   {
        var entity = context.Entity as ISoftDeletable;
        entity.Deleted = true; // when the NHibernate session is flushed, the Deleted column will be updated

        context.Cancel = true; // set this to true to make sure the repository doesn't physically delete the entity.
   }
}

Да, это в основном упрощенная и абстрактная реализация прослушивателей событий NHibernate, но вот почему мне это нравится. A) Я могу тестировать поведение, не внося NHibernate в изображение. B) Я могу использовать эти поведения вне NHibernate (скажем, репозиторий - это реализация клиента, которая обертывает вызовы службы REST). C) Слушатели событий NH могут быть настоящей болью в заднице ;)


Я бы рекомендовал номер 1 с некоторыми оговорками. Номер 2 - это то, что кажется наиболее распространенным, но, по моему опыту, репозиторий просто заканчивается грязной свалки для запросов. Если вы используете общий репозиторий (2), это всего лишь тонкая оболочка вокруг DBContext, немного бессмысленная, если вы не планируете менять ORM (плохая идея).

Но когда я напрямую обращаюсь к DBContext, я предпочитаю использовать шаблон Pipes and Filters, чтобы вы могли повторно использовать общую логику, что-то вроде

items = DBContext.Clients
    .ByPhoneNumber('1234%')
    .ByOrganisation(134);

ByPhoneNumber и By Organization - это просто методы расширения.


1

Здесь мы используем шаблон Best Repository в Asp.Net MVC:

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

База данных Factory (IDatabaseFactory.cs):

public interface IDatabaseFactory : IDisposable
{
    Database_DBEntities Get();
}

Реализация баз данных (DatabaseFactory.cs):

public class DatabaseFactory : Disposable, IDatabaseFactory
{
    private Database_DBEntities dataContext;
    public Database_DBEntities Get()
    {
        return dataContext ?? (dataContext = new Database_DBEntities());
    }

    protected override void DisposeCore()
    {
        if (dataContext != null)
            dataContext.Dispose();
    }
}

Базовый интерфейс (IRepository.cs):

public interface IRepository<T> where T : class
{
    void Add(T entity);
    void Update(T entity);
    void Detach(T entity);
    void Delete(T entity);
    T GetById(long Id);
    T GetById(string Id);
    T Get(Expression<Func<T, bool>> where);
    IEnumerable<T> GetAll();
    IEnumerable<T> GetMany(Expression<Func<T, bool>> where);
    void Commit();
}

Абстрактный класс (Repository.cs):

public abstract class Repository<T> : IRepository<T> where T : class
{
    private Database_DBEntities dataContext;
    private readonly IDbSet<T> dbset;
    protected Repository(IDatabaseFactory databaseFactory)
    {
        DatabaseFactory = databaseFactory;
        dbset = DataContext.Set<T>();
    }

    /// <summary>
    /// Property for the databasefactory instance
    /// </summary>
    protected IDatabaseFactory DatabaseFactory
    {
        get;
        private set;
    }

    /// <summary>
    /// Property for the datacontext instance
    /// </summary>
    protected Database_DBEntities DataContext
    {
        get { return dataContext ?? (dataContext = DatabaseFactory.Get()); }
    }

    /// <summary>
    /// For adding entity
    /// </summary>
    /// <param name="entity"></param>
    public virtual void Add(T entity)
    {
        try
        {
            dbset.Add(entity);
            //  dbset.Attach(entity);
            dataContext.Entry(entity).State = EntityState.Added;
            int iresult = dataContext.SaveChanges();
        }
        catch (UpdateException ex)
        {
        }
        catch (DbUpdateException ex) //DbContext
        {
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    /// <summary>
    /// For updating entity
    /// </summary>
    /// <param name="entity"></param>
    public virtual void Update(T entity)
    {
        try
        {
            // dbset.Attach(entity);
            dbset.Add(entity);
            dataContext.Entry(entity).State = EntityState.Modified;
            int iresult = dataContext.SaveChanges();
        }
        catch (UpdateException ex)
        {
            throw new ApplicationException(Database_ResourceFile.DuplicateMessage, ex);
        }
        catch (DbUpdateException ex) //DbContext
        {
            throw new ApplicationException(Database_ResourceFile.DuplicateMessage, ex);
        }
        catch (Exception ex) {
            throw ex;
        }
    }

    /// <summary>
    /// for deleting entity with class 
    /// </summary>
    /// <param name="entity"></param>
    public virtual void Delete(T entity)
    {
        dbset.Remove(entity);
        int iresult = dataContext.SaveChanges();
    }

    //To commit save changes
    public void Commit()
    {
        //still needs modification accordingly
        DataContext.SaveChanges();
    }

    /// <summary>
    /// Fetches values as per the int64 id value
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public virtual T GetById(long id)
    {
        return dbset.Find(id);
    }

    /// <summary>
    /// Fetches values as per the string id input
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public virtual T GetById(string id)
    {
        return dbset.Find(id);
    }

    /// <summary>
    /// fetches all the records 
    /// </summary>
    /// <returns></returns>
    public virtual IEnumerable<T> GetAll()
    {
        return dbset.AsNoTracking().ToList();
    }

    /// <summary>
    /// Fetches records as per the predicate condition
    /// </summary>
    /// <param name="where"></param>
    /// <returns></returns>
    public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where)
    {
        return dbset.Where(where).ToList();
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="entity"></param>
    public void Detach(T entity)
    {
        dataContext.Entry(entity).State = EntityState.Detached;
    }

    /// <summary>
    /// fetches single records as per the predicate condition
    /// </summary>
    /// <param name="where"></param>
    /// <returns></returns>
    public T Get(Expression<Func<T, bool>> where)
    {
        return dbset.Where(where).FirstOrDefault<T>();
    }
}

Как получить доступ к шаблону репозитория в контроллере:

1. У вас есть модель пользователя:

public partial class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

2. Теперь вам нужно создать класс репозитория вашего UserModel

public class UserRepository : Repository<User>, IUserRepository
{
    private Database_DBEntities dataContext;

    protected IDatabaseFactory DatabaseFactory
    {
        get;
        private set;
    }

    public UserRepository(IDatabaseFactory databaseFactory)
        : base(databaseFactory)
    {
        DatabaseFactory = databaseFactory;
    }

    protected Database_DBEntities DataContext
    {
        get { return dataContext ?? (dataContext = DatabaseFactory.Get()); }
    }

    public interface IUserRepository : IRepository<User>
    { 
    }
}

3. Теперь вам нужно создать интерфейс UserService (IUserService.cs) со всеми методами CRUD:

public interface IUserService
{
    #region User Details 
    List<User> GetAllUsers();
    int SaveUserDetails(User Usermodel);
    int UpdateUserDetails(User Usermodel);
    int DeleteUserDetails(int Id);
    #endregion
}

4. Теперь вам нужно создать UserService Interface (UserService.cs) со всеми методами CRUD:

public class UserService : IUserService
{
    IUserRepository _userRepository;
    public UserService() { }
    public UserService(IUserRepository userRepository)
    {
        this._userRepository = userRepository;
    }

    public List<User> GetAllUsers()
    {
        try
        {
            IEnumerable<User> liUser = _userRepository.GetAll();
            return liUser.ToList();
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    /// <summary>
    /// Saves the User details.
    /// </summary>
    /// <param name="User">The deptmodel.</param>
    /// <returns></returns>
    public int SaveUserDetails(User Usermodel)
    {
        try
        {
            if (Usermodel != null)
            {
                _userRepository.Add(Usermodel);
                return 1;
            }
            else
                return 0;
        }
        catch
        {
            throw;
        }
   }

   /// <summary>
   /// Updates the User details.
   /// </summary>
   /// <param name="User">The deptmodel.</param>
   /// <returns></returns>
   public int UpdateUserDetails(User Usermodel)
   {
       try
       {
           if (Usermodel != null)
           {
               _userRepository.Update(Usermodel);
               return 1;
           }
           else
               return 0;
       }
       catch
       {
           throw;
       }
   }

   /// <summary>
   /// Deletes the User details.
   /// </summary>
   /// <param name="Id">The code identifier.</param>
   /// <returns></returns>
   public int DeleteUserDetails(int Id)
   {
       try
       {
           User Usermodel = _userRepository.GetById(Id);
           if (Usermodel != null)
           {
               _userRepositoryПохожие вопросы