В моем проекте 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, и у меня возникла проблема. У меня есть MockDbContext
в моем модуле UnitTest, но поскольку я использую using(...)
, в котором используется немодельная версия, мои модульные тесты не работают. Как я могу проверить это, используя MockDbContext вместо контекста по умолчанию?
РЕДАКТИРОВАТЬ:
Я только что наткнулся на этот пост о модуле UnitTesting Mock DbContext . Так что мне кажется, что мне действительно нужно полностью передать это моему методу или просто не использовать using(MyDbContext usingDb = new MyDbContext())
, а вместо этого только временно увеличить CommandTimeout и использовать try-catch. Неужели другого выхода нет? Или есть лучший способ исправить / обойти ошибку, которую я получаю, без using(MyDbContext usingDb = new MyDbContext())
целиком?
РЕДАКТИРОВАТЬ 2:
Чтобы прояснить мою ситуацию:
У меня есть класс MyDbContext и интерфейс IMyDbContext.
В каждом из моих контроллеров есть следующее:
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, за исключением этой части using(MyDbContext usingDb = new MyDbContext())
в ListAll-методах.
2 ответа
Хотя для облегчения тестирования желательно разложить код на множители, наличие ветвей кода, которые выполняются только в рамках теста, не помогает. На самом деле это помещает код конфигурации тестовой среды в ваш живой код.
Что вам нужно, так это еще одна абстракция для создания 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 и никогда не беспокоиться об этом где-либо еще в коде.
По моему опыту, трудно получить надлежащее модульное тестирование без структурирования кода, чтобы он был удобен для модульного тестирования, но вы можете попробовать сделать класс универсальным, чтобы его можно было создать в любом контексте.
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>();
Похожие вопросы
Новые вопросы
c#
C# (произносится как «see Sharp») — это высокоуровневый мультипарадигменный язык программирования со статической типизацией, разработанный Microsoft. Код C# обычно нацелен на семейство инструментов и сред выполнения Microsoft .NET, которое включает в себя .NET, .NET Framework, .NET MAUI и Xamarin среди прочих. Используйте этот тег для ответов на вопросы о коде, написанном на C#, или о формальной спецификации C#.
MyDbContext
-класс иIMyDbContext
-интерфейс, и в моем ProductController я использую:private IMyDbContext db; public ProductController(IMyDbContext _db){ this.db = _db; }
Должен ли я вместо этого изменить это на делегат? Похоже, он может решить мою проблему, но я хочу убедиться, прежде чем менять его ВЕЗДЕ в моем коде.DbContext
были недолговечными.