首页 文章

在哪里放置与数据访问相关的业务逻辑层,同时通过依赖注入提供单元可测试性?

提问于
浏览
0

我正在寻找一种方法,在具有Entity Framework的MVC4应用程序中拥有单独的业务逻辑层,而无需在其中注入我的(真实或虚假)数据库上下文 . 现在我们有一个虚假和真实的数据库上下文,它来自 IContext ,一个存储库 Repository 和一个控制器 MyController . 使用以下模式将 IContext 注入到控制器和存储库中:

public class MyController : Controller
{
    readonly Repository _repo;

    /// <summary>
    ///     Initializes a new instance of the <see cref="Controllers.MyController"/> class which
    ///     makes use of the real Entity Framework context that connects with the database.
    /// </summary>
    public MyController()
    {
        _repo = new Repository();
    }

    /// <summary>
    ///     Initializes a new instance of the <see cref="Controllers.MyController"/> class which
    ///     can make use of a "Fake" Entity Framework context for unit testing purposes.
    /// </summary>
    /// <param name="db">Database context.</param>
    public MyController(IContext db)
    {
        _repo = new Repository(db); 
    }
    (...)
}

这是这样设置的,因为它是具有访问数据库并需要伪上下文的方法的存储库,而单元测试针对控制器并且需要实例化 .

现在我正在寻找一种方法来向项目添加业务逻辑类,其中将放置具有业务逻辑的方法 . 这些方法也需要从数据访问层( Repository )获得的信息 .

使用上述模式在此业务逻辑层中为 Repository 注入 IContext 感觉有点奇怪 . 例如 . ,

BusinessLogic(IContext db) 
{
     _repo = new Repository(db);
}

我看到两种方法来避免这个问题:

  • 调用控制器中的存储库,并将作为参数获取的数据用于业务逻辑层中的方法 .

  • 无论如何都要注入 .

我正在寻找一个好主意, Build 与数据访问相关的业务逻辑层,并提供单元可测试性,最好不必在每个类中注入数据库上下文 .

希望有人能为我提供一些见解 . 谢谢 .

2 回答

  • 1

    如果要使用EF6,则具有继承自IContext或DbContext的EF上下文

    public class UserContext : DbContext 
        {
            DbSet<User> users { get; set; }        
        }
    

    您不希望将上下文直接注入Controler .

    使用Service / DAO / Repository,它在构造函数中获取IContext / DbContext实例并处理数据访问 . 在我的示例中,我使用IUserService接受上下文并按ID返回用户 .

    public interface IUserService
        {        
           User GetUserByID(int userId);        
        }
    
        public class UserService : IService
        {
    
          private readonly UserContext _context;
    
          //Inject your mock or real context here
          public UserService(UserContext context)
          { 
    
             this._context = context;
    
          }
    
          //Implement IUserService
          public User GetUserByID(int userId)
          {  
              _context.Users.Where(u=>u.ID==userId).FirstOrDefault();
          }
    
        }
    

    然后通过接口将服务注入控制器 .

    public class UserController : Controller
      {
    
         private readonly IUserService _service;
    
         //Consider using a Dependency injection framework e.g. Unity
         public UserController(IUserService service)
         { 
            this.service = service;
         } 
    
         //The method that is tightly coupled to the view and uses the service
         [HttpGet] 
         public ActionResult GetUserByID(int id)
         {
              return View(_service.GetUserById(id));
         }
     }
    

    另外,请考虑使用模拟框架来模拟您的上下文,例如起订量

    [TestMethod]
    public void UserServiceTest()
    {
            //Initialize your Mock Data
            var testDataUser = new List<Users> 
            { 
                new User{
                                    ID = 1,
                                    Name = "MockUser"
                }
            }.AsQueryable();
    
             //Initialize the Mock DbSet
            var mockSetUser = new Mock<DbSet<User>>();
            mockSetUser.As<IQueryable<User>>().Setup(m => m.Provider).Returns(testDataUser. .Provider);
            mockSetUser.As<IQueryable<User>>().Setup(m => m.Expression).Returns(testDataUser .Expression);
            mockSetUser.As<IQueryable<User>>().Setup(m => m.ElementType).Returns(testDataUser .ElementType);
            mockSetUser.As<IQueryable<User>>().Setup(m => m.GetEnumerator()).Returns(testDataUser .GetEnumerator());
    
             //Initialize the mock Context
            var mockContext = new Mock<UserContext>();
    
            //Return the mock DbSet via the mock context
            mockContext.Setup(c => c.Users).Returns(mockSetUser.Object);
    
            //Create the service and inject the mock context
            var userService = new UserService(mockContext.Object)
    
            //Test your context via the service
            var user = userService.GetUserByID(1);
    
            Assert.AreEqual("MockUser", user.Name);
    }
    
  • 5

    我同意GMich . 同样......你的图层之间应该存在硬边界 . 它可能看起来像这样:

    IUserController < - IUserProcessor < - IUserRepository < - IContext

    您的DI和构造函数可以是:

    public UserController(IUserProcessor userProcessor){...}
    public UserProcessor(IUserRepository userRepository){...}
    public UserRepository(IContext context){...}
    public MyAppContext(string connectionString){...}
    

    创建将业务逻辑和存储库封装为属性的容器也很方便 . 例如:

    public class RepositoryContainer : IRepositoryContainer
    {
      private readonly IContext _context;
      
      private IUserRepository _userRepository;
      private IUserProfileRepository _userProfileRepository;
      
      public RepositoryContainer(IContext context)
      {
        if (context == null) throw new ArgumentNullException("context");
        
        _context = context;
      }
      
      public IUserRepository UserRepository
      {
        get 
        { 
          return _userRepository = _userRepository ?? new UserRepository(_context); 
        }
      }
      
      public IUserProfileRepository UserProfileRepository
      {
        get
        {
          return _userProfileRepository = _userProfileRepository ?? new UserProfileRepository(_context);
        }
      }
    }
    

    并且澄清一下,使用这种方法时,也可能有IBusinessLogicContainer . 我们不希望在同一容器中加入BL和Repo对象 .

相关问题