【问题标题】:How to configure the Ninject bindings for this scenario?如何为这种情况配置 Ninject 绑定?
【发布时间】:2018-06-19 17:56:00
【问题描述】:

我有一个关于 Ninject 的问题,但在直接回答问题之前,我先解释一下一般情况。

我有一个名为 ITest__Business 的业务接口及其实现 Test__Business。该类依赖于 3 个接口:ITest__Repository、ITest2__Repository 和 IConnectionUtil。 ITest__Repository、ITest2__Repository 和 IConnectionUtil 接口具有使用默认名称(分别为 Test__Repository、Test2__Repository 和 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 不同(它与不同的 DB 有自己的连接),它必须与新的存储库(repo3)共享。这样可以实现与2个不同数据库的交互。

为了实现这一点,我创建了一个属性类 ConnectionAttribute,它有一个构造函数,用于设置将从 .config 文件中读取的连接字符串。这个属性必须用新的连接字符串“Test2Connection”添加到connUtil2和repo3,表明它们是相关的。一个repo1、repo2和connUtil1不加这个属性,这样在解决依赖关系的时候,如果这些target没有这个属性,就会使用原来的连接字符串“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 时,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 个新的存储库接口来调用第三个新数据库的存储过程), 这样,业务构造函数将如下所示:

/**** 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 对于每个业务对象都是唯一的,因此对 Kernel.Get 的 2 次调用必须产生不同的业务对象, 存储库和 ConnectionUtils。

请帮帮我。

【问题讨论】:

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


    【解决方案1】:

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

    那么如何扩展您之前的 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) 的实现留给您作为练习 ;-)(请随时更新答案以使其完整!)

    【讨论】:

    • 非常感谢您的帮助,非常优雅的回复。我实现了这两种方法,它适用于我的情况。另一方面,虽然我的问题的答案你已经给了我,但我很想验证如果我添加一个新接口 ITestDb3__Repository 来连接第三个数据库是否有效。我针对这个新场景更新了我的问题,您能帮帮我吗?
    • 我明白,对于这个新场景,我必须创建一个自定义范围,基于调用范围但也为连接字符串的名称。这是正确的?但我不知道该怎么做
    • @Marlonchosky 有可能,是的。查看wiki,您的解决方案必须有所不同的相关部分是Contrasting it to InNamedScope, one would say its a limited version of InNamedScope() that automatically adds an anonymous Named Scope to the Root object generated by the Get&lt;T&gt;()。 IE。您需要为标准连接字符串添加一个命名范围,并为每个配置的连接管理器添加一个。这还需要为每个配置的连接字符串绑定IConnectionUtil InNamedScope(..)
    • @Marlonchosky 但是,可能有更好的替代解决方案。除非您提供 DI 容器应该做什么的具体示例,否则很难对这些进行推理,基本上是手动编写 DI 应该做什么的展示(所有必要的new 语句)。这样可以更轻松地推断出最佳解决方案。
    • 非常感谢。我在最后更新了我的问题,在 UPDATE #2 下方
    【解决方案2】:

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

    查看InCallScope source code,我创建了一个新的扩展方法“InCallAndConnectionStringScope”。

    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);
    }
    

    【讨论】:

      猜你喜欢
      • 2018-07-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多