在我的C#项目中,我最后有一个.ToList();
的查询:
public List<Product> ListAllProducts()
{
List<Product> products = db.Products
.Where(...)
.Select(p => new Product()
{
ProductId = p.ProductId,
...
})
.ToList();
return products;
}
我不时收到EntityException: "The underlying provider failed on Open."
错误。我根据我在SO
public List<Product> ListAllProducts()
{
try
{
// When accessing a lot of Database Rows the following error might occur:
// EntityException: "The underlying provider failed on Open." With an inner TimeoutExpiredException.
// So, we use a new db so it's less likely to occur
using(MyDbContext usingDb = new MyDbContext())
{
// and also set the CommandTimeout to 2 min, instead of the default 30 sec, just in case
((IObjectContextAdapter)usingDb).ObjectContext.CommandTimeout = 120;
List<Product> products = usingDb.Products
.Where(...)
.Select(p => new Product()
{
ProductId = p.ProductId,
...
})
.ToList();
return products;
}
}
// and also have a try-catch in case the error stil occurres,
// since we don't want the entire website to crash
catch (EntityException ex)
{
Console.WriteLine("An error has occured: " + ex.StackTrace);
return new List<Product>();
}
}
到目前为止它似乎工作,我每次都收到我的数据(以防万一我也添加了try-catch)。
现在我是UnitTesting,我正面临着一个问题。我的UnitTest中有一个MockDbContext
,但由于我使用了使用非模拟版本的using(...)
,因此我的UnitTests失败了。我怎么能用MockDbContext而不是默认的测试呢?
编辑:
我刚遇到this post about UnitTesting Mock DbContext。所以在我看来,我确实需要将它一直传递给我的方法,或者只是不使用using(MyDbContext usingDb = new MyDbContext())
而只是暂时增加CommandTimeout并使用try-catch。真的没有别的办法吗?或者,如果没有using(MyDbContext usingDb = new MyDbContext())
所有错误,我是否有更好的修复/解决方法?
编辑2:
让我的情况更加清晰:
我有MyDbContext-class和IMyDbContext-interface。
我的每个控制器都有以下内容:
public class ProductController : Controller
{
private IMyDbContext _db;
public ProductController(IMyDbContext db)
{
this._db = db;
}
... // Methods that use this "_db"
public List<Product> ListAllProducts()
{
try
{
using(MyDbContext usingDb = new MyDbContext())
{
... // Query that uses "usingDb"
}
}
catch(...)
{
...
}
}
}
在我的正常代码中,我创建了这样的实例:
ProductController controller = new ProductController(new MyDbContext());
在我的UnitTest代码中,我创建了这样的实例:
ProductController controller = new ProductController(this.GetTestDb());
// With GetTestDb something like this:
private IMyDbContext GetTestDb()
{
var memoryProducts = new Product { ... };
...
var mockDB = new Mock<FakeMyDbContext>();
mockDb.Setup(m => m.Products).Returns(memoryProducts);
...
return mockDB.Object;
}
一切都很好,在我的正常项目中作为我的UnitTest,除了ListAll方法中的这个using(MyDbContext usingDb = new MyDbContext())
部分。
答案 0 :(得分:3)
虽然希望对代码进行因子分解以便于测试,但是只有在测试中执行的代码分支并不起作用。实际上它的作用是将测试环境配置代码放入您的实时代码中。
您需要的是创建DbContext
的另一个抽象点。您应该传入构造DbContext
:
public List<Product> ListAllProducts(Func<DbContext> dbFunc)
{
using(MyDbContext usingDb = dbFunc())
{
// ...
}
}
现在您不再传递繁重的DbContext
对象,可以将方法参数重新计算到类构造函数中。
public class ProductDAL
{
private Func<DbContext> _dbFunc;
public ProductDAL(Func<DbContext> dbFunc)
{
_dbFunc = dbFunc;
}
public List<Product> ListAllProducts()
{
using(MyDbContext usingDb = _dbFunc())
{
// ...
}
}
}
最重要的是,您现在可以控制使用哪个DbContext
,包括从配置文件中提取的连接字符串,一直在您的IOC容器中,并且永远不会担心它在其他任何地方代码。
答案 1 :(得分:0)
根据我的经验,如果不将代码结构化为单元测试友好,很难得到正确的单元测试,但是你可以尝试使该类具有通用性,以便可以使用任一上下文进行实例化。
public class SomeClass<T> where T: MyDbContext, new()
{
public List<Product> ListAllProducts()
{
using(MyDbContext usingDb = new T())
{
((IObjectContextAdapter)usingDb).ObjectContext.CommandTimeout = 120;
List<Product> products = usingDb.Products.ToList();
return products;
}
}
}
// real-code:
var foo = new SomeClass<MyDbContext>();
// test
var bar = new SomeClass<MockContext>();