简体   繁体   English

简单的 Injector 构造函数参数

[英]Simple Injector constructor parameter

I'm using Simple Injector as DI Container in a project.我在项目中使用 Simple Injector 作为 DI 容器。

The problem is that I have a SqliteStorage -class, which needs the path to the db.问题是我有一个SqliteStorage -class,它需要数据库的路径。 There are multiple dbs, so I need a way to inject the path to the SqliteStorage -class at creation.有多个数据库,所以我需要一种方法来在创建时注入SqliteStorage -class 的路径。

My code looks as follows (simplified without interfaces):我的代码如下所示(简化后没有接口):

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

Possible Solutions:可能的解决方案:

  1. Include SqliteStorageOptions as parameter at every method in SqliteStorage .SqliteStorage的每个方法中包含SqliteStorageOptions作为参数。
  2. Provide a init -method in SqliteStorageSqliteStorage中提供一个init方法
  3. Create a SqliteStorageFactory with a public SqliteStorage Create(SqliteStorageOptions options) -method.使用public SqliteStorage Create(SqliteStorageOptions options)方法创建一个SqliteStorageFactory

So are there any built-in solution to my problem in simple-injector or can someone provide another (better) solution?那么在简单注射器中是否有任何内置解决方案可以解决我的问题,或者有人可以提供另一种(更好的)解决方案吗?

Thanks谢谢

Edit 1: I added some code.编辑1:我添加了一些代码。 Db1 and Db2 both connect to sqlite-dbs (different dbs, different schema), so I wanted to extract all the sqlite-stuff to its own class SqliteStorage . Db1Db2都连接到 sqlite-dbs(不同的数据库,不同的模式),所以我想将所有 sqlite-stuff 提取到它自己的 class SqliteStorage So, the SqliteStorage needs to know the db path.因此, SqliteStorage需要知道数据库路径。

Which solution is best depends a bit on whether you require Auto-Wiring (automatic constructor injection) or not.哪种解决方案最好取决于您是否需要自动接线(自动构造函数注入)。 Using conditional registrations (using RegisterConditional ) is a good pick, but you have be aware that it is limited to determining the injection based on only its direct parent.使用条件注册(使用RegisterConditional )是一个不错的选择,但您知道它仅限于仅根据其直接父项来确定注入。 This means that you can't make SqliteStorageOptions conditional based on its parent parent (either Db1 or Db2 ).这意味着您不能使SqliteStorageOptions基于其父父级( Db1Db2 )有条件。

If the Db1 and Db2 classes solely depend on a SqliteStorage and don't require any other dependencies, Auto-Wiring is not a real issue and your registrations can be as simple as the following:如果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" });

In case Auto-Wiring is required inside Db1 and Db2 , RegisterConditional gives a good alternative, because it enables Auto-Wiring:如果在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)); 

In this code snippet, both Db1 and Db2 are registered 'normally', while the SqliteStorage registrations are conditionally injected based on thei consumer.在此代码片段中, Db1Db2都“正常”注册,而SqliteStorage注册是根据消费者有条件地注入的。

This registration is more complex, because RegisterConditonal need to be supplied with a Registration instance: there is no RegisterConditional overload that directly accepts a Func<T> factory delegate.这个注册更加复杂,因为RegisterConditonal需要提供一个Registration实例:没有直接接受Func<T>工厂委托的RegisterConditional重载。

You can have 2 singletons one per each database connection.每个数据库连接可以有 2 个单例。 Let's consider an example, firstly we'll need to create an interface for your StorageService:让我们考虑一个示例,首先我们需要为您的 StorageService 创建一个接口:

public interface IStorage
{
    void UsePath();
}

Now let's create couple of implementations of this storage service:现在让我们创建这个存储服务的几个实现:

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

Enum to differentiate between implementations of IStorage:枚举以区分 IStorage 的实现:

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

Once we are done with that, let's create a wrapper for a storage source:完成后,让我们为存储源创建一个包装器:

public interface IStorageWrapper
{
    void DoStuff();
}

Now comes a tricky part, instantiate a storage wrapper service decorator:现在来了一个棘手的部分,实例化一个存储包装服务装饰器:

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

To achieve this, you will need to register your classes in Startup.cs as follows:为此,您需要在 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>();  
    }  
}); 

This wouldn't be as beautiful as calling _storage.DoStuff();这不如调用_storage.DoStuff(); , but I believe would help you with the solution of your problem. ,但我相信会帮助您解决问题。 If you still want to keep it handy, consider managing your settings file and injecting proper IOptions<> instance with a conn string you need and registering a factory method.如果您仍然想保持方便,请考虑管理您的设置文件并使用您需要的 conn 字符串注入适当的 IOptions<> 实例并注册工厂方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM