简体   繁体   English

C#.Net Core 2 DI,如何使用 class 注册接口,该接口也采用与构造函数相同的接口作为参数(装饰器)

[英]C# .Net Core 2 DI, how can I register an interface with a class that also take the same interface as constructor a parameter (Decorator)

I have a Repository that implement a IRepository interface.我有一个实现 IRepository 接口的存储库。 I have created a CachedRepository that also implement IRepository but it take a IRepository as constructor argument.我创建了一个 CachedRepository ,它也实现了 IRepository 但它采用 IRepository 作为构造函数参数。 Also, I know that I will eventually create an Oracle11Repository that inherit Repository to override some Repository method.另外,我知道我最终会创建一个继承 Repository 的 Oracle11Repository 来覆盖一些 Repository 方法。

My objectif is to be able to swap these 3 Repositorys in the startup.cs without modifying the rest of the app.我的目标是能够在 startup.cs 中交换这 3 个存储库,而无需修改应用程序的 rest。

How can I register the IRepository to use CachedRepository, specifying that the CachedRepository must use the Repository as is own IRepository?如何注册 IRepository 以使用 CachedRepository,指定 CachedRepository 必须像自己的 IRepository 一样使用 Repository?

Obviously, i got the error "A circular dependency was detected for the service of type 'IRepository'"显然,我收到错误“检测到'IRepository'类型的服务的循环依赖”

Or maybe it's my approch that is not correct?或者也许是我的方法不正确?

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

It looks to me like you need to set up Repository as a base class for your functionality, and a base interface that will make bases consistent for future injection, so a rename and a new interface might be in order.在我看来,您需要将Repository设置为基础 class 以实现您的功能,以及使基础与未来注入保持一致的基础接口,因此可能需要重命名和新接口。 So lets say your database is SQL, and your Repository class declaration would become因此,假设您的数据库是 SQL,您的Repository class 声明将变为

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

and the base repository interface is now并且基础存储库接口现在是

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

You now have an implementation specific to your SQL access and an interface for injection, and then you inject it into your CachedRepository class like below.您现在有一个特定于您的 SQL 访问的实现和一个用于注入的接口,然后您将其注入到您的CachedRepository class 中,如下所示。

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 
        }

}  

Now in your Startup class, you can use 2 injections to configure it how you want it:现在在您的Startup class 中,您可以使用 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>();
}

Now you have some flexibility built in, because to change to an Oracle DB, you just create the Oracle repository base:现在您有了一些内置的灵活性,因为要更改为 Oracle DB,您只需创建 Oracle 存储库库:

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

and then in your DI container just change the line然后在您的 DI 容器中更改行

services.AddTransient<IRepositoryBase, SqlRepository>();

to

 services.AddTransient<IRepositoryBase, OracleRepository>();

and you've switched database implementations by adding 1 new DB class, and then changing 1 line in startup, and this leaves your caching behavior alone.并且您已经通过添加 1 个新 DB class 来切换数据库实现,然后在启动时更改 1 行,这样就可以单独处理缓存行为。

Above discusses switching your base database, but you could of course change the level up as well, so CachedRepository can be switched out for anything that implements the IRepository interface, so you get the flexibility built into that layer as well.上面讨论了切换您的基础数据库,但您当然也可以更改级别,因此可以将CachedRepository切换为实现IRepository接口的任何内容,因此您也可以在该层中获得内置的灵活性。

From what I read of your question I think I understand and this appears to solve your problem.从我读到的你的问题,我想我明白了,这似乎可以解决你的问题。 No more circular dependency.不再有循环依赖。

Please comment if not, hope this helps!如果没有请评论,希望这有帮助!

I finaly find a way to acheive that.我终于找到了实现这一目标的方法。

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

Now it would be nice to not have to specify all others constructors arguments since they are all registred.现在不必指定所有其他构造函数 arguments 会很好,因为它们都已注册。

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

相关问题 如何使用 .NET CORE DI 为同一接口注册多个实现 - How to register multiple implementation for same interface using .NET CORE DI 如何在AutoFac中注册此.NET接口? - How can I register this .NET interface with AutoFac? Autofac 6 - 将装饰器注册为也实现另一个接口 - Autofac 6 - register a decorator as also implementing another interface 如何在 Asp.Net Core 中注册同一接口的多个实现? - How to register multiple implementations of the same interface in Asp.Net Core? .Net Core如何多次注册同一个接口? - How to register same interface multiple times in .Net Core? 我怎样才能拥有一个实现接口并继承自抽象 class 的 class? (C# .NET 3.5) - How can I have a class that implements an interface and inherits from an abstract class? (C# .NET 3.5) Asp.net 内核如何注册也包含自定义接口的 IHostedService - Asp.net core How to Register IHostedService which also Contains a Custom Interface 如何将 C# 类作为字符串 UserInput,并使其可用于 ASP.NET Core 运行时? - How can I take a C# Class, as a string UserInput, and make it available to the ASP.NET Core runtime? 如何在Asp.Net Core DI上注入具有不同生命周期的同一个类? - How can I inject same class with different Life Cycles on Asp.Net Core DI? 如何在 ASP NET Core 中使用 DI 注入特定的接口实现 - How to inject specific interface implementation using DI in ASP NET Core
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM