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

本文关键字:存储 模式 最佳 NET MVC ASP | 更新日期: 2023-09-27 18:36:35

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

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

我正在使用 C# 和实体框架(所有最新版本)。

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

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

  2. 典型的存储库模式。出于我不明白的原因,这种典型的模式涉及为数据库中使用的每个表创建一个包装类。这对我来说似乎是错误的。事实上,由于它们也是作为接口实现的,因此从技术上讲,我将为每个表创建两个包装类。EF 为我创建表。我不认为这种方法有意义。

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

我很想从其他人那里得到一些关于这个主题的意见。您是编写自己的存储库,使用上述存储库之一,还是完全执行其他操作。请分享。

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

我使用了 #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的规范模式来构建有意义的、用户输入驱动的查询,而不是拥有一个包含数百万个令人困惑的属性的接口,但这假设您不会发现规范模式本身;)令人困惑。

最后一个难题是当您的存储库需要执行特定的存储库前和存储库后操作时。现在,您可以非常轻松地为特定实体创建通用存储库的实现,然后覆盖相关方法并执行您需要执行的操作,并更新 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,但有一些警告。数字 2 似乎是最常见的,但根据我的经验,存储库最终会成为查询的混乱垃圾场。如果您使用通用存储库(2),它只是DBContext的薄包装器,除非您打算更改ORM(坏主意),否则实际上毫无意义。

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

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

电话号码和按组织只是扩展方法。

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

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

Database Factory (IDatabaseFactory.cs):

public interface IDatabaseFactory : IDisposable
{
    Database_DBEntities Get();
}
数据库工厂

实现(数据库工厂.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();
    }
}

基本接口(独立.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();
}

抽象类(存储库.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.现在您必须创建用户模型的存储库类

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方法创建用户服务接口(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.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 - Work Unit of Work&(可扩展/通用)存储库框架中有一个现成的解决方案。这将为您节省大量时间。他们实现了一个通用存储库(也有一个异步存储库)。为了扩展任何存储库,他们使用了这样的扩展:

     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)可能会过度工作,具体取决于您的项目,但总的来说,它是帮助您启动和运行的好解决方案。