我的方法中有很多DB上下文的实例化,例如:
using (myEntities context = new myEnties())
[do something with the context...]
}
我现在知道,我的代码是遗留代码,因为它缺少单元测试。为了使我的代码可测试,我需要以某种方式注入上下文,可能通过类的构造函数。然后我可能会使用Effort.EF6或其他东西来模拟上下文。
但是,首先我必须远离本地构建上下文。但这是我的问题:当我很久以前开始使用EF时,我了解到拥有一个短暂的生活环境是一个非常好的主意(在旧的ADO时代,人们会考虑一旦建立连接就会三思而后行) 。将上下文存储在字段中并且根本不释放它是一个好主意(我在MS代码示例中看到过这种做法)?它会释放自己吗"使用"不是必需的吗?
旁注:我可以选择使用集成测试,只提供原始连接并设置测试数据等。然后我就不需要重构数百个"使用"声明。这当然会减慢测试速度,但它也会测试我的数据库(我使用了很多存储过程)。这是一种可行的方法吗?
答案 0 :(得分:2)
我发现将单元测试引入可以逐步推出的EF相关代码的最简单方法是使用存储库模式。通常,当您看到存储库的示例时,您将看到通用实现,即。每个提供CRUD操作的实体的存储库。相反,我赞成在每个业务领域使用一个存储库,根据每个控制器的存储库思路。 (不是那么具体,而是存储库服务于域的不同区域。)存储库负责:创建实体,检索实体,并且在硬删除的情况下,删除实体。为了使存储库彼此之间以及使用DB上下文的其他代码更好,您还应该考虑使用工作单元模式将代码包装在将与DbContext和存储库交互的控制器或服务层类中。我经常使用EF的一个非常好的是Mehdime DBContextScope。
使用像DBContextScope这样的工作单元,存储库本身保持非常轻量级,其中任何检索数据的方法都返回IQueryable<TEntity>
,包括在预期检索单个实体时。创建方法负责确保代码传递所有必需的数据/引用,而删除方法确保EF在需要时清除孤立的记录。通过返回IQueryable,您可以避免移动大量选择逻辑或决定将哪些数据返回到存储库,因为调用代码可以执行以下操作:.Include(),. Select(),. Any(),. Count()等等,以确保延迟执行时执行的查询非常理想。
这种方法可以与使用DbContexts的代码一起实现,并且Repository实现很容易被模拟。 (即返回存根实体列表等)
答案 1 :(得分:1)
我看到一个长期存在的上下文,即EF会在你使用它们时跟踪很多实体。并且在实际查询之前尝试找到你在内存中查询的内容。当你的内存对象增长时,这当然需要更长的时间。但这取决于您的要求。有时跟踪对象并从内存中重用它们很有用。
您可以尝试使用以下内容禁用此功能:
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
但最终处理上下文是一个好习惯。虽然垃圾收集者在没有任何参考的情况下看到它也会发生这种情况。
答案 2 :(得分:1)
您可以引入ContextFactory
抽象并保留数百个using语句,并仅更改上下文的初始化方式。
public interface IContextFactory
{
DbContext Create();
}
并使用它
using (var context = _contextFactory.Create())
{
// Query staff
}
使用上面的方法,您将能够为测试下的代码提供模拟上下文,或者提供连接到测试数据库的上下文以进行集成测试。
建议使用一些内存数据库提供程序或带有内存模式的SQLLite来测试EF相关逻辑作为黑盒子。使用上下文工厂,您将能够将所需的上下文实现传递给测试下的代码。