在简单注入器v3.0.3中,使用ResolveUnregisteredType会导致Torn Lifestyle警告
本文关键字:ResolveUnregisteredType Torn 警告 Lifestyle 使用 注入器 简单 v3 | 更新日期: 2023-09-27 18:10:24
在创建新的web应用程序时,我使用一个自制的"外部"库来获得一些基本的基础设施。我最近对我的存储库的工作方式做了一些改变,并在简单注入器诊断警告中遇到了这个警告:
SimpleInjector。DiagnosticVerificationException:配置为无效的。报告了以下诊断警告:IUnitOfWork的注册映射到与IUnitOfWork注册相同的实现和生活方式,IUnitOfWork、IUnitOfWork和IUnitOfWork都可以。它们都映射到EmptyUnitOfWork (Singleton)。这将导致每个注册解析到不同的实例:每个注册都有自己的实例实例。的详细信息,请参阅"错误"属性警告。请参见https://simpleinjector.org/diagnostics如何修复问题及如何抑制个别警告。
我的库的核心程序集有一个IUnitOfWork
的空实现称为EmptyUnitOfWork
,它只是一个简单的无操作类:
internal sealed class EmptyUnitOfWork : IUnitOfWork
{
public void SaveChanges()
{
// Do nothing
}
}
当没有其他可用的工作单元时,将该类注册到容器中。我通过使用container.ResolveUnregisteredType(...)
这样做:
// Register an EmptyUnitOfWork to be returned when a IUnitOfWork is requested:
container.ResolveUnregisteredType += (sender, e) =>
{
if (e.UnregisteredServiceType == typeof(IUnitOfWork))
{
// Register the instance as singleton.
var registration = Lifestyle.Singleton.CreateRegistration<IUnitOfWork, EmptyUnitOfWork>(container);
e.Register(registration);
}
};
这是我第一次使用上面的方法,但是在这个测试中工作得很好:
[Fact]
public void RegistersEmptyUnitOfWork_AsSingleton_WhenIUnitOfWorkIsNotRegisted()
{
var instance = _container.GetInstance<IUnitOfWork>();
var registration = _container.GetRegistration(typeof (IUnitOfWork));
Assert.NotNull(instance);
Assert.IsType<EmptyUnitOfWork>(instance);
Assert.Equal(Lifestyle.Singleton, registration.Lifestyle);
}
现在对于有趣的部分,我在支持库中有一个扩展方法,如果我的应用程序需要一个,它会注册一个EntityFramework IUnitOfWork
:
public static void RegisterEntityFramework<TContext>(this Container container) where TContext : DbContext
{
if (container == null)
throw new ArgumentNullException(nameof(container));
var lifestyle = Lifestyle.CreateHybrid(() =>
HttpContext.Current != null,
new WebRequestLifestyle(),
new LifetimeScopeLifestyle()
);
container.Register<DbContext, TContext>(lifestyle);
container.Register<IUnitOfWork, EntityFrameworkUnitOfWork>(lifestyle);
container.Register(typeof (IRepository<>), typeof (EntityFrameworkRepository<>), lifestyle);
}
但不知何故,这抛出了简单注入器的警告-但我只是注入了EntityFrameworkUnitOfWork
,所以EmptyUnitOfWork
不应该被触发?
这个设计的原因是我在我的核心库中有一个CommandTransactionDecorator
,它使用IUnitOfWork
来保存更改。我只是想有一个空的,如果一个IUnitOfWork
是不需要的应用程序。
作为参考,这是装饰器:
internal sealed class CommandTransactionDecorator<TCommand> : IHandleCommand<TCommand> where TCommand : ICommand
{
private readonly IUnitOfWork _unitOfWork;
private readonly Func<IHandleCommand<TCommand>> _handlerFactory;
public CommandTransactionDecorator(IUnitOfWork unitOfWork, Func<IHandleCommand<TCommand>> handlerFactory)
{
_unitOfWork = unitOfWork;
_handlerFactory = handlerFactory;
}
public void Handle(TCommand command)
{
_handlerFactory().Handle(command);
_unitOfWork.SaveChanges();
}
}
看起来这就是发出警告的注册符:
var registration = Lifestyle.Singleton.CreateRegistration<IUnitOfWork, EmptyUnitOfWork>(container);
e.Register(registration);
将其更改为e.Register(() => new EmptyUnitOfWork());
使警告消失,但生活方式不是单身吗?
您所看到的是ResolveUnregisteredType
被多次调用。这将导致为同一类型进行多个单例注册。每个注册都有自己的实例。这将导致应用程序由该类型的多个实例组成,当您将类型注册为单例类型时,通常不希望发生这种情况。因为你的EmptyUnitOfWork
没有任何行为,可能没有问题,但简单注入器显然不能猜到这是这种情况,所以它抛出一个异常。
你正在经历的是一个在Simple Injector v3中引入的突破性更改/错误。在简单注入器v1和v2中,ResolveUnregisteredType
的注册结果被缓存;这意味着对Verify()
的调用只会触发你的自定义委托一次。然而,在Simple Injector v3.0中,结果注册不再被缓存。这是一个疏忽,已经被忽略了。我们的想法是让ResolveUnregisteredType
具有上下文意识。为了了解上下文,缓存不再是一种选择。所以缓存被删除了,但我们最终决定不让ResolveUnregisteredType
上下文感知,而我们忘记了再次添加缓存。
Verify()
方法的使用。Verify()
在单个线程上构建所有对象图。然而,如果不使用Verify()
,对象图是惰性编译的,如果你正在运行一个多线程应用程序,多个线程可以同时调用ResolveUnregisteredType
;简单注入器从未锁定ResolveUnregisteredType
,这是有记录的。
因此,这样做的结果是,如果没有调用Verify()
,您仍然可以在该特定组件的多个注册中结束,这当然再次可能导致非常难看的难以发现的问题,通常只在生产中以某种方式出现一次。
你应该这样写注册:
Lazy<Registration> registration = new Lazy<Registration>(() =>
Lifestyle.Singleton.CreateRegistration<IUnitOfWork, EmptyUnitOfWork>(container));
container.ResolveUnregisteredType += (sender, e) => {
if (e.UnregisteredServiceType == typeof(IUnitOfWork)) {
e.Register(registration.Value);
}
};
但是在Simple Injector v3中,你几乎不再需要使用ResolveUnregisteredType
事件了。您可以进行以下注册:
container.RegisterConditional<IUnitOfWork, EntityFrameworkUnitOfWork>(Lifestyle.Scoped,
c => true);
// NOTE: This registration must be made second
container.RegisterConditional<IUnitOfWork, EmptyUnitOfWork>(Lifestyle.Singleton,
c => !c.Handled);
这完全解决了必须考虑多线程的问题。这里我们进行了两个条件注册,其中第一个总是应用(使用谓词c => true
)。你可能想使用Register<IUnitOfWork, EFUoW>()
使第一次注册成为无条件的,但这是行不通的,因为简单注入器会检测到第二次注册永远不能应用,并且会抛出异常。因此,c => true
谓词的使用抑制了这种检测。我通常不会建议这样的构造,因为它蒙蔽了简单注入器。然而,在你的情况下,这似乎是合理的,因为两个注册是在不同的时刻进行的。
我现在必须考虑是否要在v3中更改此行为并进行缓存。缓存的优点是它可以提高性能,但缺点是它隐藏了bug。