繁体   English   中英

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

[英]Unable to resolve keyed generic parameter of registered type using Autofac

这个问题是对我先前的问题的扩展或替换,无法解决,但KeyFilterAttribute无法正常工作的AutoFac Keyed服务

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

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

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

非通用IUnitOfWork接口:

public interface IUnitOfWork

通用UnitOfWork类:

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

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

注意:这是Autofac网站上的常见问题解答,但我将尝试在此处拆开一些东西以提供帮助。

概念1:最后获胜

能够使用同一接口注册不同类型的目的是出于两个目的:

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

您的注册在这里...

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

...说两件事:

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

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

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

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

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

-

再次,这是Autofac网站上的常见问题解答 ,我建议您通读以更深入地了解为什么您要的东西会有点困难,以及为什么在某些情况下有意地困难。

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

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应该采用确切的工作单元类型,因为它也不能互换使用任何旧的工作单元。

public class CommentsService : ICommentsService
{
  public CommentsService(UnitOfWork<ReconciliationDbContext> reconciliationUoW)
  { /* ... */ }
}

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

暂无
暂无

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

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