RCravens有些很棒的见解。我想展示一下如何实现他的建议。
最好从定义一个数据访问类接口开始实现:
public interface IPostRepository
{
IEnumerable<Post> GetMostRecentPosts(int blogId);
}
然后实现一个数据类。Entity Framework上下文的构建成本很低,但是当您不处理它们时,可能会出现不一致的行为,因此我通常发现最好将所需的数据提取到内存中,然后处理上下文。
public class PostRepository : IPostRepository
{
public IEnumerable<Post> GetMostRecentPosts(int blogId)
{
using(var context = new BlogContext())
{
return context.Posts
.Where(p => p.UserId == userId)
.OrderByDescending(p => p.TimeStamp)
.Take(10)
.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允许您使用自己的控制器工厂代替默认工厂,这样您就可以指定像Ninject这样的IoC框架来决定如何创建控制器。您可以设置注入框架,使其知道当您请求一个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));
这使得测试你控制器操作中期望的特定行为变得容易,无需设置数据库或任何特殊的内容。像这样的单元测试易于编写、确定性强(其通过/失败状态基于代码,而非数据库内容),且速度快(通常可以在一秒钟内运行数千个这样的测试)。