#asp.net-mvc #ninject #repository-pattern
#asp.net-mvc #ninject #репозиторий-шаблон
Вопрос:
В моем приложении MVC3 у меня есть интерфейс IDataRepository, на который ссылаются все мои контроллеры, чтобы предоставить им доступ к уровню данных. Существует также класс DataRepository, который реализует IDataRepository для конкретного источника данных (в моем случае, Entity Framework, производный от nHydrate). Класс DataRepository принимает единственный аргумент, который является строкой подключения к базовой базе данных.
Я успешно использую nInject для IoC с классами контроллеров, используя следующую привязку:
kernel.Bind<IDataRepository>()
.To<DataRepository>()
.WithConstructorArgument("connectionString", DataRepositoryBase.GetConnectionString());
Сегодня я прочитал о области видимости nInject и подумал, что было бы полезно организовать все так, чтобы для каждого запроса создавался только один экземпляр DatabaseRepository (я думаю, это будет более эффективно, хотя с EF я не уверен).
К сожалению, я не могу понять, как правильно реализовать шаблон. Например, это не работает:
kernel.Bind<DataRepository>()
.ToSelf()
.InRequestScope()
.WithConstructorArgument("connectionString", DataRepositoryBase.GetConnectionString());
kernel.Bind<IDataRepository>()
.To<DataRepository>();
Я думал, что это создало бы только один экземпляр DataRepository, который использовался бы во всех ссылках на IDataRepository. В сообщении об ошибке сообщалось, что не удалось найти совпадение для параметра ConnectionString, а DataRepository не был самосвязываемым. Я попробовал несколько вариантов, но когда я смог заставить его работать, шаблон singleton не выполнялся (т. Е. Я мог видеть в отладчике, что создавалось несколько экземпляров DataRepository).
Я здесь упускаю что-то очевидное :).
— Дополнение — К сожалению, это предложение не предотвращает создание нескольких экземпляров в рамках одного запроса.
Чтобы было понятно, то, что я пробовал, было:
public class BaseControllerModule : NinjectModule
{
public override void Load()
{
Bind<IDataRepository>().To<DataRepository>().InRequestScope()
.WithConstructorArgument("connectionString", DataRepositoryBase.GetConnectionString());
}
}
и то, что я отслеживал, было конструктором:
public DataRepository( string connectionString )
: base(connectionString)
{
}
— Дополнительная информация # 2 —
Вот схема классов, которую Ninject решает для меня:
public class DataRepositoryBase
{
protected DataRepositoryBase( string connectionString )
{}
public static string GetConnectionString() {}
}
public class DataRepository : DataRepositoryBase, IDataRepository
{
public DataRepository( string connectionString )
: base(connectionString)
{}
}
Я опустил детали реализации, но, надеюсь, это рисует лучшую картину.
Просматривая это, я задаюсь вопросом, не создаю ли я проблем, делая ConnectionString параметром конструктора как для DataRepository, так и для его базового класса DataRepositoryBase. Разве Ninject не разрешил бы строку подключения при вызове конструктора базового класса?
p.s. Я запоздало понял, что мне не нужна база данных DataRepositoryBase, потому что ее функциональность может быть объединена в DataRepository. Я сделал это, но у меня все еще есть конструктор для DataRepository, вызываемый несколько раз в том, что кажется одним запросом.
p2.s. Ради интереса я попробовал объявить InSingletonScope() в определении привязки Ninject. Это сработало — конструктор для DataRepository теперь вызывается только один раз, при первом обращении к приложению. Но я не думаю, что это хорошая идея иметь синглтоны в приложении MVC. Похоже, что это приведет к «блокировке» «состояния» приложения в памяти.
— еще больше информации —
Похоже, проблема в том, как я разработал свое приложение MVC. То, что я предполагал, было единственным запросом из браузера обратно на сервер, часто приводит к последовательной обработке нескольких запросов (я наблюдаю запуск события BeginRequest в классе MvcApplication). Кажется, что каждый раз, когда я переключаюсь на другой контроллер, генерируется новый запрос (например, с помощью RedirectToAction). Я думаю, это имеет смысл, но это означает, что InRequestScope Ninject не совсем будет делать то, что я хочу.
Но это также заставляет меня задуматься, не неправильно ли я только что разработал приложение. Похоже, мне следует объединить все методы действий, которые могут быть вызваны при вызове браузера, в один контроллер. Вместо этого я упорядочил методы действий по тому, как они вписываются в концептуальную модель моего приложения.
Ответ №1:
Эти две привязки говорят: Когда запрашивается DataRepository
, повторно используйте экземпляр для всех событий в запросе и установите строку подключения в DataRepositoryBase.GetConnectionString() .
Но когда запрашивается IDataRepository
, создавайте новый экземпляр для каждого события и позвольте Ninject решать, что он вводит в строку подключения.
То, что вы действительно хотите, делается путем добавления InRequestScope
к первому фрагменту кода.
Комментарии:
1. @Отметьте, что в этом случае вы, скорее всего, выполняете разрешение вне запроса. или вы используете несколько экземпляров Ninject, Ninject defenately вернет один и тот же экземпляр, если объект области видимости один и тот же. Область запроса использует HttpContext.Current в качестве области. Проверьте, не равно ли оно null, и убедитесь, что вы используете тот же экземпляр ninject.
2. Я добавил некоторую дополнительную информацию к исходному сообщению, надеясь, что это может прояснить мой вопрос. Но я не уверен, как проверить, что один и тот же экземпляр ninject используется во всех вызовах конструктора. Это статический класс, и я не уверен, где «хранится» экземпляр. HttpContext.Current не равен нулю в вызове конструктора, который я отслеживаю.
3. Я пометил это как ответ, потому что, ну, это так :). Моя основная проблема заключалась в том, что я думал, что каждая публикация или возврат на сервер — это один запрос. На самом деле, каждый из них может генерировать несколько запросов, если, например, вы передаете управление с одного контроллера другому или перенаправляете на https или с него. Оба из которых я делал в своем приложении :).
Ответ №2:
Разве этого не было бы достаточно для наличия singleton?
kernel.Bind<IDataRepository>()
.To<DataRepository>()
.InSingletonScope()
.WithConstructorArgument("connectionString", DataRepositoryBase.GetConnectionString());
RequestScope
не является синглтоном, это означает, что объекты являются отдельными для каждого пользовательского вызова.
Кстати, я думаю, что реальное хранилище не должно быть одноэлементным — оно скорее должно следовать шаблону Единицы работы, что означает, что его время жизни должно представлять одну операцию с данными более высокого уровня, а само соединение должно быть на более низком уровне, чем хранилище.
Комментарии:
1. Вы правы, я был небрежен в своей терминологии. Что я хочу сделать, так это использовать один экземпляр DataRepository на протяжении всей обработки одного веб-запроса. Это то, что, как я думал, сделала InRequestScope(). Но я мог видеть, что несколько экземпляров DataRepository создаются в течение одного цикла обработки запроса. По крайней мере, я предполагаю, что это был один цикл обработки запроса: обработка одного запроса для перехода на другую веб-страницу в приложении MVC.