ASP.NET MVC3和entity framework代码第一架构
我以前的问题让我再次想到层,存储库,dependency injection和像这样的架构。
我的架构现在看起来像这样:
我首先使用EF代码,所以我只是创build了POCO类和上下文。 这创build了数据库和模型。
级别更高的是业务层类(提供者)。 我为每个域使用不同的提供程序…像MemberProvider,RoleProvider,TaskProvider等,我在这些提供程序中的每个提供了我的DbContext的新实例。
然后我在我的控制器中实例化这些提供程序,获取数据并将它们发送到Views。
我最初的架构包括仓库,我摆脱了,因为我被告知这只是增加了复杂性,所以为什么我不只是使用EF。 我想这样做..直接从控制器使用EF,但是我必须编写testing,这与真实的数据库有点复杂。 我不得不假冒 – 莫名其妙的数据。 所以我为每个提供者做了一个接口,并用列表中的硬编码数据做了伪造的提供者。 这样我就回到了一些东西,我不知道如何正确地进行。
这些事情开始太复杂了……许多方法和“pat”“……它创造了太多的噪音和无用的代码。
有entity framework创build和ASP.NET MVC3应用程序的任何简单和可testing的架构?
如果您想要使用TDD(或其他testing覆盖率高的testing方法)和EF,则必须编写集成或端到端testing。 这里的问题是,嘲笑上下文或存储库的任何方法只是创buildtesting,可以testing你的上层逻辑(它使用这些嘲笑),而不是你的应用程序。
简单的例子:
我们来定义通用存储库:
public interface IGenericRepository<TEntity> { IQueryable<TEntity> GetQuery(); ... }
并且让我们写一些业务方法:
public IEnumerable<MyEntity> DoSomethingImportant() { var data = MyEntityRepo.GetQuery().Select((e, i) => e); ... }
现在,如果你嘲笑版本库,你将使用Linq-To-Objects,你将会有一个绿色testing,但是如果你使用Linq-To-Entities运行应用程序,你会得到一个exception,因为在L2E中不支持索引select过载。
这是一个简单的例子,但在查询和其他常见错误中使用方法也会发生同样的情况。 此外,这也影响像添加,更新,删除通常暴露在存储库上的方法。 如果你没有写一个模拟真正模拟EF上下文和参照完整性的行为,你将不会testing你的实现。
故事的另一部分是延迟加载的问题,unit testing对于嘲笑也很难被发现。
因此,您还应该引入集成或端到端testing,这些testing将使用真实的EF上下文L2E与真实数据库一起工作。 顺便说一句。 使用端到端testing需要正确使用TDD。 为了在ASP.NET MVC中编写端到端的testing,您可以使用WatiN ,也可以使用SpecFlow for BDD,但是这确实会增加很多工作,但是您将会真正testing您的应用程序。 如果你想了解更多关于TDD的知识,我推荐这本书 (唯一的缺点就是Java中的例子)。
如果你不使用通用的资源库,并且你的查询隐藏在某些不会暴露IQueryable
但是直接返回数据的类中,那么集成testing是有意义的。
例:
public interface IMyEntityRepository { MyEntity GetById(int id); MyEntity GetByName(string name); }
现在,您可以编写集成testing来testing此存储库的实现,因为查询在此类中隐藏,而不会暴露给上层。 但是这种types的存储库在某种程度上被认为是存储过程的旧实现。 这个实现会损失很多ORM特性,或者你将不得不做很多额外的工作 – 例如添加规范模式以便能够在上层定义查询。
在ASP.NET MVC中,您可以用控制器级别的集成testing部分replace端到端testing。
根据评论编辑:
我不是说你需要unit testing,集成testing和端到端testing。 我说,testing应用程序需要更多的努力。 所需testing的数量和types取决于您的应用程序的复杂程度,应用程序的预期未来,其他团队成员的技能和技能。
小的直接项目可以在没有testing的情况下创build(好的,这不是一个好主意,但我们都做了,最后它工作),但是一旦项目超过某个阈值,就可以发现引入新function或维护项目非常困难,因为你永远不知道它是否会破坏已经有效的东西 – 这就是所谓的回归。 防止回归的最佳防御是一套很好的自动化testing。
- unit testing可以帮助你testing方法。 这样的testing应该覆盖方法中的所有执行path。 这些testing应该非常简短,易于编写 – 复杂的部分可以设置依赖关系(嘲笑,伪造,存根)。
- 集成testing可帮助您testing跨多个层次的function,并且通常跨多个进程(应用程序,数据库)。 你不需要拥有所有的东西,而是select他们有用的地方。
- 端到端testing就像用例/用户故事/function的validation。 他们应该涵盖整个stream程的要求。
不需要多次testing提取 – 如果您知道该function是在端到端testing中testing的,则不需要为相同的代码编写集成testing。 另外如果你知道这个方法只有单一的执行path,那么你就不需要为它编写unit testing。 TDD方法的效果要好得多,你需要从一个大的testing(端到端或集成)开始,然后再深入unit testing。
根据您的开发方法,您不必从头开始进行多种types的testing,但随着应用程序变得越来越复杂,您可以稍后再介绍它们。 TDD / BDD是个例外,你甚至可以在写单行其他代码之前,至less开始使用端到端和unit testing。
所以你问的是错误的问题。 问题不在于简单吗? 问题是最后会有什么帮助,什么复杂性适合您的应用程序? 如果您想要轻松地进行unit testing的应用程序和业务逻辑,则应将EF代码封装到其他可被嘲笑的类中。 但在同一时间,您必须引入其他types的testing,以确保EF代码工作。
我不能说你的环境/项目/团队会采用什么方法,但是我可以从我过去的项目中解释一下例子:
我和两个同事一起工作了大约5-6个月。 该项目基于ASP.NET MVC 2 + jQuery + EFv4,并且是以增量和迭代的方式开发的。 它有很多复杂的业务逻辑和许多复杂的数据库查询。 我们从通用存储库和高代码覆盖率开始,使用unit testing+集成testing来validation映射(插入,删除,更新和select实体的简单testing)。 几个月后,我们发现我们的方法不起作用。 我们有超过1.200个unit testing,代码覆盖率大约是60%(这不是很好),还有很多回归问题。 在EF模型中改变任何东西都会在几个星期内没有触及的部分引入意想不到的问题。 我们发现我们缺less对我们的应用程序逻辑进行集成testing或端到端testing。 同样的结论是在另一个项目上工作的平行团队,使用集成testing被认为是新项目的推荐。
使用存储库模式增加复杂性吗? 在你的情况下,我不这么认为。 它使TDD更容易,代码更易于pipe理。 尝试使用通用存储库模式来获得更多分离和更干净的代码。
如果您想在Entity Framework中了解更多有关TDD和devise模式的内容,请查看: http : //msdn.microsoft.com/zh-cn/ff714955.aspx
但是,似乎你正在寻找一种模拟testingentity framework的方法。 一种解决scheme是使用虚拟种子方法来生成数据库初始化的数据。 请看Seed节: http : //blogs.msdn.com/b/adonet/archive/2010/09/02/ef-feature-ctp4-dbcontext-and-databases.aspx
你也可以使用一些模拟框架。 我知道的最有名的是:
- 犀牛嘲笑
- 起订量
- Typemock (商业)
要查看.NET模拟框架的更完整列表,请查看: https : //stackoverflow.com/questions/37359/what-c-mocking-framework-to-use
另一种方法是使用像SQLite这样的内存数据库提供程序。 更多研究是否有entity framework的内存提供者?
最后,下面是关于unit testingentity framework的一些很好的链接(一些链接指的是entity framework4.0,但你会明白的):
http://mosesofegypt.net/post/Introducing-Entity-Framework-Unit-Testing-with-TypeMock-Isolator.aspx
如何在unit testing中伪造我的数据库层?
我所做的是我使用一个简单的ISession和EFSession对象,女巫很容易在我的控制器模拟,易于访问与Linq和强types。 使用Ninject注入DI。
public interface ISession : IDisposable { void CommitChanges(); void Delete<T>(Expression<Func<T, bool>> expression) where T : class, new(); void Delete<T>(T item) where T : class, new(); void DeleteAll<T>() where T : class, new(); T Single<T>(Expression<Func<T, bool>> expression) where T : class, new(); IQueryable<T> All<T>() where T : class, new(); void Add<T>(T item) where T : class, new(); void Add<T>(IEnumerable<T> items) where T : class, new(); void Update<T>(T item) where T : class, new(); } public class EFSession : ISession { DbContext _context; public EFSession(DbContext context) { _context = context; } public void CommitChanges() { _context.SaveChanges(); } public void Delete<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T : class, new() { var query = All<T>().Where(expression); foreach (var item in query) { Delete(item); } } public void Delete<T>(T item) where T : class, new() { _context.Set<T>().Remove(item); } public void DeleteAll<T>() where T : class, new() { var query = All<T>(); foreach (var item in query) { Delete(item); } } public void Dispose() { _context.Dispose(); } public T Single<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T : class, new() { return All<T>().FirstOrDefault(expression); } public IQueryable<T> All<T>() where T : class, new() { return _context.Set<T>().AsQueryable<T>(); } public void Add<T>(T item) where T : class, new() { _context.Set<T>().Add(item); } public void Add<T>(IEnumerable<T> items) where T : class, new() { foreach (var item in items) { Add(item); } } /// <summary> /// Do not use this since we use EF4, just call CommitChanges() it does not do anything /// </summary> /// <typeparam name="T"></typeparam> /// <param name="item"></param> public void Update<T>(T item) where T : class, new() { //nothing needed here }
如果我想从EF4切换到MongoDB,我只需要制作一个MongoSession来实现ISession …
我有同样的问题,决定我的MVC应用程序的一般devise。 Shiju Varghese的这个 CodePlex项目很有帮助。 它是在ASP.net MVC3,EF CodeFirst中完成的,同样也使用了一个服务层和一个存储库层。 dependency injection是使用Unity完成的。 这很简单,很容易遵循。 它也支持一些4非常好的博客文章。 它值得检查。 而且,不要放弃存储库..是的。