简体   繁体   English

如何使用开放的泛型类型注入集合以通过Autofac服务

[英]How to inject collection with open generic types to service with Autofac

I've tried to explain my admittedly complex problem to the best of my abilities. 我试图尽我所能解释我公认的复杂问题。 Let me know if there is anything I can add to clarify. 让我知道是否有任何需要补充的内容。

A brief background 简要背景

I have a DbWrapperCollection I use to store DbWrapper<TInput. TOutput> 我有一个DbWrapperCollection用于存储DbWrapper<TInput. TOutput> DbWrapper<TInput. TOutput> (since TInput and TOutput will vary, the collection is really just a list of non-generic “containers” containing the generic as an object as well as the input and out as System.Types – see my implementation below) DbWrapper<TInput. TOutput> (因为TInputTOutput会有所不同,所以该集合实际上只是一个非泛型“容器”的列表,其中包含泛型作为对象以及输入和输出为System.Types –请参阅下面的实现)

On the other hand, I have a variable number of services, all with their own IDbWrapperCollection that I want to inject with autofac at startup. 另一方面,我有可变数量的服务,所有服务都具有自己的IDbWrapperCollection ,我希望在启动时通过autofac注入这些服务。

Essentially what I want to do is this: 本质上我想做的是这样的:

builder.RegisterType<SaveApplDbWrapper>().As<DbWrapper<AppleDto, SavedAppleDbEntity>>()
    .Named<string>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>().As<IUcHandler<OrangeDto, OrangeDbEntity>>()
    .Named<string>("fruitService");
builder.RegisterType<SaveMelon>().As<IUcHandler<MelonDto, MelonDbEntity>>()
    .Named<string>("appleService");

builder.Register(c => new FruitService(c.ResolveNamed("appleService")))
    .As<IDbMapperService>();

My problem 我的问题

As you can see above, I specifically left out the expected type parameters when calling ResolveNamed() . 如您在上面看到的,在调用ResolveNamed()时,我特别省略了预期的类型参数。 That's because I, being new to autofac (and to some extent, generics), I specifically don't know if there are any strategies to inject a list of open generic DbWrappers and defer closing of my generic type. 这是因为我是autofac(在某种程度上是泛型)的新手,我特别不知道是否有任何策略可以插入开放的泛型DbWrappers列表并推迟我的泛型类型的关闭。

I will try to explain which strategies i've researched for dealing with this below, as well as my implementation so far 我将尝试在下面解释为解决此问题而研究的策略,以及到目前为止的实现方式

My own research 我自己的研究

The way I see it, I could either create a non-generic baseclass for my wrappers and save them as that baseclass, delegating the responsibility of resolving the original generic type to that baseclass, or ditch my wrapper collection idea in favor of specific parameters on my service constructor (boring – and not compatible with my composite-inspired implementation). 以我的方式看,我可以为包装器创建一个非泛型基类,然后将它们保存为该基类,委派将原始泛型类型解析为该基类的责任,或者放弃我的包装器集合的想法,转而使用特定的参数我的服务构造函数(很无聊,并且与我的复合启发式实现不兼容)。

With the popularity of composite pattern, I'd imagine I'm not the first person with a composite-pattern-like solution with “generic leafs” wanting to use DI and an IoC. 随着复合模式的普及,我想我不是第一个使用“通用叶子”的,希望使用DI和IoC的类似于复合模式的解决方案的人。

I'm planning to use my fruitService like this: 我打算像这样使用我的fruitService:

myFruitService.GetDbMapper<MyFruitDto, DbEntityForThatSaidFruit(myOrange);

The service looks in it's DbMapperCollection, finds the mapper with the provided type arguments, and call its implementation of Save(); 该服务在其DbMapperCollection中进行查找,找到具有提供的类型参数的映射器,并调用其Save()的实现;

Implementation so far 到目前为止的实施

For those curious, here is my implementations: 对于那些好奇的人,这里是我的实现:

DbWrappers: DbWrappers:

class SaveApplebWrapper : DbWrapper<TInput, TOutput>
// and plenty more wrapppers for any fruit imaginable

Service: 服务:

public abstract class DbMapperService : IDbMapperService
{
    public IWrapperCollection Wrappers { get; set; }

    protected BestandService(IWrapperCollection wrappers)
    {
        Wrappers = wrappers;
    }

    public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
    {
        return Wrappers.GetWrapper<TInput, TResult>();
    }
}

My WrapperCollection helper classes: 我的WrapperCollection帮助器类:

public struct WrapperKey
{
    public static WrapperKey NewWrapperKey <TInput, TResult>()
    {
        return new WrapperKey { InputType = typeof(TInput), ResultType = typeof(TResult) };
    }

    public Type InputType { get; set; }
    public Type ResultType { get; set; }
}

public struct WrapperContainer
{
    public WrapperContainer(object wrapper) : this()
    {
        Wrapper= wrapper;
    }

    public object Wrapper{ get; set; }

    public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
    {
        return Wrapper as DbWrapper<TInput, TResult>;
    }
}

And my WrapperCollection: 还有我的WrapperCollection:

public class UcWrapperCollection : Dictionary<WrapperKey, WrapperContainer>,
    IDbWrapperCollection
{
    public void AddWrapper<TInput, TResult>(UcHandler<TInput, TResult> handler)
    {
        Add(WrapperKey.NewWrapperKey<TInput, TResult>(), new WrapperContainer(handler));
    }

    public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
    {
        var key = WrapperKey.NewWrapperKey<TInput, TResult>();
        return this[key].GetWrapper<TInput, TResult>();
    }
}

Questions I've looked at without luck 我运气不好看的问题

Some questions I've looked at, none of which seemed relevant to my case (although my problem could potentially be solved with a generic delegate, I don't think it's a very optimal solution for my problem. 我看过的一些问题,似乎都与我的情况无关(尽管我的问题可以用通用委托来解决,但我认为这不是解决我问题的最佳解决方案。

  • Injecting Generic type parameters with Autofac 使用Autofac注入通用类型参数
  • Autofac. Autofac。 How to inject a open Generic Delegate in constructor 如何在构造函数中注入开放的泛型委托
  • How to inject a factory of generic types with Autofac 如何使用Autofac注入泛型类型的工厂
  • Autofac with nested open generics 具有嵌套开放泛型的Autofac

I don't think you're going to be able to do what you want. 我认为您将无法做自己想做的事。 Sorry, probably not the answer you'd like. 抱歉,可能不是您想要的答案。 I'll show you why, and maybe some workarounds, but having an arbitrary collection of closed generics that don't get closed until resolution isn't really a thing. 我将向您展示原因以及可能的解决方法,但是拥有封闭的泛型的任意集合,直到解决问题才关闭,这并不是一件真正的事情。

Let's ignore DI for a second and just consider FruitService , which I don't see in the question, but which we see in a usage here: 让我们FruitService忽略DI,只考虑FruitService ,我在问题中没有看到它,但是我们在这里的用法中看到了它:

builder.Register(c => new FruitService(c.ResolveNamed("appleService")))
    .As<IDbMapperService>();

Note we can see that FruitService implements IDbMapperService because it's registered as that interface. 注意,我们可以看到FruitService实现了IDbMapperService因为它已注册为该接口。

Further, we can see FruitService looks like it should take some sort of collection of things, since there are two things named the same in the registration example. 此外,我们可以看到FruitService看起来应该采取某种方式的集合,因为在注册示例中有两个名称相同的事物。

builder.RegisterType<SaveApplDbWrapper>().As<DbWrapper<AppleDto, SavedAppleDbEntity>>()
    .Named<string>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>().As<IUcHandler<OrangeDto, OrangeDbEntity>>()
    .Named<string>("fruitService");

I noticed that these both implement different generic types. 我注意到它们都实现了不同的泛型类型。 I have to assume based on the rest of the question that these have no common base class. 我必须根据其余问题假设它们没有通用的基类。

To make it more concrete and get past the Autofac part, which I don't think is really relevant to the larger issue, let's consider it like this: 为了使它更具体并超越Autofac部分,我认为它与更大的问题并不真正相关,让我们这样考虑:

var wrapper = new[] { CreateWrapper("appleService"), CreateHandler("appleService") };
var service = new FruitService(wrapper);

Let's assume CreateWrapper and CreateHandler both take a string and, magically, creates the appropriate wrapper/handler types. 假设CreateWrapperCreateHandler都使用字符串,并且神奇地创建了适当的包装器/处理程序类型。 Doesn't matter how it happens. 没关系如何发生。

There are two things to consider here that relate closely: 这里有两件事需要紧密联系:

  • What is the type of the parameter in the FruitService constructor? FruitService构造函数中的参数类型是什么?
  • What do you expect CreateWrapper("appleService") and CreateHandler("appleService") to return? 您期望CreateWrapper("appleService")CreateHandler("appleService")返回什么?

There are basically only two options here I can see. 我在这里基本上只有两个选择。

Option 1: Use object . 选项1:使用object

If there's no common base class, then everything has to be object . 如果没有通用的基类,那么一切都必须是object

public class FruitService : IDBMapperService
{
  private readonly IEnumerable<object> _wrappers;
  public FruitService(IEnumerable<object>wrapper)
  {
    this._wrapper = wrapper;
  }

  public object GetWrapper<TInput, TResult>()
  {
    object foundWrapper = null;
    // Search through the collection using a lot of reflection
    // to find the right wrapper, then
    return foundWrapper;
  }
}

It's not clear that DbWrapper<TInput, TResult> can be cast to IUcHandler<TInput, TResult> so you can't even rely on that. 尚不清楚DbWrapper<TInput, TResult>可以IUcHandler<TInput, TResult>IUcHandler<TInput, TResult>因此您甚至不能依赖它。 There's no commonality. 没有共同点。

But let's say there is common base class. 但是,可以说有一个通用的基类。

Option 2: Use the common base class 选项2:使用通用基类

It seems there's already a notion of DbWrapper<TInput, TResult> . 似乎已经有了DbWrapper<TInput, TResult>的概念。 It's important to note that even if you have that generic defined, once you close it, they're two different types. 重要的是要注意,即使您定义了该泛型,一旦将其关闭,它们也是两种不同的类型。 DbWrapper<AppleDto, SavedAppleDbEntity> is not castable to DbWrapper<OrangeDto, SavedOrangeDbEntity> . DbWrapper<AppleDto, SavedAppleDbEntity>不可转换为DbWrapper<OrangeDto, SavedOrangeDbEntity> Generics are more like "class templates" than base classes. 泛型更像是“类模板”而不是基类。 They're not the same thing. 他们不是一回事。

You can't, for example, do: 例如,您不能执行以下操作:

var collection = new DbWrapper<,>[]
{
  new DbWrapper<AppleDto, SavedAppleDbEntity>(),
  new DbWrapper<OrangeDto, SavedOrangeDbEntity>()
};

However, if you have a common interface or base class, you can do... 但是,如果您有通用的接口或基类,则可以...

var collection = new IDbWrapper[]
{
  new DbWrapper<AppleDto, SavedAppleDbEntity>(),
  new DbWrapper<OrangeDto, SavedOrangeDbEntity>()
};

But that'd mean you can switch to that and, ostensibly, use the common interface. 但这意味着您可以切换到表面上,并使用通用接口。

public class FruitService : IDBMapperService
{
  private readonly IEnumerable<object> _wrappers;
  public FruitService(IEnumerable<object>wrapper)
  {
    this._wrapper = wrapper;
  }

  public IDbWrapper GetWrapper<TInput, TResult>()
  {
    IDbWrapper foundWrapper = null;
    // Search through the collection using a lot of reflection
    // to find the right wrapper, then
    return foundWrapper;

    // IDbWrapper could expose those `TInput` and `TResult`
    // types as properties on the interface, so the reflection
    // could be super simple and way more straight LINQ.
  }
}

Your consuming code could just take IDbWrapper and call non-generic methods to get things done. 您使用的代码可以只使用IDbWrapper并调用非泛型方法来完成任务。

Bringing it back to Autofac... 将其带回Autofac ...

Remember I mentioned the key was in figuring out what the Create methods should return; 记得我曾提到关键是弄清楚Create方法应该返回什么。 or what the FruitService constructor expects? 还是FruitService构造函数期望什么? That. 那。 That in spades. 黑桃色的。

You could register everything as keyed objects. 您可以将所有内容注册为键对象。

builder.RegisterType<SaveApplDbWrapper>()
       .Named<object>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>()
       .Named<object>("fruitService");
builder.RegisterType<SaveMelon>()
       .Named<object>("appleService");

builder
  .Register(c => new FruitService(c.ResolveNamed<IEnumerable<object>>("appleService")))
  .As<IDbMapperService>();

The Resolve operations in Autofac are the create methods from my example. Autofac 中的 Resolve操作我的示例中的create方法 There's no magic there; 那里没有魔术。 it's just creating objects. 它只是创建对象。 You still have to know what type you want it to provide. 您仍然必须知道您要提供的类型。

Or you could use a common base class. 或者,您可以使用通用的基类。

builder.RegisterType<SaveApplDbWrapper>()
       .Named<IDbWrapper>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>()
       .Named<IDbWrapper>("fruitService");
builder.RegisterType<SaveMelon>()
       .Named<IDbWrapper>("appleService");

builder
  .Register(c => new FruitService(c.ResolveNamed<IEnumerable<IDbWrapper>>("appleService")))
  .As<IDbMapperService>();

If you don't mind mixing the DI system into the FruitService you can do something like this: 如果您不介意将DI系统混入FruitService ,则可以执行以下操作:

public class FruitService
{
  private readonly ILifetimeScope _scope;
  public FruitService(ILifetimeScope scope)
  {
    this._scope = scope;
  }

  public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
  {
    var type = typeof(DbWrapper<TInput, TResult>);
    var wrapper = this._lifetimeScope.Resolve(type);
    return wrapper;
  }
}

You'd have to register things without them being named and As a DbWrapper , but it'd work if everything is based on that. 您必须注册事物而无需将其命名为As DbWrapper ,但是如果一切都基于DbWrapper ,它就可以工作。

builder.RegisterType<SaveApplDbWrapper>()
       .As<DbWrapper<AppleDto, SavedAppleDbEntity>>();
// Must be DbWrapper, can also be other things...
builder.RegisterType<SaveOrangeDbWrapper>()
       .As<IUcHandler<OrangeDto, OrangeDbEntity>>()
       .As<DbWrapper<OrangeDto, OrangeDbEntity>>();
builder.RegisterType<SaveMelon>()
       .As<DbWrapper<MelonDto, MelonDbEntity>>()
       .As<IUcHandler<MelonDto, MelonDbEntity>>();

builder.RegisterType<FruitService>()
       .As<IDbMapperService>();

When you resolve IDbMapperService the FruitService constructor will get a reference to the lifetime scope from which it was resolved. 解析IDbMapperServiceFruitService构造函数将获得对其解析的生存期范围的引用。 All of the wrappers will be resolved from that same scope. 所有包装器都将在同一范围内解析。

Folks generally don't like mixing IoC references into their code like this, but it's the only way I can see you'd get away from having to mess with reflection or casting up and down all over. 人们通常不喜欢像这样将IoC引用混入他们的代码中,但这是我看到您摆脱混乱或全程抛弃的唯一途径。

Good luck! 祝好运!

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

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