简体   繁体   English

在运行时获取通用和抽象 class 的实例

[英]Get Instance of Generic and abstract class in Run-Time

在此处输入图像描述

I have an abstract implementation in the ClassLibrary2 and I also have the EntryPointLibrary where I inject the dependencies..我在 ClassLibrary2 中有一个抽象实现,我也有注入依赖项的 EntryPointLibrary。

Code in ClassLibrary2: ClassLibrary2 中的代码:

public abstract class BaseApplicationDbContext<TEntity> : DbContext, IDisposable
    where TEntity : BaseEntity
{
    public BaseApplicationDbContext(DbContextOptions options)
        : base(options)
    {
    }


    public virtual DbSet<TEntity> Entity { get; set; }


    public abstract Task HandleSomething(BaseEntity baseEntity);
}



public class BaseEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
}

So I have a generic and abstract DbContext to be implemented in the EntryPointLibrary and I also have an entity with base things.所以我有一个通用和抽象的 DbContext 要在 EntryPointLibrary 中实现,我还有一个带有基本事物的实体。

Then I also have a Handler class which will receive and handle asynchronous messages (via pubsub):然后我还有一个处理程序 class 将接收和处理异步消息(通过 pubsub):

public class Handler
{
    private readonly IServiceProvider _serviceProvider;

    public Handler(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task TreatTheMessage(string jsonMessage)
    {
        //Notes:
        //----- This is a simple example, but for my real implementation I can't get an instance of BaseApplicationDbContext from the ctor
        //----- This library doesn't have acesse to the EntryPointLibrary, so I don't have access to the Entity class neither the ApplicationDbContext class, but I need to get the ApplicationDbContext instance here. The EntryPointLibrary is the entry point
        using var scope = _serviceProvider.CreateScope();
        using var applicationDbContext = scope.ServiceProvider.GetService<BaseApplicationDbContext<BaseEntity>>();

        //Convert from json to BaseEntity
        BaseEntity baseEntity = new BaseEntity
        {
            Id = 1,
            //....
        };

        await applicationDbContext.HandleSomething(baseEntity);

    }
}

Then in the EntryPointLibrary I have 3 entities where the Entity.cs is extending the BaseEntity (from library2) and is being used to setup the generic: (Note that I have the implementation of an abstract method where I will receice the BaseEntity with value from the Handler class)然后在 EntryPointLibrary 我有 3 个实体,其中 Entity.cs 正在扩展 BaseEntity(来自 library2)并用于设置泛型:(请注意,我有一个抽象方法的实现,我将接收 BaseEntity 的值来自处理程序类)

public class ApplicationDbContext : BaseApplicationDbContext<Entity>
{
    public ApplicationDbContext(DbContextOptions options)
        : base(options)
    {
    }

    public override async Task HandleSomething(BaseEntity baseEntity)
    {
        var entityWithSubEntities = Entity
             .Include(x => x.SubEntity1)
             .Include(x => x.SubEntity2);
    }
}

public class Entity : BaseEntity
{
    public SubEntity1 SubEntity1 { get; set; }
    public SubEntity2 SubEntity2 { get; set; }
}

And in the Program.cs file I am injecting the dependencies:在 Program.cs 文件中,我正在注入依赖项:

class Program
{
    static void Main(string[] args)
    {
        //setup our DI
        var serviceProvider = new ServiceCollection()
            .AddPubSubAsHostedService() //This is to subscribe a PubSub which will fire the Handler class from ClassLibrary2 when a Message is comming in
            .AddDbContext<ApplicationDbContext>((serviceProvider, dbOptionsBuilder) =>
            {
                dbOptionsBuilder.UseNpgsql("dbConnectionString");
            })
            .AddScoped<BaseApplicationDbContext<Entity>, ApplicationDbContext>()
            .BuildServiceProvider();


        Console.WriteLine("Hello World!");
    }
}

Question: In the Handler class how can I get the instance of the BaseApplicationDbContext which is being implemented by the ApplicationDbContext and using the Entity class?问题:在处理程序 class 中,如何获取由 ApplicationDbContext 实现并使用实体 class 的 BaseApplicationDbContext 实例? -> this following line is returning null: -> 下面这行返回 null:

using var applicationDbContext = scope.ServiceProvider.GetService<BaseApplicationDbContext<BaseEntity>>();

There's a couple problems here.这里有几个问题。 First, the MS DI library is quite limited in terms of inheritance and registering open generic types.首先,MS DI 库在 inheritance 和注册开放泛型类型方面非常有限。 It's a bare-bones DI, originally designed with the idea that people would use a "real DI" in production apps, but that usually doesn't happen (and often can't happen, unfortunately).这是一个简单的 DI,最初的设计理念是人们会在生产应用程序中使用“真正的 DI”,但这通常不会发生(不幸的是,这通常不会发生)。

The other problem is that the code is designed assuming generic variance holds for classes, which just isn't the case.另一个问题是代码的设计假设类的通用方差成立,但事实并非如此。 Eg, this code will fail to compile :例如,此代码将无法编译

BaseApplicationDbContext<Entity> context = null;
BaseApplicationDbContext<BaseEntity> baseContext = context;

So, even ignoring the DI limitations (for now), what you're trying to do just won't work.因此,即使忽略 DI 限制(暂时),您尝试做的事情也行不通。 You can't have an instance of BaseApplicationDbContext<Entity> and treat it as an instance of BaseApplicationDbContext<BaseEntity> .您不能拥有BaseApplicationDbContext<Entity>的实例并将其视为BaseApplicationDbContext<BaseEntity>的实例。

First, I'd take a step back and reconsider the design.首先,我会退后一步,重新考虑设计。 Do you really need a BaseEntity ?你真的需要一个BaseEntity吗? Or a BaseApplicationDbContext ?还是BaseApplicationDbContext If this complexity is necessary , then fine;如果这种复杂性是必要的,那很好; but do not create abstractions without needing to.但不要在不需要的情况下创建抽象。 Premature abstraction only causes complexity;过早的抽象只会导致复杂性; it doesn't solve any real problems (it only attempts to solve future problems, and may or may not have the correct solution).它不能解决任何实际问题(它只是试图解决未来的问题,可能有也可能没有正确的解决方案)。

But, assuming that the design is solid and appropriate, then you'll need to solve this by using interfaces.但是,假设设计是可靠且合适的,那么您需要通过使用接口来解决这个问题。 Interfaces allow multiple implementations, and as a bonus they can also allow generic variance.接口允许多种实现,并且作为奖励,它们还可以允许泛型变化。

Eg :例如

public interface IEntityHandler
{
    Task HandleSomething(BaseEntity baseEntity);
}
    
public class BaseApplicationDbContext<TEntity> : IEntityHandler
    where TEntity : BaseEntity
{
    ...
}

Then you can use IEntityHandler instead.然后您可以改用IEntityHandler

Or, using generic variance to increase type safety ( fiddle ):或者,使用泛型方差来提高类型安全性(小提琴):

public interface IEntityHandler<in TEntity>
{
    Task HandleSomething(TEntity entity);
}
    
public class BaseApplicationDbContext<TEntity> : IEntityHandler<TEntity>
    where TEntity : BaseEntity
{
    ...
    public abstract Task HandleSomething(TEntity entity);
}

Once you get the shape of what you want working, then attack the DI side of it.一旦你得到你想要工作的形状,然后攻击它的 DI 方面。 You'll need to register and resolve your own interface type;您需要注册并解析您自己的接口类型; the MS DI won't register it for you. MS DI 不会为您注册它。

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

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