2012-06-07 2 views
59

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

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

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

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

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

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

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

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

+1

Я бы сказал, что ссылка в номере 2 не является типичным шаблоном репозитория. Как правило, у вас есть репозиторий для каждого сводного корня в [DDD] (http://en.wikipedia.org/wiki/Domain-driven_design). Это хорошая [SO thread] (http://stackoverflow.com/questions/1958621/whats-an-aggregate-root) по этой теме. Пример в номере 2, как вы говорите, просто завершает таблицу. Похоже, что они реализуют шаблон только для реализации шаблона без реальной выгоды. Поэтому я согласился бы с тобой. – RobertMS

+0

Возможно, вы правы. Однако при поиске в Интернете большинство примеров, которые я нашел, создавали отдельные обертки для каждого объекта, в том числе в некоторых моих книгах. В этом отношении код в ссылке, которую я разместил, выглядел типичным. Спасибо за ссылки. Я проверю их. –

+1

@JonathanWood Здесь [решение мне больше нравится] (http://huyrua.wordpress.com/2010/07/13/entity-framework-4-poco-repository-and-specification-pattern/) (черт побери, я использую эта ссылка много). А именно, не общий интерфейс репозитория с общими методами. Это по-прежнему относительно тонкая оболочка вокруг «DbContext», но это позволяет упростить тестирование. –

ответ

34

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

Если у вас есть более мелкий домен или вам нужно сузить, какие сущности, которые ваш домен разрешает запрашивать, я полагаю, что # 2- или # 3, который определяет интерфейсы репозитория сущности, которые сами реализуют общий репозиторий, имеет смысл. Тем не менее, я считаю утомительным и ненужным писать интерфейс и конкретную реализацию для каждого объекта, который я хочу запросить. Что хорошего public interface IFooRepository : IRepository<Foo> (опять же, если мне не нужно ограничивать разработчиков набором разрешенных совокупных корней)?

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

Это покрывает 95% моих потребностей в персистентности. Если мне нужно выполнить какое-то действие настойчивости, которое невозможно сделать в общем случае, я использую самодельный API ICommand. Например, скажем, что я работаю с 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 или локатора службы и изменяю свой общий репозиторий, чтобы взять коллекцию зарегистрированных IRepositoryBehavior s и проверить каждое поведение против текущего типа репозитория и обернуть операцию в обработчиках 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 могут быть настоящей болью в заднице ;)

+0

Спасибо за фрагменты кода. Я потрачу некоторое время на это. –

+0

У меня наконец появилось больше времени, чтобы провести с этим. Я немного удивлен некоторыми из этого кода. Вы, казалось, говорили, что вам нравится очень общий подход, и, тем не менее, вы создаете специализированные интерфейсы, которые более конкретны, чем даже примеры, которые я изучил. Почему это необходимо? (BTW, если вам когда-нибудь захочется сделать более полную запись с исходным кодом, я бы хотел опубликовать что-то подобное на моем http://www.blackbeltcoder.com сайте.) –

12

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

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

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

В ByPhoneNumber и К организации являются только методы расширения.

+0

Спасибо, но как насчет возможных проблем с производительностью, таких как те, которые я поднял? Если вы обновляете DBC-контекст каждый раз, когда вам это нужно, существует вероятность того, что разные части кода будут запрашивать одни и те же данные и не будут кэшироваться. –

+4

@Johnathan: используйте инъекцию зависимостей, так что все, что требует DBContext, получит один и тот же контекст для каждого жизненного цикла запроса. –

0

Готовое к использованию решение находится в URF - Unit of Work & (extensible/generic) Repositories Framework. Это сэкономит вам много времени. Они реализовали общий репозиторий (также есть репозиторий async). Для расширения любого хранилища они использовали расширения, как это:

 public static decimal GetCustomerOrderTotalByYear(this IRepository<Customer> repository, string customerId, int year) 
    { 
     return repository 
      .Queryable() 
      .Where(c => c.CustomerID == customerId) 
      .SelectMany(c => c.Orders.Where(o => o.OrderDate != null && o.OrderDate.Value.Year == year)) 
      .SelectMany(c => c.OrderDetails) 
      .Select(c => c.Quantity*c.UnitPrice) 
      .Sum(); 
    } 

Некоторые классы, как QueryObject может быть переутомление в зависимости от вашего проекта, но это, относительно общего хорошее решение, чтобы помочь вам встать и бежать.

1

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

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

Database Factory (IDatabaseFactory.cs):

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

Factory Database Реализации (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. Теперь вы должны создать класс Repository вашего 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 Interface (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 (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.Delete(Usermodel); 
       return 1; 
      } 
      else 
       return 0; 
     } 
     catch 
     { 
      throw; 
     } 
    } 

} 

5.Теперь вы все установить для Repository Pattern, и вы можете получить доступ ко всем данным в контроллере пользователя:

//Here is the User Controller 
public class UserProfileController : Controller 
{ 

    IUserService _userservice; 
    public CustomerProfileController(IUserService userservice) 
    { 
     this._userservice = userservice; 
    } 

    [HttpPost] 
    public ActionResult GetAllUsers(int id) 
    { 
    User objUser=new User(); 

    objUser = _userservice.GetAllUsers().Where(x => x.Id == id).FirstOrDefault(); 

    } 
} 

Ура !!

+0

Похоже, что этот код очень много уже реализованный для вас с DbContext. Не знаю, как этот подход имеет смысл. –

Смежные вопросы