C#. Net Core 2 DI, как мне зарегистрировать интерфейс с классом, который также принимает тот же интерфейс, что и конструктор параметра (Decorator) - PullRequest
0 голосов
/ 01 мая 2020

У меня есть репозиторий, который реализует интерфейс IRepository. Я создал CachedRepository, который также реализует IRepository, но он принимает IRepository в качестве аргумента конструктора. Кроме того, я знаю, что в конечном итоге я создам Oracle11Repository, который наследует Repository, чтобы переопределить какой-либо метод Repository.

Моя задача - иметь возможность поменять эти 3 репозитория в файле startup.cs без изменения остальной части приложения.

Как я могу зарегистрировать IRepository для использования CachedRepository, указав, что CachedRepository должен использовать репозиторий как собственный IRepository?

Очевидно, я получил ошибку "Обнаружена циклическая зависимость для службы типа 'IRepository' "

Или, может быть, это мой подход не так?

//Repository.cs
public class Repository : IRepository
{
        private readonly IDbConnectionFactory dbFactory;
        private readonly ILogger<Repository> logger;

        public AssessmentRepository(IDbConnectionFactory dbFactory, ILogger<Repository> logger, IConfiguration configuration)
        {
            this.dbFactory = dbFactory;
            this.logger = logger;
        }

        public async Task<PagedEnvelope<SearchResult>> Search(string term)
        {}
}
//CachedRepository.cs
public class Repository : IRepository
{
        private IRepository repos;
        private readonly ILogger<Repository> logger;
        private IMemoryCache cache;

        public CachedREpository(IRepository repos, ILogger<Repository> logger,  IMemoryCache cache, IConfiguration configuration)
        {
            this.repos = repos;
            this.logger = logger;
            ConfigureCache(cache, configuration);
        }

        public async Task<PagedEnvelope<SearchResult>> Search(string term)
        {
            // If not in cache, call repos.Search(term) then cache and return 
        }
}
//Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    //...
    services.AddTransient<IRepository, CachedRepository>();
}

Ответы [ 2 ]

0 голосов
/ 01 мая 2020

В конце концов я нахожу способ достичь этого.

//Startup.cs
public void ConfigureServices(IServiceCollection services)
{
  //...
  // Register a concrete implementation so DI know how to construct it with dependency
  services.AddTransient<Oracle11Repository>();

  // Register a call back to tell specific IRepository to inject
  services.AddTransient<IRepository>(sp =>
                new CachedRepository(
                    sp.GetService<Oracle11Repository>(),
                    sp.GetService<ILogger<CachedRepository>>(),
                    sp.GetService<IMemoryCache>(),
                    sp.GetService<IConfiguration>())
  );

Теперь было бы неплохо не указывать аргументы всех других конструкторов, поскольку все они зарегистрированы.

0 голосов
/ 01 мая 2020

Мне кажется, вам нужно установить Repository в качестве базового класса для вашей функциональности и базового интерфейса, который сделает базы согласованными для будущей инъекции, поэтому может потребоваться переименование и новый интерфейс. Допустим, ваша база данных SQL, а объявление класса Repository станет

public class SqlRepository : IRepositoryBase
{
    // SQL Specific Search Method
}

, а базовый интерфейс репозитория теперь

public interface IRepositoryBase
{
    public Task<PagedEnvelope<SearchResult>> Search(string term);
}

Теперь у вас есть спецификация реализации c к вашему SQL доступу и интерфейсу для инъекции, а затем вы вводите его в свой CachedRepository класс, как показано ниже.

public class CachedRepository : IRepository
{
    private IRepositoryBase _reposBase;
    private IMemoryCache _cache;

    public CachedREpository(IRepositoryBase reposBase,   
                        IMemoryCache cache, 
                        IConfiguration configuration)
    {
        this._reposBase = reposBase;
        this._cache = cache;
        // Note I took the logger out, you have it in the _reposBase object now. 
        ConfigureCache(cache, configuration);
    }

    public async Task<PagedEnvelope<SearchResult>> Search(string term)
        {
            // If not in cache, call _reposBase.Search(term) then cache and return 
        }

}  

Теперь в вашем классе Startup вы можете использовать 2 инъекции, чтобы настроить его так, как вы хотите:

//Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    //...
    // Set up injection for Base first
    services.AddTransient<IRepositoryBase, SqlRepository>();

    // Then set up injection for your Cached repo
    services.AddTransient<IRepository, CachedRepository>();
}

Теперь у вас есть некоторая встроенная гибкость, потому что для изменения для Oracle БД вы просто создаете базу хранилища Oracle:

public class OracleRepository : IRepositoryBase
{
    // Oracle Specific Search Method
}

, а затем в своем DI-контейнере просто измените строку

services.AddTransient<IRepositoryBase, SqlRepository>();

на

 services.AddTransient<IRepositoryBase, OracleRepository>();

и вы переключили реализации базы данных, добавив 1 новый класс БД, а затем изменив 1 строку при запуске, и это оставит ваше поведение в кэшировании в покое.

Выше обсуждалось переключение вашей базовой базы данных, но вы, конечно, могли бы также изменить уровень вверх, поэтому CachedRepository можно переключить для всего, что реализует интерфейс IRepository, так что вы получаете гибкость, встроенную в этот слой, а также.

Из того, что я прочитал о вашем вопросе, я думаю, что понимаю, и это, кажется, решает вашу проблему. Нет больше круговой зависимости.

Прокомментируйте, если нет, надеюсь, это поможет!

...