【问题标题】:Simple Injector constructor parameter简单的 Injector 构造函数参数
【发布时间】:2020-06-23 21:06:38
【问题描述】:

我在项目中使用 Simple Injector 作为 DI 容器。

问题是我有一个SqliteStorage-class,它需要数据库的路径。有多个 db,所以我需要一种方法来在创建时注入 SqliteStorage-class 的路径。

我的代码如下(简化后没有接口):

public class SqliteStorageOptions
{
    public string Path {get; set;}
}

public class SqliteStorage
{
    private readonly string _path;

    public SqliteStorage(SqliteStorageOptions options)
    {
        _path = options.Path;
    }
}

public class Db1
{
    private readonly SqliteStorage _sqlite;

    public Db1(SqliteStorage sqlite)
    {
        _sqlite = sqlite;
    }
}

public class Db2
{
    private readonly SqliteStorage _sqlite;

    public Db1(SqliteStorage sqlite)
    {
        _sqlite = sqlite;
    }
}


// without di
var db1 = new Db1(new SqliteStorage(new SqliteStorageOptions { Path = "db1.db" });
var db2 = new Db2(new SqliteStorage(new SqliteStorageOptions { Path = "db2.db" });

可能的解决方案:

  1. SqliteStorage 的每个方法中包含SqliteStorageOptions 作为参数。
  2. SqliteStorage 中提供一个init 方法
  3. 使用public SqliteStorage Create(SqliteStorageOptions options) 方法创建SqliteStorageFactory

那么对于我在 simple-injector 中的问题是否有任何内置解决方案,或者有人可以提供另一种(更好的)解决方案吗?

谢谢

编辑 1: 我添加了一些代码。 Db1Db2 都连接到 sqlite-dbs(不同的 dbs,不同的模式),所以我想将所有 sqlite-stuff 提取到它自己的类 SqliteStorage。所以,SqliteStorage 需要知道 db 路径。

【问题讨论】:

  • 您显示的代码有什么问题?不确定我是否理解您要解决的问题。
  • DI框架应该如何知道注入哪条路径?如果这可以根据 DI 框架中可用的其他服务提供的值来确定,那么您应该能够为 SqliteStorageOptions 注册一个工厂方法,并且一切都会“正常工作”。如果不是,则 DI 容器无法知道要注入什么,因此您需要使用您提到的一种可能的解决方案(我建议 #1 或 #3)。
  • 您有多个数据库,但它们都具有相同的架构还是不同的架构?这是一个对回答您的问题很重要的区别。
  • 我添加了一些代码。 @StriplingWarrior Db1, Db2 知道他们的路径。 @Steven 不同的数据库,不同的架构。 SqliteStorage 是对 sqlite-stuff 的抽象。

标签: c# dependency-injection simple-injector


【解决方案1】:

哪种解决方案最好取决于您是否需要 Auto-Wiring(自动构造函数注入)。使用条件注册(使用RegisterConditional)是一个不错的选择,但您知道它仅限于仅基于其直接父项来确定注入。这意味着您不能根据其父父级(Db1Db2)使 SqliteStorageOptions 成为条件。

如果 Db1Db2 类仅依赖于 SqliteStorage 并且不需要任何其他依赖项,则自动装配不是真正的问题,您的注册可以像下面这样简单:

container.Register(
    () => new Db1(new SqliteStorage(new SqliteStorageOptions { Path = "db1.db" }));
container.Register(
    () => new Db2(new SqliteStorage(new SqliteStorageOptions { Path = "db2.db" });

如果Db1Db2 内部需要自动连线,RegisterConditional 是一个很好的选择,因为它启用了自动连线:

container.Register();
容器.Register();

container.RegisterConditional(
    Lifestyle.CreateRegistration(
        () => new SqliteStorage(new SqliteStorageOptions { Path = "db1.db" }),
        容器),
    c => c.Consumer.ImplementationType == typeof(Db1));

container.RegisterConditional(
    Lifestyle.CreateRegistration(
        () => new SqliteStorage(new SqliteStorageOptions { Path = "db2.db" }),
        容器),
    c => c.Consumer.ImplementationType == typeof(Db2));

在这段代码 sn-p 中,Db1Db2 都是“正常”注册的,而 SqliteStorage 注册是根据消费者有条件地注入的。

此注册更复杂,因为需要为RegisterConditonal 提供Registration 实例:没有直接接受Func<T> 工厂委托的RegisterConditional 重载。

【讨论】:

    【解决方案2】:

    每个数据库连接可以有 2 个单例。让我们考虑一个示例,首先我们需要为您的 StorageService 创建一个接口:

    public interface IStorage
    {
        void UsePath();
    }
    

    现在让我们创建这个存储服务的几个实现:

    public class RedisStorage: IStorage
    {
        private readonly string _path;
    
        public RedisStorage(string path)
        {
            _path = path;
        }
    
        public void UsePath()
        {
            Console.WriteLine($"Here's path: {_path}");
        }
    }
    
    public class SqlStorage: IStorage
    {
        private readonly string _path;
    
        public SqlStorage(string path)
        {
            _path = path;
        }
    
        public void UsePath()
        {
            Console.WriteLine($"Here's path: {_path}");
        }
    }
    

    枚举来区分 IStorage 的实现:

    public class StorageSource
    {
        public enum StorageTypes
        {
            Redis=1,
            Sql=2
        }
    }
    

    完成后,让我们为存储源创建一个包装器:

    public interface IStorageWrapper
    {
        void DoStuff();
    }
    

    现在有一个棘手的部分,实例化一个存储包装服务装饰器:

    public class StorageServiceWrapper: IStorageWrapper
    {
        private readonly Func<string, IStorage> _storage;
    
        public StorageServiceWrapper(Func<string, IStorage> storage)
        {
            _storage = storage;
        }
    
        public void UsePath()
        {
            _storage(StorageSource.StorageTypes.Redis.ToString()).DoStuff();
            //uncomment for sql
            //_storage(StorageSource.StorageTypes.Sql.ToString()).DoStuff();
        }
    }
    

    为此,您需要在 Startup.cs 中注册您的类,如下所示:

    services.AddScoped<IStorageWrapper, StorageServiceWrapper>();  
      
    services.AddSingleton<RedisStorage>();  
    services.AddSingleton<SqlStorage>();  
      
    services.AddTransient<Func<string, IStorage>>(serviceProvider => key =>  
    {  
        switch (key)  
        {  
            case "Redis":  
                return serviceProvider.GetService<RedisStorage>();  
            default:  
                return serviceProvider.GetService<SqlStorage>();  
        }  
    }); 
    

    这不会像调用_storage.DoStuff(); 那样漂亮,但我相信会帮助您解决问题。如果您仍然想方便使用,请考虑管理您的设置文件并使用您需要的 conn 字符串注入适当的 IOptions 实例并注册工厂方法。

    【讨论】:

    • 我添加了一些代码。我已经为 dbs 准备了 Db1, Db2-classes。两者都依赖于SqliteStorage,它应该抽象出sqlite-stuff。所以SqliteStorage 需要数据库的路径。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-24
    • 1970-01-01
    • 2013-05-09
    • 1970-01-01
    • 2021-01-20
    相关资源
    最近更新 更多