Property違いの同じ型を複数DI設定してみる。 その1

Propertyの違う、同一Objectを複数DI設定したいだけの人生だった

IEnumerableでInjectionする

DIでper RequestでInjectionしてほしいクラス

    public class PiyoService {

        public string TagName { get; }

        public PiyoService(string tagName) {
            TagName = tagName;
        }
    }

DI設定(ServiceDescriptorの設定)

            services
                .AddScoped(serviceProvider => new PiyoService("Hoge"))
                .AddScoped(serviceProvider => new PiyoService("Fuga"));

Constructor Injection

        public ProductController(ProductService productService, IEnumerable<PiyoService> piyoServices) {
            ProductService = productService;
            PiyoServices = piyoServices;
        }

当たり前ですがこのInjectionの方法だと、IEnumerableが包含するObjectすべてがInjectionされるタイミングでInstance生成されるので、注意が必要です。

尚、プロパティ違いで2つのObjectをServiceDescriptorに設定している状態で、IEnumerableではなく、PiyoServiceでInjectionした場合、コード上、最後に登録したObjectがInjectionされるようです。(上記コードだと、TagNameが"Fuga"のObjectがInjectionされてきました。)

Factoryクラスを作成してInjectionする

DIでper RequestでInjectionしてほしいクラス

using Dapper;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Threading.Tasks;

namespace MicroORMWrapper {
    public class SqlManager : IAsyncDisposable {
        private DbConnection DbConnection { get; set; }

        public bool IsOpenedConnection => DbConnection.State == ConnectionState.Open;

        public string ConnectionName { get; }

        public SqlManager(DbConnection dbConnection, string connectionName) {
            DbConnection = dbConnection;
            ConnectionName = connectionName;
            OpenConnection();
        }

        public void OpenConnection() {
            if (IsOpenedConnection) {
                return;
            }

            DbConnection.Open();
        }

        public async ValueTask CloseConnectionAsync() {
            if (!IsOpenedConnection) {
                return;
            }

            DbConnection.CloseAsync();
        }

        public IEnumerable<TResult> Select<TResult>(string query) where TResult : class? {
            return DbConnection.Query<TResult>(query);
        }

        public IEnumerable<TResult> Select<TResult>(string query, object prameters) where TResult : class? {
            return DbConnection.Query<TResult>(query, prameters);
        }

        public IEnumerable<TResult> Select<TResult, TInclude1>(string query, Func<TResult, TInclude1, TResult> includeFunc, object prameters, string splitOn = "Id") where TResult : class? where TInclude1 : class? {
            return DbConnection.Query(query, includeFunc, prameters, null, true, splitOn);
        }

        public IEnumerable<TResult> Select<TResult, TInclude1, TInclude2>(string query, Func<TResult, TInclude1, TInclude2, TResult> includeFunc, object prameters, string splitOn = "Id") where TResult : class? where TInclude1 : class? where TInclude2 : class? {
            return DbConnection.Query(query, includeFunc, prameters, null, true, splitOn);
        }

        public Task<IEnumerable<TResult>> SelectAsync<TResult>(string query) where TResult : class? {
            return DbConnection.QueryAsync<TResult>(query);
        }

        public Task<IEnumerable<TResult>> SelectAsync<TResult>(string query, object prameters) where TResult : class? {
            return DbConnection.QueryAsync<TResult>(query, prameters);
        }

        public Task<IEnumerable<TResult>> SelectAsync<TResult, TInclude1>(string query, Func<TResult, TInclude1, TResult> includeFunc, object prameters, string splitOn = "Id") where TResult : class? where TInclude1 : class? {
            return DbConnection.QueryAsync(query, includeFunc, prameters, null, true, splitOn);
        }

        public Task<IEnumerable<TResult>> SelectAsync<TResult, TInclude1, TInclude2>(string query, Func<TResult, TInclude1, TInclude2, TResult> includeFunc, object prameters, string splitOn = "Id") where TResult : class? where TInclude1 : class? where TInclude2 : class? {
            return DbConnection.QueryAsync(query, includeFunc, prameters, null, true, splitOn);
        }

        public List<TResult> SelectAsList<TResult>(string query) where TResult : class? {
            return DbConnection.Query<TResult>(query).AsList();
        }

        public List<TResult> SelectAsList<TResult>(string query, object prameters) where TResult : class? {
            return DbConnection.Query<TResult>(query, prameters).AsList();
        }

        public List<TResult> SelectAsList<TResult, TInclude1>(string query, Func<TResult, TInclude1, TResult> includeFunc, object prameters, string splitOn = "Id") where TResult : class? where TInclude1 : class? {
            return DbConnection.Query(query, includeFunc, prameters, null, true, splitOn).AsList();
        }

        public List<TResult> SelectAsList<TResult, TInclude1, TInclude2>(string query, Func<TResult, TInclude1, TInclude2, TResult> includeFunc, object prameters, string splitOn = "Id") where TResult : class? where TInclude1 : class? where TInclude2 : class? {
            return DbConnection.Query(query, includeFunc, prameters, null, true, splitOn).AsList();
        }

        public BuiltInType GetValue<BuiltInType>(string query) {
            return DbConnection.ExecuteScalar<BuiltInType>(query);
        }

        public BuiltInType GetValue<BuiltInType>(string query, object prameters) {
            return DbConnection.ExecuteScalar<BuiltInType>(query, prameters);
        }

        public Task<BuiltInType> GetValueAsync<BuiltInType>(string query) {
            return DbConnection.ExecuteScalarAsync<BuiltInType>(query);
        }

        public Task<BuiltInType> GetValueAsync<BuiltInType>(string query, object prameters) {
            return DbConnection.ExecuteScalarAsync<BuiltInType>(query, prameters);
        }

        public int Execute(string query) {
            return DbConnection.Execute(query);
        }

        public int Execute(string query, object prameters) {
            return DbConnection.Execute(query, prameters);
        }

        public Task<int> ExecuteAsync(string query) {
            return DbConnection.ExecuteAsync(query);
        }

        public Task<int> ExecuteAsync(string query, object prameters) {
            return DbConnection.ExecuteAsync(query, prameters);
        }

        public async ValueTask DisposeAsync() {
            await CloseConnectionAsync();
            GC.SuppressFinalize(this);
        }
    }
}

InjectionしてほしいFactoryクラス

using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;

namespace MicroORMWrapper.Extensions.DependencyInjection {

    public interface ISqlManagerFactory {
        SqlManager GetByName(string name);
    }

    public class SqlManagerFactory : ISqlManagerFactory {

        private readonly IServiceProvider _serviceProvider;

        public SqlManagerFactory(IServiceProvider serviceProvider)
            => _serviceProvider = serviceProvider;

        public SqlManager GetByName(string name) {
            return _serviceProvider.GetServices<SqlManager>().Single(o => o.ConnectionName == name);
        }
    }
}

DI設定(ServiceDescriptorの設定)

        public static IServiceCollection AddSqlManagers(this IServiceCollection serviceDescriptors, IEnumerable<(string connectionName, string connectionString)> connectionSettings) {
            foreach (var (connectionName, connectionString) in connectionSettings) {
                serviceDescriptors
                        .AddScoped<DbConnection>(serviceProvider => new SqlConnection(connectionString))
                        .AddScoped(serviceProvider => new SqlManager(serviceProvider.GetServices<DbConnection>().Single(o => o.ConnectionString == connectionString), connectionName));
            }

            return serviceDescriptors
                .AddScoped<ISqlManagerFactory, SqlManagerFactory>();
        }

Constructor Injection

        public ProductRepository(ISqlManagerFactory sqlManagerFactory) {
            SqlManager = sqlManagerFactory.GetByName(DatabaseNameResource.KashilogDatabase);
        }

結果から言うと、SqlManagerFactory.GetByNameメソッド内で_serviceProvider.GetServicesを呼び出す時にSqlManagerに関連するObjectのInstanceがすべて生成されるため、IEnumerableでのInjectionと課題感は変わりませんでした。

長くなってきたので次回に続きます。