如何为此方案配置Ninject绑定?

时间:2018-01-10 14:23:42

标签: c# .net visual-studio ninject ninject-extensions

我对Ninject有疑问,但在直接回答问题之前,我将解释一般情况。

我有一个名为ITest__Business的业务接口及其实现Test__Business。该类依赖于3个接口:ITest__Repository,ITest2__Repository和IConnectionUtil。接口ITest__Repository,ITest2__Repository和IConnectionUtil具有使用默认名称(分别为Test__Repository,Test2__Repository和ConnectionUtil)实现它们的类。

这些域类表示具有存储库类依赖关系的业务实体和用于处理数据库连接的打开和关闭的连接实用程序。业务类依赖于repositorys和connectionutil。业务中创建的connectionutil由2个存储库共享(因此业务和存储库都处理与DB的单个连接)。

这是前面提到的代码:

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)共享。这实现了与2个不同数据库的交互。

为了实现这一点,我创建了一个属性类ConnectionAttribute,它具有一个构造函数,用于设置将从.config文件中读取的连接字符串。必须使用新连接字符串&#34; Test2Connection&#34;将此属性添加到connUtil2和repo3,表明这些属性是相关的。 repo1,repo2和connUtil1不添加此属性,因此在解决依赖关系时,如果这些目标没有此属性,则原始连接字符串&#34; Test1Connection&#34;将被使用。

[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&lt; ITest__Business&gt;时,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,因为范围只有在存在属性但不存在其值的情况下。 对于这个新场景,ninject配置将如何?

============================================ =======================

更新#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个新的存储库接口,调用该第3个新数据库的存储过程), 有了这个,业务构造函数将如下所示:

/**** 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必须生成不同的业务对象, repositorys和ConnectionUtils。

请帮帮我。

2 个答案:

答案 0 :(得分:0)

问题很明显:您使用范围,但每个连接字符串只存在一次。但是,每个连接字符串需要每个业务实例的范围。

那么如何延长之前似乎工作正常的InScallScope()绑定呢?

保留用于指定连接的自定义属性,可以为IConnectionUtil创建多个绑定:

Kernel.Bind<IConnectionUtil>().To<ConnectionUtil>()
    .InCallScope()
    .WithConstructorArgument("connStringKey", "Test1Connection");

Kernel.Bind<IConnectionUtil>().To<ConnectionUtil>()
    .WhenAnyAncestorMatches(HasConnectionStringAttribute)
    .InCallScope()
    .WithConstructorArgument("connStringKey", RetrieveConnectinStringFromAttribute);

我将bool HasConnectionStringAttribute(IContext context)string RetrieveConnectinStringFromAttribute(IContext context)的实现作为练习留给您;-)(随时更新答案以使其完整!)

答案 1 :(得分:0)

在思考和调查响应很长一段时间之后,感谢@BatteryBackupUnit的支持,我找到了一种基于InCallScope创建自定义范围的方法。

回顾InCallScope source code,我创建了一个新的扩展方法&#34; InCallAndConnectionStringScope&#34;。

public static class NinjectExtensions {
    public static IBindingNamedWithOrOnSyntax<T> InCallAndConnectionStringScope<T>(this IBindingInSyntax<T> syntax, Func<IContext, string> getConnString) {
        return syntax.InScope(context => {
            var connString = getConnString(context);
            var ScopeParameterName = $"NamedScopeInCallScope_{connString}";
            var rootContext = context;
            while (!IsCurrentResolveRoot(rootContext) && rootContext.Request.ParentContext != null) {
                rootContext = rootContext.Request.ParentContext;
            }

            return GetOrAddScope(rootContext, ScopeParameterName);
        });
    }

    private static bool IsCurrentResolveRoot(IContext context) {
        return context.Request.GetType().FullName == "Ninject.Extensions.ContextPreservation.ContextPreservingResolutionRoot+ContextPreservingRequest";
    }

    private static object GetOrAddScope(IContext parentContext, string scopeParameterName) {
        var namedScopeParameter = GetNamedScopeParameter(parentContext, scopeParameterName);
        if (namedScopeParameter == null) {
            namedScopeParameter = new NamedScopeParameter(scopeParameterName);
            parentContext.Parameters.Add(namedScopeParameter);
        }

        return namedScopeParameter.Scope;
    }

    private static NamedScopeParameter GetNamedScopeParameter(IContext context, string scopeParameterName) {
        return context.Parameters.OfType<NamedScopeParameter>().SingleOrDefault(parameter => parameter.Name == scopeParameterName);
    } 
}

绑定配置:

public class ConsoleCustomModule : NinjectModule {
    public override void Load() {
        Bind<IConnectionUtil>().To<ConnectionUtil>().InCallAndConnectionStringScope(GetConnectionString)
            .WithConstructorArgument(GetConnectionString);

        Bind<ITestDb2__Repository>().To<TestDb2__Repository>();
        Bind<ITest__Repository>().To<Test__Repository>();
        Bind<ITest__Business>().To<Test__Business>();
    }

    private string GetConnectionString(IContext context) {
        var cadena = "Test1Connection";
        if (context.Request.Target.Member.DeclaringType.Name.Contains("Repository")) {
            var pr = context.Request.ParentRequest.Target.GetCustomAttributes(typeof(ConnectionAttribute), false);
            cadena = pr.Length == 0 ? "Test1Connection" : ((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 ? "Test1Connection" : ((ConnectionAttribute)attrs[0]).ConnectionString;
        }

        return cadena;
    }
}

具有以下构造函数的签名:

public Test__Business(IConnectionUtil connUtil1, ITest__Repository repo1, ITest__Repository repo1_2,
    [Connection("Test2Connection")]IConnectionUtil connUtil2, [Connection("Test2Connection")] ITest__Repository repo2, [Connection("Test2Connection")] ITestDb2__Repository repo2_2, 
    [Connection("Test3Connection")]IConnectionUtil connUtil3, [Connection("Test3Connection")]ITestDb2__Repository repo3) {
    this.connUtil1 = connUtil1;
    this.connUtil2 = connUtil2;
    this.connUtil3 = connUtil3;
    this.repo1 = repo1;
    this.repo1_2 = repo1_2;
    this.repo2 = repo2;
    this.repo2_2 = repo2_2;
    this.repo3 = repo3;
}

您可以根据以下测试所需的范围验证对象:

[TestMethod]
public void Varias_Pruebas() {
    NinjectKernel.Init();
    var business1 = NinjectKernel.Kernel.Get<ITest__Business>();
    var business2 = NinjectKernel.Kernel.Get<ITest__Business>();

    // Different business objects.
    Assert.AreNotEqual(business1, business2);
    Assert.AreNotSame(business1, business2);

    // Different ConnectionUtil objects.
    Assert.AreNotEqual(business1.ConnectionUtil, business1.ConnectionUtil2);
    Assert.AreNotSame(business1.ConnectionUtil, business1.ConnectionUtil2);
    Assert.AreNotEqual(business1.ConnectionUtil, business1.ConnectionUtil2);
    Assert.AreNotSame(business1.ConnectionUtil, business1.ConnectionUtil2);
    Assert.AreNotEqual(business1.ConnectionUtil2, business1.ConnectionUtil3);
    Assert.AreNotSame(business1.ConnectionUtil2, business1.ConnectionUtil3);

    // Different repositories objects.
    Assert.AreNotEqual(business1.repo1, business1.repo1_2);
    Assert.AreNotSame(business1.repo1, business1.repo1_2);
    Assert.AreNotEqual(business1.repo1, business2.repo1);
    Assert.AreNotSame(business1.repo1, business2.repo1);
    Assert.AreNotEqual(business1.repo2, business1.repo2_2);
    Assert.AreNotSame(business1.repo2, business1.repo2_2);
    Assert.AreNotEqual(business1.repo2, business2.repo2);
    Assert.AreNotSame(business1.repo2, business2.repo2);

    // ConnectionUtils are shared between parameters with the same connString value of the connection attribute.
    Assert.AreEqual(business1.ConnectionUtil, business1.repo1.ConnectionUtil);
    Assert.AreSame(business1.ConnectionUtil, business1.repo1.ConnectionUtil);
    Assert.AreEqual(business1.ConnectionUtil, business1.repo1_2.ConnectionUtil);
    Assert.AreSame(business1.ConnectionUtil, business1.repo1_2.ConnectionUtil);
    Assert.AreEqual(business1.ConnectionUtil2, business1.repo2.ConnectionUtil);
    Assert.AreSame(business1.ConnectionUtil2, business1.repo2.ConnectionUtil);
    Assert.AreEqual(business1.ConnectionUtil2, business1.repo2_2.ConnectionUtil);
    Assert.AreSame(business1.ConnectionUtil2, business1.repo2_2.ConnectionUtil);
     Assert.AreEqual(business1.ConnectionUtil3, business1.repo3.ConnectionUtil);
    Assert.AreSame(business1.ConnectionUtil3, business1.repo3.ConnectionUtil);

    // No ConnectionUtils are shared between parameters with different connString value from the connection attribute.
    Assert.AreNotEqual(business1.ConnectionUtil, business1.repo2.ConnectionUtil);
    Assert.AreNotSame(business1.ConnectionUtil, business1.repo2.ConnectionUtil);
    Assert.AreNotEqual(business1.ConnectionUtil, business1.repo2_2.ConnectionUtil);
    Assert.AreNotSame(business1.ConnectionUtil, business1.repo2_2.ConnectionUtil);
    Assert.AreNotEqual(business1.ConnectionUtil2, business1.repo1.ConnectionUtil);
    Assert.AreNotSame(business1.ConnectionUtil2, business1.repo1.ConnectionUtil);
    Assert.AreNotEqual(business1.ConnectionUtil2, business1.repo1_2.ConnectionUtil);
    Assert.AreNotSame(business1.ConnectionUtil2, business1.repo1_2.ConnectionUtil);
    Assert.AreNotEqual(business1.ConnectionUtil2, business1.repo3.ConnectionUtil);
    Assert.AreNotSame(business1.ConnectionUtil2, business1.repo3.ConnectionUtil);
    Assert.AreNotEqual(business1.ConnectionUtil3, business1.repo1.ConnectionUtil);
    Assert.AreNotSame(business1.ConnectionUtil3, business1.repo1.ConnectionUtil);
    Assert.AreNotEqual(business1.ConnectionUtil3, business1.repo1_2.ConnectionUtil);
    Assert.AreNotSame(business1.ConnectionUtil3, business1.repo1_2.ConnectionUtil);
    Assert.AreNotEqual(business1.ConnectionUtil3, business1.repo2.ConnectionUtil);
    Assert.AreNotSame(business1.ConnectionUtil3, business1.repo2.ConnectionUtil);
    Assert.AreNotEqual(business1.ConnectionUtil3, business1.repo2_2.ConnectionUtil);
    Assert.AreNotSame(business1.ConnectionUtil3, business1.repo2_2.ConnectionUtil);
}