У меня есть вопрос о Ninject, но прежде чем перейти непосредственно к вопросу, я объясню общий сценарий.
У меня есть бизнес-интерфейс под названием ITest__Business и его реализация Test__Business. Этот класс зависит от 3 интерфейсов: ITest__Repository, ITest2__Repository и IConnectionUtil. Интерфейсы ITest__Repository, ITest2__Repository и IConnectionUtil имеют классы, реализующие их с именем по умолчанию (Test__Repository, Test2__Repository и ConnectionUtil соответственно).
Эти доменные классы представляют бизнес-сущности с зависимостями классов репозитория и утилитой подключения для обработки открытия и закрытия подключения к базе данных. Бизнес-класс зависит как от репозиториев, так и от утилиты подключения. Connectionutil, созданный в бизнесе, совместно используется двумя репозиториями (так что и бизнес, и репозитории обрабатывают одно соединение с БД).
Это код вышеупомянутого:
public interface ITest__Business {
TablaUno ManageTablaUno(TablaUno tablaUno);
IConnectionUtil ConnectionUtil {get; }
IConnectionUtil ConnectionUtil2 {get; }
}
public class Test__Business : ITest__Business {
private IConnectionUtil connUtil1;
private ITest__Repository repo1;
private ITest2__Repository repo2;
public Test__Business(IConnectionUtil connUtil1, ITest__Repository repo1, ITest2__Repository repo2) {
this.connUtil1 = connUtil1;
this.repo1 = repo1;
this.repo2 = repo2;
}
public TablaUno ManageTablaUno(TablaUno tablaUno){
using (var scope = new TransactionScope()) {
// Methods of repo1 and repo2 within transaction.
//...
}
}
public interface ITest__Repository {
IConnectionUtil ConnectionUtil { get; }
TablaUno ManageTablaUno(TablaUno tablaUno);
}
public class Test__Repository : BaseRepository, ITest__Repository {
public Test__Repository(IConnectionUtil connectionUtil) : base(connectionUtil) {
// The connectionUtil is passed to the base class to retrieve the DbConnection
}
public TablaUno ManageTablaUno(TablaUno tablaUno) {
// Invocation to stored procedure with the connection of the base class.
}
}
public interface ITest2__Repository {/*Metodos propios*/}
public class Test2__Repository : BaseRepository, ITest2__Repository {
// Logic similar to Test_Repository.
}
public interface IConnectionUtil {
DbConnection Connection { get; }
void Open();
void Close();
}
public class ConnectionUtil : IConnectionUtil, IDisposable {
private SqlConnection connection;
public ConnectionUtil(string connStringKey) {
var connString = WebConfigurationManager.ConnectionStrings[connStringKey].ConnectionString;
connection = new SqlConnection(connString);
}
public DbConnection Connection => connection;
public void Open() {
try {
connection.Open();
} catch (Exception ex) {
Debug.WriteLine($"Excepcion en ConnectionUtil.Open: {ex.Message}");
throw;
}
}
public void Close() {
try {
if (connection != null && connection.State == ConnectionState.Open) {
connection.Close();
}
} catch (Exception ex) {
Debug.WriteLine($"Excepcion en ConnectionUtil.Close: {ex.Message}");
throw;
}
}
/*Disposable Logic*/
}
И моя конфигурация модуля Ninject:
public class ConsoleModule : NinjectModule {
public override void Load() {
Kernel.Bind<IConnectionUtil>().To<ConnectionUtil>().InCallScope().WithConstructorArgument("connStringKey", "Test1Connection");
Kernel.Bind<ITest__Repository>().To<Test__Repository>().InTransientScope();
Kernel.Bind<ITest2__Repository>().To<Test2__Repository>().InTransientScope();
Kernel.Bind<ITestDb2__Repository>().To<TestDb2__Repository>().InTransientScope();
Kernel.Bind<ITest__Business>().To<Test__Business>().InTransientScope();
}
}
Впоследствии был запрошен новый репозиторий (repo3) и добавлен новый connectionutil (connUtil2). ConnUtil2, созданный в бизнесе, отличается от connUtil1 (у него есть собственное подключение к другой БД), и он должен использоваться совместно с новым репозиторием (repo3). Это позволяет взаимодействовать с двумя разными базами данных.
Для этого я создал класс атрибутов ConnectionAttribute с конструктором, задающим строку подключения, которая будет считываться из файла .config. Этот атрибут необходимо добавить в connUtil2 и repo3 с новой строкой подключения «Test2Connection», указывающей, что они связаны. Repo1, repo2 и connUtil1 не добавляют этот атрибут, поэтому при решении зависимостей, если эти цели не имеют этого атрибута, будет использоваться исходная строка подключения «Test1Connection».
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)]
public sealed class ConnectionAttribute : Attribute {
public string ConnectionString { get; private set; }
public ConnectionAttribute(string connectionString) {
ConnectionString = connectionString;
}
}
public class Test__Business : ITest__Business {
// Properties and methods are ignored for brevity.
public Test__Business(IConnectionUtil connUtil1, [Connection("Test2Connection")] IConnectionUtil connUtil2,
ITest__Repository repo1, ITest2__Repository repo2, [Connection("Test2Connection")] ITestDb2__Repository repo3) {
this.connUtil1 = connUtil1;
this.connUtil2 = connUtil2;
this.repo1 = repo1;
this.repo2 = repo2;
this.repo3 = repo3;
}
}
Я также обновил модуль ninject:
public class ConsoleModule : NinjectModule {
private IList<string> scopeList = new List<string>();
public ConsoleModule() {
foreach (ConnectionStringSettings connstr in WebConfigurationManager.ConnectionStrings) {
scopeList.Add(connstr.Name);
}
}
//public override void Load() {
// Kernel.Bind<IConnectionUtil>().To<ConnectionUtil>().InCallScope().WithConstructorArgument("connStringKey", "Test1");
// Kernel.Bind<ITest__Repository>().To<Test__Repository>().InTransientScope();
// Kernel.Bind<ITest2__Repository>().To<Test2__Repository>().InTransientScope();
// Kernel.Bind<ITestDb2__Repository>().To<TestDb2__Repository>().InTransientScope();
// Kernel.Bind<ITest__Business>().To<Test__Business>().InTransientScope();
//}
public override void Load() {
Kernel.Bind<IConnectionUtil>().To<ConnectionUtil>().InScope(context => {
var scopeCadena = string.Empty;
if (context.Request.Target.Member.DeclaringType.Name.Contains("Repository")) {
var pr = context.Request.ParentRequest.Target.GetCustomAttributes(typeof(ConnectionAttribute), false);
var cadena = pr.Length == 0 ? "Test1" : ((ConnectionAttribute)pr[0]).ConnectionString;
scopeCadena = scopeList.Single(x => x == cadena);
}
if (context.Request.Target.Member.DeclaringType.Name.Contains("Business")) {
var attrs = context.Request.Target.GetCustomAttributes(typeof(ConnectionAttribute), false);
var cadena = attrs.Length == 0 ? "Test1" : ((ConnectionAttribute)attrs[0]).ConnectionString;
scopeCadena = scopeList.Single(x => x == cadena);
}
return scopeCadena;
}).WithConstructorArgument("connStringKey", context => {
var cadena = "Test1";
if (context.Request.Target.Member.DeclaringType.Name.Contains("Repository")) {
var pr = context.Request.ParentRequest.Target.GetCustomAttributes(typeof(ConnectionAttribute), false);
cadena = pr.Length == 0 ? "Test1" : ((ConnectionAttribute)pr[0]).ConnectionString;
}
if (context.Request.Target.Member.DeclaringType.Name.Contains("Business")) {
var attrs = context.Request.Target.GetCustomAttributes(typeof(ConnectionAttribute), false);
cadena = attrs.Length == 0 ? "Test1" : ((ConnectionAttribute)attrs[0]).ConnectionString;
}
return cadena;
});
Kernel.Bind<ITest__Repository>().To<Test__Repository>().InTransientScope();
Kernel.Bind<ITestDb2__Repository>().To<TestDb2__Repository>();
Kernel.Bind<ITest__Business>().To<Test__Business>();
}
Кажется, это работает нормально, но проблема в том, что каждый раз, когда я вызываю Kernel.Get ‹ITest__Business›, connUtil1 и connUtil2 распределяются между бизнесом, и мне нужно, чтобы они были связаны с областью бизнеса, создавая новые connUtil1 и connUtil2 с каждым бизнес-экземпляром.
Как мне настроить ninject для этого нового случая? Пожалуйста помогите.
============================================== ====================
ОБНОВЛЕНИЕ
С другой стороны, хотя ответ на мой вопрос дан, мне было любопытно проверить, работает ли он, если добавлен новый интерфейс ITestDb3__Repository для подключения к третьей базе данных. Обновление моего конструктора бизнес-класса выглядит так:
public Test__Business(IConnectionUtil connUtil1,
ITest__Repository repo1, ITest2__Repository repo2,
[Connection("Test2Connection")] IConnectionUtil connUtil2, [Connection("Test2Connection")] ITestDb2__Repository repo3,
[Connection("Test3Connection")] IConnectionUtil connUtil3, [Connection("Test3Connection")] ITestDb2__Repository repo4) {
this.connUtil1 = connUtil1;
this.connUtil2 = connUtil2;
this.connUtil3 = connUtil3;
this.repo1 = repo1;
this.repo2 = repo2;
this.repo3 = repo3;
this.repo3 = repo4;
}
Что должно произойти, так это то, что должно быть 3 разных connectionUtils, а repo4 использует один и тот же connUtil3. Но для этого нового сценария connUtil3 равен connUtil2, так как это область действия только в том случае, если он имеет атрибут, но не его значение. Какой будет конфигурация нинжекта для этого нового сценария?
============================================== ====================
ОБНОВЛЕНИЕ №2
Что мне нужно, так это иметь способ связать репозитории с connectionutils в бизнес-классе.
Первый случай — это когда бизнесу необходимо подключиться к 2 базам данных и зависеть от 3 репозиториев, чтобы они внутренне выполняли вызов хранимых процедур. Репозитории не обрабатывают подключение, но это делается через интерфейс IConnectionUtil, который получает строку подключения. В этом случае код будет следующим:
/**** Test__Business constructor signature ****/
public Test__Business(IConnectionUtil connUtil1, ITest__Repository repo1_1, ITest2__Repository repo1_2,
IConnectionUtil connUtil2, ITestDb2__Repository repo2_1) { /* ... */ }
/**** Test__Business creation statements (equivalent to Kernel.Get<ITest__Business>) ****/
// repository1_1 and repository1_2 share the same connUtil1.
IConnectionUtil connUtil1 = new ConnectionUtil("TestConnection1"); // Connection string of the 1st database.
ITest__Repository repository1_1 = new Test__Repository(connUtil1);
ITest2__Repository repository1_2 = new Test2__Repository(connUtil1);
// repository2_1 shares the same connUtil2.
IConnectionUtil connUtil2 = new ConnectionUtil("TestConnection2"); // Connection string of the 2nd database.
ITestDb2__Repository repository2_1 = new TestDb2__Repository(connUtil2);
ITest__Business business = new Test__Business(connUtil1, repository1_1, repository1_2, connUtil2, repository2_1);
Второй случай: если попросить изменить бизнес так, чтобы он подключался к третьей базе данных, теперь в зависимости от нового IConnectionUtil и новых репозиториев, которые используют этот connectionUtil (предположим, что были разработаны 2 новых интерфейса репозитория, которые вызывали хранимые процедуры этого третьего). новая база данных), при этом бизнес-конструктор будет выглядеть так:
/**** Test__Business constructor signature ****/
public Test__Business(IConnectionUtil connUtil1, ITest__Repository repo1_1, ITest2__Repository repo1_2,
IConnectionUtil connUtil2, ITestDb2__Repository repo2_1,
IConnectionUtil connUtil3, ITestDb3_1__Repository repository3_1, ITestDb3_2__Repository repository3_2) { /* ... */}
/**** Test__Business creation statements (equivalent to Kernel.Get<ITest__Business>) ****/
// repository1_1 and repository1_2 share the same connUtil1.
IConnectionUtil connUtil1 = new ConnectionUtil("TestConnection1"); // Connection string of the 1st database.
ITest__Repository repository1_1 = new Test__Repository(connUtil1);
ITest2__Repository repository1_2 = new Test2__Repository(connUtil1);
// repository2_1 shares the same connUtil2.
IConnectionUtil connUtil2 = new ConnectionUtil("TestConnection2"); // Connection string of the 2nd database.
ITestDb2__Repository repository2_1 = new TestDb2__Repository(connUtil2);
// repository3_1 and repository3_2 share the same connUtil3.
IConnectionUtil connUtil3 = new ConnectionUtil("TestConnection3"); // Connection string of the 3rd database.
ITestDb3_1__Repository repository3_1 = new TestDb3_1__Repository(connUtil3);
ITestDb3_2__Repository repository3_2 = new TestDb3_1__Repository(connUtil3);
ITest__Business business = new Test__Business(connUtil1, repository1_1, repository1_2, connUtil2, repository2_1, connUtil3, repository3_1, repository3_2);
И так далее, если мне нужно добавить новые подключения к другим базам данных. Мне нужен способ связать в бизнес-конструкторе, какие репозитории используют один и тот же connectionutil.
ПРИМЕЧАНИЕ. Каждый репозиторий и ConnectionUtil уникальны для каждого бизнес-объекта, поэтому 2 вызова Kernel.Get должны создавать разные бизнес-объекты, репозитории и ConnectionUtils.
Пожалуйста помогите.