简体   繁体   English

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

[英]Best Repository Pattern for ASP.NET MVC

I recently learned ASP.NET MVC (I love it). 我最近学习了ASP.NET MVC(我喜欢它)。 I'm working with a company that uses dependency injection to load a Repository instance in each request, and I'm familiar with using that repository. 我正在与一家使用依赖注入的公司合作,在每个请求中加载Repository实例,我熟悉使用该存储库。

But now I'm writing a couple of MVC applications of my own. 但现在我正在编写自己的几个MVC应用程序。 I don't fully understand the hows and whys of the repository my company uses, and I'm trying to decide the best approach to implement data access. 我不完全理解我公司使用的存储库的方法和原因,我正在尝试确定实现数据访问的最佳方法。

I am using C# and Entity Framework (with all the latest versions). 我正在使用C#和Entity Framework(包含所有最新版本)。

I see three general approaches for handling data access. 我看到了处理数据访问的三种通用方法。

  1. Regular DB context within a using statement each time I access data. 每次访问数据时,using语句中的常规DB上下文。 This is simple and it works okay. 这很简单,工作正常。 However, if two locations need to read the same data within one request, the data must be read twice. 但是,如果两个位置需要在一个请求中读取相同的数据,则必须读取两次数据。 (With a single repository per request, the same instance would be used in both places and I understand the second read would simply return the data from the first read.) (每个请求使用一个存储库,两个地方都会使用相同的实例,我理解第二次读取只会返回第一次读取的数据。)

  2. A typical repository pattern . 典型的存储库模式 For reasons I don't understand, this typical pattern involves creating a wrapper class for every table used from the database. 由于我不理解的原因,这种典型的模式涉及为数据库中使用的每个表创建一个包装类。 That seems wrong to me. 这对我来说似乎不对。 In fact, since they are implemented also as interfaces, I'd technically be creating two wrapper classes for each table. 事实上,由于它们也是作为接口实现的,我在技术上会为每个表创建两个包装类。 EF creates tables for me. EF为我创建表格。 I don't believe this approach makes sense. 我不相信这种方法是有道理的。

  3. There is also a generic repository pattern where a single repository class is created to serve all entity objects. 还有一个通用存储库模式 ,其中创建单个存储库类以服务所有实体对象。 This makes much more sense to me. 这对我来说更有意义。 But does it make sense to others? 但对别人有意义吗? Is the link above the best approach? 链接是最好的方法吗?

I'd love to get some input from others on this topic. 我很想从其他人那里得到关于这个话题的一些意见。 Are you writing your own repository, using one of those above, or doing something different altogether. 您是在编写自己的存储库,使用上述方法之一,还是完全不同的方式。 Please share. 请分享。

I have used a blend of #2 and #3, but I prefer a strict generic repository if possible (stricter than even suggested in the link for #3). 我使用了#2和#3的混合,但如果可能的话,我更喜欢严格的通用存储库(比#3的链接更严格)。 #1 is no good because it plays poorly with unit testing. #1不好,因为它在单元测试中表现不佳。

If you have a smaller domain or need to constrict which entities that your domain allows to be queried, I suppose #2- or #3 that defines entity specific repository interfaces that themselves implement a generic repository- makes sense. 如果您有一个较小的域或需要限制您的域允许查询的实体,我认为#2-或#3定义了自己实现通用存储库的实体特定存储库接口 - 这是有意义的。 However, I find it to be exhausting and unnecessary to write an interface and a concrete implementation for every entity I want to query. 但是,我发现为我想要查询的每个实体编写一个接口和一个具体的实现是很费劲的。 What good is public interface IFooRepository : IRepository<Foo> (again, unless I need to constrain developers to a set of allowed aggregate roots)? public interface IFooRepository : IRepository<Foo>什么public interface IFooRepository : IRepository<Foo> (同样,除非我需要将开发人员限制为一组允许的聚合根)?

I just define my generic repository interface, with Add , Remove , Get , GetDeferred , Count , and Find methods (Find returns an IQueryable interface allowing LINQ), create a concrete generic implementation, and call it a day. 我只是定义了我的通用存储库接口,使用AddRemoveGetGetDeferredCountFind方法(Find返回一个允许LINQ的IQueryable接口),创建一个具体的通用实现,并将其称为一天。 I rely heavily on Find and thus LINQ. 我非常依赖于Find和LINQ。 If I need to use a specific query more than once, I use extension methods and write the query using LINQ. 如果我需要多次使用特定查询,我使用扩展方法并使用LINQ编写查询。

This covers 95% of my persistence needs. 这涵盖了我95%的持久性需求。 If I need to perform some sort of persistence action that can't be done generically, I use a home-grown ICommand API. 如果我需要执行某些通常无法完成的持久性操作,我会使用自行开发的ICommand API。 For example, say I'm working with NHibernate and I need to perform a complex query as part of my domain, or perhaps I need to do a bulk command. 例如,假设我正在使用NHibernate,我需要执行一个复杂的查询作为我的域的一部分,或者我可能需要执行批量命令。 The API looks roughly like this: 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;
}

Now I can create an interface to represent a specific command. 现在我可以创建一个表示特定命令的接口。

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

I can create a concrete implementation and use raw SQL, NHibernate HQL, whatever, and register it with my service locator. 我可以创建一个具体的实现并使用原始SQL,NHibernate HQL,等等,并将其注册到我的服务定位器。

Now in my business logic I can do something like this: 现在在我的业务逻辑中,我可以做这样的事情:

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

var overdueAccounts = query.Execute();

You can also use a Specification pattern with IQuery to build meaningful, user-input-driven queries, rather than having an interface with million confusing properties, but that assumes you don't find the specification pattern confusing in its own right ;). 您还可以使用带有IQuery的规范模式来构建有意义的,用户输入驱动的查询,而不是具有百万个令人困惑的属性的接口,但这假设您没有发现规范模式本身令人困惑;)。

One last piece of the puzzle is when your repository needs to do specific pre- and -post repository operation. 最后一个难题是当您的存储库需要执行特定的pre-and-post存储库操作时。 Now, you can very easily create an implementation of your generic repository for a specific entity, then override the relevant method(s) and do what you need to do, and update your IoC or service locator registration and be done with it. 现在,您可以非常轻松地为特定实体创建通用存储库的实现,然后覆盖相关方法并执行您需要执行的操作,并更新您的IoC或服务定位器注册并完成它。

However, sometimes this logic is cross-cutting and awkward to implement by overriding a repository method. 但是,有时这种逻辑是交叉的,并且通过覆盖存储库方法很难实现。 So I created IRepositoryBehavior , which is basically an event sink. 所以我创建了IRepositoryBehavior ,它基本上是一个事件接收器。 (Below is just a rough definition off the top of my head) (下面只是我脑海中的粗略定义)

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

Now, these behaviors can be anything. 现在,这些行为可以是任何东西。 Auditing, security checking, soft-delete, enforcing domain constraints, validation, etc. I create a behavior, register it with the IoC or service locator, and modify my generic repository to take in a collection of registered IRepositoryBehavior s, and check each behavior against the current repository type and wrap the operation in the pre/post handlers for each applicable behavior. 审计,安全检查,软删除,强制执行域约束,验证等。我创建一个行为,将其注册到IoC或服务定位器,并修改我的通用存储库以接收已注册的IRepositoryBehavior的集合,并检查每个行为针对当前存储库类型,并针对每个适用的行为将操作包装在前/后处理程序中。

Here's an example soft-delete behavior (soft-delete means that when someone asks to delete an entity, we just mark it as deleted so it can't be returned again, but is never actually physically removed). 这是一个示例软删除行为(软删除意味着当有人要求删除实体时,我们只是将其标记为已删除,因此无法再次返回,但实际上从未实际删除过)。

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.
   }
}

Yes, this is basically a simplified and abstracted implementation of NHibernate's event listeners, but that's why I like it. 是的,这基本上是NHibernate事件监听器的简化和抽象实现,但这就是我喜欢它的原因。 A) I can unit test a behavior without bringing NHibernate into the picture B) I can use these behaviors outside of NHibernate (say the repository is client implementation that wraps REST service calls) C) NH's event listeners can be a real pain in the ass ;) A)我可以在不将NHibernate带入图片的情况下对行为进行单元测试B)我可以在NHibernate之外使用这些行为(比如存储库是包装REST服务调用的客户端实现)C)NH的事件监听器可能是一个真正的痛苦屁股;)

I would recommend number 1, with some caveats. 我会建议1号,但有一些警告。 Number 2 is what seems to be most common but in my experience the repository just ends up a messy dumping ground for queries. 2号似乎是最常见的,但根据我的经验,存储库最终会成为查询的混乱倾倒场。 If you use a generic repository (2) it is just a thin wrapper around the DBContext, a bit pointless really unless you are planning on changing ORM's (bad idea). 如果你使用通用存储库(2),它只是围绕DBContext的一个薄包装,除非你打算改变ORM(糟糕的主意),否则真的有点无意义。

But when I access DBContext directly I prefer to use a Pipes and Filters pattern so you can reuse common logic, something like 但是当我直接访问DBContext时,我更喜欢使用管道和过滤器模式,因此您可以重用常见的逻辑,例如

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

The ByPhoneNumber and By Organisation are just extension methods. ByPhoneNumber和By Organization只是扩展方法。

Here we go for Best Repository Pattern in Asp.Net MVC: 在这里,我们选择Asp.Net MVC中的最佳存储库模式:

The Repository pattern adds a separation layer between the data and domain layers of an application. 存储库模式在应用程序的数据层和域层之间添加了分隔层。 It also makes the data access parts of an application better testable. 它还使应用程序的数据访问部分更易于测试。

Database Factory (IDatabaseFactory.cs): 数据库工厂(IDatabaseFactory.cs):

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

Database Factory Implementations (DatabaseFactory.cs): 数据库工厂实现(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();
    }
}

Base Interface (IRepository.cs): 基本接口(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();
}

Abstract Class (Repository.cs): 抽象类(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>();
    }
}

How to access this repository pattern in controller: 如何在控制器中访问此存储库模式:

1. You have User Model : 1.你有用户模型:

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

2. Now you have to create Repository Class of your UserModel 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. Now you have to create UserService Interface (IUserService.cs) with all CRUD Methods: 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. Now you have to create UserService Interface (UserService.cs) with all CRUD Methods: 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. Now you All set for your Repository Pattern and you can access all data in User Controller: 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();
    }
}

There is a ready to use solution at URF - Unit of Work & (extensible/generic) Repositories Framework . URF上有一个现成的解决方案- 工作单元和(可扩展/通用)存储库框架 It will save you a lot of time. 它会为你节省很多时间。 They implemented a generic repository (also there is an async repository). 他们实现了一个通用存储库(也有一个异步存储库)。 For extending any repository they have used extensions like this: 为了扩展任何存储库,他们使用了这样的扩展:

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

Some classes like QueryObject may be an overwork depending on your project but overally it is good solution to help you get up and running. 像QueryObject这样的一些类可能会过度工作,这取决于你的项目,但总的来说它是帮助你启动和运行的好方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM