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

时间:2012-06-07 03:21:13

标签: c# asp.net-mvc repository repository-pattern

我最近学过ASP.NET MVC(我喜欢它)。我正在与一家使用依赖注入的公司合作,在每个请求中加载Repository实例,我熟悉使用该存储库。

但现在我正在编写自己的几个MVC应用程序。我不完全理解我公司使用的存储库的方法和原因,我正在尝试确定实现数据访问的最佳方法。

我正在使用C#和Entity Framework(包含所有最新版本)。

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

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

  2. 一个典型的repository pattern。由于我不理解的原因,这种典型的模式涉及为数据库中使用的每个表创建一个包装类。这对我来说似乎不对。事实上,由于它们也是作为接口实现的,我在技术上会为每个表创建两个包装类。 EF为我创建表格。我不相信这种方法是有道理的。

  3. 还有一个generic repository pattern,其中创建了一个存储库类来为所有实体对象提供服务。这对我来说更有意义。但对别人有意义吗?链接是最好的方法吗?

  4. 我很乐意从其他人那里获得有关此主题的一些意见。您是在编写自己的存储库,使用上述方法之一,还是完全不同的方式。请分享。

4 个答案:

答案 0 :(得分:36)

我使用了#2和#3的混合,但如果可能的话,我更喜欢严格的通用存储库(比#3的链接更严格)。 #1不好,因为它在单元测试中表现不佳。

如果您有一个较小的域或需要限制您的域允许查询的实体,我认为#2-或#3定义了自己实现通用存储库的实体特定存储库接口 - 这是有意义的。但是,我发现为我想要查询的每个实体编写一个接口和一个具体的实现是很费劲的。 public interface IFooRepository : IRepository<Foo>有什么用(再次,除非我需要将开发人员限制在一组允许的聚合根中)?

我只使用AddRemoveGetGetDeferredCountFind方法定义了我的通用存储库界面( Find返回一个允许LINQ的IQueryable接口,创建一个具体的通用实现,并在一天内调用它。我非常依赖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;
}

现在我可以创建一个代表特定命令的界面。

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

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

这是一个示例软删除行为(软删除意味着当有人要求删除实体时,我们只是将其标记为已删除,因此无法再次返回,但实际上从未实际删除过。)

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 :(得分:12)

我会推荐1号,但有一些警告。 2号似乎是最常见的,但根据我的经验,存储库最终会成为查询的混乱倾倒场。如果你使用通用存储库(2),它只是围绕DBContext的一个薄包装,除非你打算改变ORM(糟糕的主意),否则真的有点无意义。

但是当我直接访问DBContext时,我更喜欢使用管道和过滤器模式,因此您可以重用常用逻辑,例如

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

ByPhoneNumber和By Organization只是扩展方法。

答案 2 :(得分:1)

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

存储库模式在应用程序的数据层和域层之间添加了一个分隔层。它还使应用程序的数据访问部分更易于测试。

数据库工厂(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的Repository Class

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方法创建UserService接口(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方法创建UserService接口(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();
    }
}

答案 3 :(得分:0)

URF - Unit of Work & (extensible/generic) Repositories Framework有一个随时可用的解决方案。它会为你节省很多时间。 他们实现了一个通用存储库(也有一个异步存储库)。为了扩展任何存储库,他们使用了这样的扩展:

     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这样的某些类可能会过度工作,具体取决于你的项目,但总的来说它是帮助你启动和运行的好方法。