64

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

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

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

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

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

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

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

我很想从其他人那里得到一些关于这个话题的意见。您是在编写自己的存储库,使用上述其中之一,还是在做一些完全不同的事情。请分享。

4

4 回答 4

35

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

如果您有一个较小的域或需要限制您的域允许查询的实体,我认为定义实体特定存储库接口的 #2- 或 #3 本身实现通用存储库 - 是有意义的。但是,我发现为我要查询的每个实体编写接口和具体实现既累人又没有必要。有什么好处public interface IFooRepository : IRepository<Foo>(同样,除非我需要将开发人员限制在一组允许的聚合根上)?

我只是定义了我的通用存储库接口,使用Add, Remove, Get, GetDeferred,CountFind方法(Find 返回一个IQueryable允许 LINQ 的接口),创建一个具体的通用实现,并称之为一天。我严重依赖FindLINQ。如果我需要多次使用特定查询,我会使用扩展方法并使用 LINQ 编写查询。

这涵盖了我 95% 的持久性需求。如果我需要执行一些一般情况下无法完成的持久性操作,我会使用自制的ICommandAPI。例如,假设我正在使用 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 的事件侦听器可能是一个真正的麻烦;)

于 2012-06-07T04:04:15.027 回答
12

我会推荐 1 号,但有一些注意事项。2 号似乎是最常见的,但根据我的经验,存储库最终只是一个用于查询的杂乱垃圾场。如果您使用通用存储库 (2),它只是 DBContext 的一个薄包装器,除非您打算更改 ORM(坏主意),否则真的有点毫无意义。

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

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

ByPhoneNumber 和 By Organization 只是扩展方法。

于 2012-06-07T03:37:30.650 回答
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 类

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();
    }
}
于 2017-07-13T06:15:54.963 回答
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 这样的类可能会过度工作,但总的来说,它是帮助您启动和运行的好解决方案。

于 2017-01-16T11:00:10.417 回答