RCraven 有一些出色的见解。我想告诉你如何实施他的建议。
最好先为数据访问类定义一个接口来实现:
public interface IPostRepository
{
IEnumerable<Post> GetMostRecentPosts(int blogId);
}
然后实现一个数据类。实体框架上下文的构建成本很低,如果不处置它们,您可能会得到不一致的行为,因此我发现通常最好将您想要的数据拉入内存,然后处置上下文。
public class PostRepository : IPostRepository
{
public IEnumerable<Post> GetMostRecentPosts(int blogId)
{
// A using statement makes sure the context is disposed quickly.
using(var context = new BlogContext())
{
return context.Posts
.Where(p => p.UserId == userId)
.OrderByDescending(p => p.TimeStamp)
.Take(10)
// ToList ensures the values are in memory before disposing the context
.ToList();
}
}
}
现在您的控制器可以接受这些存储库之一作为构造函数参数:
public class BlogController : Controller
{
private IPostRepository _postRepository;
public BlogController(IPostRepository postRepository)
{
_postRepository = postRepository;
}
public ActionResult Index(int blogId)
{
var posts = _postRepository.GetMostRecentPosts(blogId);
var model = new PostsModel { Posts = posts };
if(!posts.Any()) {model.Message = "This blog doesn't have any posts yet";}
return View("Posts", model);
}
}
MVC 允许您使用自己的控制器工厂代替默认设置,因此您可以指定您的 IoC 框架(如 Ninject)决定如何创建控制器。您可以设置您的注入框架,以知道当您请求 IPostRepository 时,它应该创建一个 PostRepository 对象。
这种方法的一大优势是它使您的控制器可进行单元测试。例如,如果您想确保您的模型在没有帖子时收到消息,您可以使用像 Moq 这样的模拟框架来设置您的存储库不返回帖子的场景:
var repositoryMock = new Mock<IPostRepository>();
repositoryMock.Setup(r => r.GetMostRecentPosts(1))
.Returns(Enumerable.Empty<Post>());
var controller = new BlogController(repositoryMock.Object);
var result = (ViewResult)controller.Index(1);
Assert.IsFalse(string.IsNullOrEmpty(result.Model.Message));
这使您可以轻松地测试您期望从控制器操作中获得的特定行为,而无需设置您的数据库或任何类似的特殊操作。像这样的单元测试很容易编写,具有确定性(它们的通过/失败状态基于代码,而不是数据库内容),而且速度很快(您通常可以在一秒钟内运行一千个)。