ASP.NET MVC的最佳存储库模式

我最近学习ASP.NET MVC(我喜欢它)。 我正在与一家公司使用dependency injection来加载每个请求中的存储库实例,我熟悉使用该存储库。

但是现在我正在写一些我自己的MVC应用程序。 我并不完全了解我公司使用的存储库的方式和原因,我正在试图确定实现数据访问的最佳方法。

我正在使用C#和entity framework(与所有最新版本)。

我看到了三种处理数据访问的一般方法。

  1. 每次访问数据时,使用语句中的常规数据库上下文。 这很简单,它工作正常。 但是,如果两个位置需要在一个请求中读取相同的数据,则必须读取两次数据。 (每个请求使用一个存储库,两个地方都使用相同的实例,我知道第二次读取只是从第一次读取返回数据。)

  2. 典型的存储库模式 。 由于我不明白的原因,这个典型的模式涉及到为数据库中使用的每个表创build一个包装类。 这对我来说似乎是错误的。 实际上,由于它们也是作为接口来实现的,所以在技术上我会为每个表创build两个包装类。 EF为我创build表格。 我不相信这种方法是有道理的。

  3. 还有一个通用的存储库模式 ,其中创build单个存储库类来为所有实体对象提供服务。 这对我来说更有意义。 但是对别人有意义吗? 链接是否是最好的方法?

我很想得到这个话题的其他人的意见。 你正在编写自己的存储库,使用上述之一,或完全不同的东西。 请分享。

我已经使用了#2和#3的混合,但我更喜欢一个严格的通用存储库(如果可能的话,甚至比#3的链接更严格)。 #1不好,因为它在unit testing中效果不佳。

如果您的域较小或者需要限制您的域允许查询哪些实体,那么我认为#2-或#3定义了实体特定的存储库接口,它们自己实现了一个通用存储库 – 这是合理的。 然而,我发现为每个我想查询的实体编写一个接口和一个具体的实现是耗尽和不必要的。 public interface IFooRepository : IRepository<Foo>什么好处public interface IFooRepository : IRepository<Foo> (同样,除非我需要将开发人员限制为一组允许的聚合根)。

我只是使用AddRemoveGetGetDeferredCountFind方法(Find返回一个允许LINQ的IQueryable接口)来定义我的通用存储库接口,创build一个具体的通用实现,然后每天调用它。 我很大程度上依赖于Find和LINQ。 如果我需要多次使用特定查询,我使用扩展方法并使用LINQ编写查询。

这涵盖了95%的持久性需求。 如果我需要执行一些不能一般完成的持久化操作,我使用本地ICommand API。 例如,说我正在使用NHibernate,我需要执行一个复杂的查询作为我的域的一部分,或者我可能需要做一个批量命令。 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; } 

现在我可以创build一个接口来表示一个特定的命令。

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

我可以创build一个具体的实现,并使用原始SQL,NHibernate的HQL,无论如何,并注册它与我的服务定位器。

现在在我的业务逻辑中,我可以做这样的事情:

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

你也可以在IQuery使用规范模式来构build有意义的,用户input驱动的查询,而不是具有百万令人迷惑的属性的接口,但是假设你没有发现规范模式本身的困惑;)。

最后一块难题是,当您的存储库需要执行特定的存储前和存储库操作时。 现在,您可以非常轻松地为特定实体创build通用存储库的实现,然后覆盖相关的方法并执行您所需要的操作,并更新您的IoC或服务定位器注册并完成相应的操作。

但是,有时这种逻辑是交叉的,而且很难通过重写一个存储库方法来实现。 所以我创build了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); } 

现在,这些行为可以是任何事情。 审计,安全检查,软删除,强制域限制,validation等。我创build一个行为,将其注册到IoC或服务定位器,并修改我的通用存储库以接收已注册的IRepositoryBehavior集合,并检查每个行为针对当前的存储库types,并将操作包装在每个适用行为的前/后处理程序中。

下面是一个软删除行为的例子(软删除意味着当有人要求删除一个实体时,我们只是将其标记为已删除,所以它不能被再次返回,但实际上从未被实际删除)。

 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)我可以unit testing一个行为,而不将NHibernate带入图片B)我可以在NHibernate之外使用这些行为(比如说存储库是包装REST服务调用的客户端实现)C)NH的事件监听器可以是一个真正的痛苦;)

我会build议数字1,有一些注意事项。 数字2似乎是最常见的,但根据我的经验,存储库只是结束了一个凌乱的倾倒地面查询。 如果你使用一个通用的资源库(2),它只是一个简单的DBContext包装器,除非你打算改变ORM(坏主意),否则有点毫无意义。

但是,当我直接访问DBContext时,我更喜欢使用Pipes和Filters模式,这样可以重用常用的逻辑

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

ByPhoneNumber和By Organization只是扩展方法。

在这里,我们将使用Asp.Net MVC中的最佳存储库模式:

存储库模式在应用程序的数据层和域层之间添加了一个分隔层。 它也使应用程序的数据访问部分更好地testing。

数据库工厂(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.现在您必须创build您的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.现在必须使用所有CRUD方法创buildUserService接口(IUserService.cs):

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

4.现在你必须用所有的CRUD方法创buildUserService接口(UserService.cs):

 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.现在您全部设置为您的存储库模式,并且您可以访问用户控制器中的所有数据:

 //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(); } } 

干杯!

在URF – 工作单元和(可扩展/通用)存储库框架中有一个随时可用的解决scheme。 它会为你节省很多时间。 他们实现了一个通用的仓库(也有一个asynchronous仓库)。 为了扩展任何存储库,他们使用了这样的扩展:

  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这样的类可能是一个过度的工作,这取决于你的项目,但总的来说这是帮助你起步和运行的很好的解决scheme。