简单注入器构造函数参数

Simple Injector constructor parameter

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

问题是我有一个 SqliteStorage-class,它需要数据库的路径。有多个数据库,所以我需要一种方法来在创建时注入 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
  3. 中提供init方法
  4. 使用 public SqliteStorage Create(SqliteStorageOptions options) 方法创建 SqliteStorageFactory

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

谢谢

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

每个数据库连接可以有 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<> 实例并注册工厂方法。

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

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

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

如果 Db1Db2 中需要自动装配,RegisterConditional 提供了一个很好的选择,因为它启用了自动装配:

container.Register<Db1>();
container.Register<Db2>();

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

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

在此代码段中,Db1Db2 都已注册 'normally',而 SqliteStorage 注册是根据消费者有条件地注入的。

这个注册比较复杂,因为 RegisterConditonal 需要提供一个 Registration 实例:没有直接接受 Func<T> 工厂委托的 RegisterConditional 重载.