如何将具有开放泛型类型的集合注入到使用 Autofac 提供服务



我试图尽我所能解释我公认的复杂问题。如果有什么可以补充澄清的,请告诉我。

简要背景

我有一个用来存储DbWrapper<TInput. TOutput>DbWrapperCollection(因为TInputTOutput会有所不同,该集合实际上只是一个非泛型"容器"的列表,其中包含泛型作为对象以及输入和输出作为 System.Types – 请参阅下面的实现)

另一方面,我有可变数量的服务,所有服务都有自己的IDbWrapperCollection,我想在启动时注入 autofac。

本质上我想做的是这样的:

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

我的问题

正如你在上面看到的,我在调用ResolveNamed()时特别省略了预期的类型参数。那是因为我是 autofac(在某种程度上是泛型)的新手,我特别不知道是否有任何策略可以注入开放泛型DbWrappers列表并推迟关闭我的泛型类型。

我将尝试在下面解释我研究过哪些策略来处理这个问题,以及到目前为止我的实施

我自己的研究

在我看来,我可以为我的包装器创建一个非泛型基类并将它们保存为该基类,将解析原始泛型类型的责任委托给该基类,或者放弃我的包装器集合想法,转而支持我的服务构造函数上的特定参数(无聊 - 并且与我的复合启发实现不兼容)。

随着复合模式的流行,我想我不是第一个使用类似复合模式的解决方案的人,"通用叶子"想要使用 DI 和 IoC。

我打算像这样使用我的水果服务:

myFruitService.GetDbMapper<MyFruitDto, DbEntityForThatSaidFruit(myOrange);

该服务在其 DbMapperCollection 中查找,查找具有提供的类型参数的映射器,并调用其实现 Save();

迄今为止的执行情况

对于那些好奇的人,这是我的实现:

数据库包装器:

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

服务:

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

我的包装器集合帮助程序类:

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>;
}
}

还有我的包装收藏:

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

我看过的问题没有运气

我看过的一些问题,似乎都与我的情况无关(尽管我的问题可以通过通用代表来解决,但我认为这不是我的问题的非常理想的解决方案。

  • 使用 Autofac 注入泛型类型参数
  • 自动法克。如何在构造函数中注入打开的泛型委托
  • 如何使用 Autofac 注入泛型类型的工厂
  • 具有嵌套开放泛型的 Autofac

我不认为你能做你想做的事。对不起,可能不是你想要的答案。我将向您展示原因,也许还有一些解决方法,但是拥有任意的封闭泛型集合,这些泛型在解决之前不会关闭并不是一件好事。

让我们暂时忽略 DI,只考虑FruitService,我在问题中没有看到,但我们在这里的用法中看到:

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

请注意,我们可以看到FruitService实现了IDbMapperService,因为它已注册为该接口。

此外,我们可以看到FruitService看起来它应该需要某种东西的集合,因为在注册示例中有两个名称相同的东西。

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

我注意到它们都实现了不同的泛型类型。我必须根据问题的其余部分假设这些没有共同的基类。

为了使它更具体并超越 Autofac 部分,我认为这与更大的问题并不真正相关,让我们这样考虑:

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

让我们假设CreateWrapperCreateHandler都接受一个字符串,并神奇地创建适当的包装器/处理程序类型。不管它是如何发生的。

这里有两件事需要考虑,它们密切相关:

  • FruitService构造函数中的参数类型是什么?
  • 你期望CreateWrapper("appleService")CreateHandler("appleService")会得到什么回报?

我能看到的基本上只有两个选项。

选项 1:使用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;
}
}

目前尚不清楚DbWrapper<TInput, TResult>是否可以投射到IUcHandler<TInput, TResult>,因此您甚至不能依赖它。没有共性。

但是,假设存在公共基类。

选项 2:使用公共基类

似乎已经有DbWrapper<TInput, TResult>的概念了.请务必注意,即使您定义了该泛型,一旦关闭它,它们也是两种不同的类型。DbWrapper<AppleDto, SavedAppleDbEntity>不能浇注DbWrapper<OrangeDto, SavedOrangeDbEntity>.泛型更像是"类模板"而不是基类。它们不是一回事。

例如,您不能执行以下操作:

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

但是,如果您有通用接口或基类,则可以执行...

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

但这意味着您可以切换到该接口,并且表面上使用通用接口。

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.
}
}

您的使用代码可能只需要IDbWrapper并调用非泛型方法来完成工作。

把它带回Autofac...

还记得我提到过,关键在于弄清楚Create方法应该返回什么;或者FruitService构造函数期望什么?那。那是黑桃。

您可以将所有内容注册为键控对象。

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

Autofac 中的Resolve操作是我示例中的创建方法。那里没有魔法;它只是创建对象。您仍然必须知道您希望它提供哪种类型。

或者,您可以使用公共基类。

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

如果您不介意将 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;
}
}

您必须在不命名的情况下注册事物并AsDbWrapper,但如果一切都基于此,它将起作用。

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

解析IDbMapperService时,FruitService构造函数将获得对解析它的生存期范围的引用。所有包装器都将从同一范围解析。

人们通常不喜欢像这样将 IoC 引用混合到他们的代码中,但这是我可以看到您不必弄乱反射或上下投射的唯一方法。

祝你好运!

最新更新