无法使用Autofac解析已注册类型的键控通用参数

时间:2018-10-23 11:29:08

标签: c# dependency-injection inversion-of-control autofac unit-of-work

该问题是对我先前的问题Unable to resolve AutoFac Keyed service with KeyFilterAttribute not working的扩展还是替代?

因此,我有一个通用的UnitOfWork模式。在我之前的问题中,IUnitOfWork接口是通用接口。但是,我意识到IUnitOfWork的通用类型参数TContext没有用。因此,我决定删除通用参数的IUnitOfWork。

但是,在我之前的问题中提到的问题仍然存在-将错误类型的DbContext分配给UnitOfWork的_dbContext属性。使用此代码,我无法在服务依赖项上明确指定上下文,因为它不再是通用接口。因此,唯一的解决方案是依靠Autofac键控服务。有人可以帮我解决这个问题。

以下是我修改后的代码的新代码段:

非通用IUnitOfWork接口:

public interface IUnitOfWork

通用工作单元类:

public sealed class UnitOfWork<TContext> : IDisposable, IUnitOfWork where TContext : IDbContext
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(UnitOfWork<TContext>));

        private readonly IDbContext _dbContext;
        private Dictionary<string, IRepository> _repositories;
        private IDbTransaction Transaction { get; set; }

        public UnitOfWork(IDbContext context)
        {
            _dbContext = context;
        }
    }

Autofac注册:

builder.RegisterType<CommentsService>().As<ICommentsService().WithAttributeFiltering();

builder.RegisterType<ReconciliationDbContext>().As<IDbContext>();
builder.RegisterType<GenevaDataDbContext>().As<IDbContext>();
builder.RegisterType<OpenStaarsDbContext>().As<IDbContext>();

builder.RegisterType<UnitOfWork<ReconciliationDbContext>>().Keyed<IUnitOfWork>(ContextKey.Recon).InstancePerLifetimeScope();
builder.RegisterType<UnitOfWork<OpenStaarsDbContext>>().Keyed<IUnitOfWork>(ContextKey.OpenStaars).InstancePerLifetimeScope();

CommentsService类:

public class CommentsService : ICommentsService
{
        private readonly IUnitOfWork _reconciliationUoW;

        public CommentsService([KeyFilter(ContextKey.Recon)]IUnitOfWork reconciliationUoW)
        {
            _reconciliationUoW = reconciliationUoW;
        }
}

1 个答案:

答案 0 :(得分:0)

如果您不打算使IDbContext通用,就必须一直使用键控服务。在DI中设计深层次的事物时,需要考虑一些事项。

注意:这是an FAQ on the Autofac site ,但我会尝试在此处拆开一些东西以提供帮助。

概念1:最后获胜

能够在同一接口上注册不同类型的目的有两个:

  • 您需要解析这些内容的列表(例如IEnumerable<IDbContext>)或
  • 您正在覆盖要解决的默认问题。

您在这里的注册...

builder.RegisterType<ReconciliationDbContext>().As<IDbContext>();
builder.RegisterType<GenevaDataDbContext>().As<IDbContext>();
builder.RegisterType<OpenStaarsDbContext>().As<IDbContext>();

...说两件事:

  • 如果我解决IEnumerable<IDbContext>,我想实例化这三件事并交还给我。
  • 如果我解析一个IDbContext,我想要一个ReconciliationDbContext ...不,等等,我真的想要一个GenevaDataDbContext ...不,我的意思是我想要一个{{1 }}。是。每当我解析一个OpenStaarsDbContext时,我都想IDbContext

概念2:接口使用者不了解底层实现

接口与实现的重点在于,您不知道什么是基础实现。这就是李斯科夫替代原则。如果OpenStaarsDbContext 必须具有UnitOfWork<ReconciliationDbContext> 并且只能使用该类型...则将ReconciliationDbContext放入该类的构造函数中,不要使用接口。句号如果必须具有特定类型并且不能将所有ReconciliationDbContext类型都相同,则存在设计问题。

概念3:认真地说,界面使用者不了解底层实现

在试图弄清楚如何使其在堆栈上工作的过程中,您会想到:“嘿,我可以将参数传递给工作单元类,并一直传递向下解析堆栈,并设置某种“上下文” ...” This is also an FAQ.您不能这样做,因为同样,您不应该知道或不在乎解析堆栈的整个链是什么。如果需要,您无需使用DI或“反相控制”-您也可以采用旧方法重新安装所有内容。

-

再次,这是an FAQ on the Autofac site ,我建议您通读一遍,以更深入地了解为什么您要的内容会有点困难,以及为什么在某些情况下, 故意困难。

但是,如果要使用键控服务,则必须从字面上一直进行下去。

IDbContext很好,但是您还需要类似...

CommentService

然后您必须更新您的注册才能启用过滤功能,例如...

public class ReconciliationUnitOfWork : UnitOfWork<ReconciliationDbContext>
{
  public ReconciliationUnitOfWork([KeyFilter(ContextKey.Recon)]IDbContext context)
  { /* ... */ }
}

是的,那很痛苦。您可以通过不默认在任何地方使用相同的界面来简化操作。

builder.RegisterType<ReconciliationDbContext>()
       .Keyed<IDbContext>(ContextKey.Recon);
builder.RegisterType<ReconciliationUnitOfWork>()
       .Keyed<IUnitOfWork>(ContextKey.Recon)
       .InstancePerLifetimeScope();

然后,您没有数据库上下文的键控内容,但是您必须这样做...

public class ReconciliationUnitOfWork : UnitOfWork<ReconciliationDbContext>
{
  public ReconciliationUnitOfWork(ReconciliationDbContext context)
    : base(context)
  { /* ... */ }
}

好的,现在您仍然可以根据需要解析所有builder.RegisterType<ReconciliationDbContext>() .AsSelf() .As<IDbContext>(); builder.RegisterType<ReconciliationUnitOfWork>() .Keyed<IUnitOfWork>(ContextKey.Recon) .InstancePerLifetimeScope(); 对象,但是您也可以解析IDbContext作为一种具体类型,以在那里支持新的ReconciliationDbContext

如何使它更通用?重新添加泛型,因为您说您已从其他问题中删除了泛型。 ReconciliationUnitOfWork应该具有类型UnitOfWork<TContext>而不是TContext的构造函数参数。您可以添加一个约束,例如:

IDbContext

现在您可以放心地实现public class UnitOfWork<TContext> where TContext : IDbContext 的实现,但这将是您需要的强类型。

然后您可以不必注册上下文和类型的每种组合而变得更加通用。

IDbContext

然后builder.RegisterGeneric(typeof(UnitOfWork<>)) .AsSelf() .InstancePerLifetimeScope(); 应该采用确切的工作单元类型,因为它也不能互换使用任何旧的工作单元。

CommentsService

现在,您实际上正在使用类型系统,并且没有违反Liskov替换原理。您的生活会更轻松,您会得到想要的。