简体   繁体   English

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

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

This question if sort of an extension or replacement to my earlier question Unable to resolve AutoFac Keyed service with KeyFilterAttribute not working 这个问题是对我先前的问题的扩展或替换,无法解决,但KeyFilterAttribute无法正常工作的AutoFac Keyed服务

So, I have a generic UnitOfWork pattern. 因此,我有一个通用的UnitOfWork模式。 In my earlier question the IUnitOfWork interface was a generic interface. 在我之前的问题中,IUnitOfWork接口是通用接口。 However, I realized that the generic type parameter TContext of IUnitOfWork was of no use. 但是,我意识到IUnitOfWork的通用类型参数TContext没有用。 So I decided to strip my IUnitOfWork of the generic parameter. 因此,我决定删除通用参数的IUnitOfWork。

However, the problem mentioned in my earlier question still exists - wrong type of DbContext is being assigned to the _dbContext property of UnitOfWork. 但是,在我之前的问题中提到的问题仍然存在-将错误类型的DbContext分配给UnitOfWork的_dbContext属性。 With this code, I cannot explicitly specify the context on the service dependency as it is no longer a generic interface. 使用此代码,我无法在服务依赖项上明确指定上下文,因为它不再是通用接口。 So the only solution is to depend on Autofac keyed service. 因此,唯一的解决方案是依靠Autofac键控服务。 Could someone help me this problem. 有人可以帮我这个问题。

Below are the new code snippets for my modified code: 以下是我修改后的代码的新代码段:

Non-generic IUnitOfWork interface: 非通用IUnitOfWork接口:

public interface IUnitOfWork

Generic UnitOfWork class: 通用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 registrations: 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 class: CommentsService类:

public class CommentsService : ICommentsService
{
        private readonly IUnitOfWork _reconciliationUoW;

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

You're going to have to use keyed services all the way down if you aren't going to make IDbContext generic. 如果不打算使IDbContext通用,则将必须一直使用键控服务。 There are a few things you need to consider when you're designing deep levels of things in DI. 在设计DI中的深层次内容时,需要考虑一些事项。

Note: This is an FAQ on the Autofac site but I'll try and unwrap a couple of things here to help out. 注意:这是Autofac网站上的常见问题解答,但我将尝试在此处拆开一些东西以提供帮助。

Concept 1: Last In Wins 概念1:最后获胜

The point of being able to register different types with the same interface is for two purposes: 能够使用同一接口注册不同类型的目的是出于两个目的:

  • You need to resolve a list of those things (eg, IEnumerable<IDbContext> ) OR 您需要解析这些内容的列表 (例如IEnumerable<IDbContext> ),或者
  • You are overriding the default thing being resolved. 您正在覆盖要解决的默认问题。

Your registrations here... 您的注册在这里...

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

...say two things: ...说两件事:

  • If I resolve IEnumerable<IDbContext> I want these three things instantiated and handed back to me. 如果我解析IEnumerable<IDbContext>我希望实例化这三件事并交还给我。
  • If I resolve a single IDbContext I want a ReconciliationDbContext ... no, wait, I really want a GenevaDataDbContext ... no, I meant I want an OpenStaarsDbContext . 如果我解析一个IDbContext我想要一个ReconciliationDbContext ...不,等等,我真的想要GenevaDataDbContext ...不,我的意思是我想要一个OpenStaarsDbContext Yes. 是。 Any time I resolve a single IDbContext I want OpenStaarsDbContext . 每当我解析一个IDbContext我都想要OpenStaarsDbContext

Concept 2: Interface Consumers Don't Know About Underlying Implementations 概念2:接口使用者不了解底层实现

The whole point of interface vs. implementation is so you don't know what the underlying implementation is. 接口与实现的重点在于您不知道底层实现是什么。 This is the Liskov Substitution Principle. 这就是李斯科夫替代原则。 If UnitOfWork<ReconciliationDbContext> must have a ReconciliationDbContext and can only work with that type... then put ReconciliationDbContext in the constructor for that class and don't use an interface. 如果UnitOfWork<ReconciliationDbContext> 必须具有ReconciliationDbContext且只能使用该类型...则将ReconciliationDbContext放入该类的构造函数中,不要使用接口。 Full stop. 句号 If you must have a specific type and can't treat all IDbContext types the same, then there's a design problem. 如果您必须具有特定类型并且不能将所有IDbContext类型都IDbContext相同,则存在设计问题。

Concept 3: Seriously, Interface Consumers Don't Know About Underlying Implementations 概念3:认真地说,接口使用者不了解底层实现

At some point along the way of trying to figure out how to make this work down the stack you'll think, "Hey - I could pass a parameter to the unit-of-work class and have that passed all the way down the resolve stack and set some sort of 'context' that..." NO. 在尝试弄清楚如何在堆栈上进行此工作的过程中,您会想到:“嘿,我可以将参数传递给工作单元类,并一直沿解析过程传递堆叠并设置某种“上下文” ...” This is also an FAQ. 这也是一个常见问题解答。 You can't do that because, again, you shouldn't know or care about what the whole chain of the resolve stack is. 您不能这样做,因为同样,您不应该知道或不在乎解析堆栈的整个链是什么。 If you need that, you're not using DI or "inverting control" - you may as well new-up everything the old way. 如果需要,您无需使用DI或“反相控制”-您也可以采用旧方法重新设置所有内容。

-- -

Again, this is an FAQ on the Autofac site and I recommend reading through to understand in more depth why what you're asking for is going to be kind of difficult and why, in some cases, it's intentionally difficult. 再次,这是Autofac网站上的常见问题解答 ,我建议您通读以更深入地了解为什么您要的东西会有点困难,以及为什么在某些情况下有意地困难。

However, if you want to use keyed services, you'll have to do it literally all the way down the stack. 但是,如果要使用键控服务,则必须从字面上一直进行下去。

CommentService is fine, but you'll also need stuff like... CommentService很好,但是您还需要类似...的东西

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

And then you'll have to update your registrations to enable the filtering like... 然后,您必须更新您的注册才能启用过滤功能,例如...

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

Yeah, that's painful. 是的,那很痛苦。 You can make it a little easier by not defaulting to the same interface everywhere. 您可以通过不默认在所有地方使用相同的界面来使其更容易一些。

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

Then you don't have the keyed stuff for the database contexts, but you'll have to do... 然后,您没有数据库上下文的关键内容,但是您必须这样做...

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

OK, now you can still resolve all the IDbContext objects if you need, but you can also resolve ReconciliationDbContext as a concrete type to support the new ReconciliationUnitOfWork there. 好的,现在您仍然可以根据需要解析所有IDbContext对象,但是您也可以将ReconciliationDbContext解析为一种具体类型,以在那里支持新的ReconciliationUnitOfWork

How can you make it even more generic? 如何使它更通用? Add the generics back in that you said you removed from your other question. 重新添加泛型,因为您说您已从其他问题中删除了泛型。 UnitOfWork<TContext> should have a constructor parameter of type TContext instead of IDbContext . UnitOfWork<TContext>应该具有类型为TContext的构造函数参数,而不是IDbContext You could add a constraint, like: 您可以添加一个约束,例如:

public class UnitOfWork<TContext> where TContext : IDbContext

Now you're assured to get an IDbContext implementation but it'll be a strong type, which is what you need. 现在可以确保获得IDbContext实现,但这将是一个强大的类型,这正是您所需要的。

You could then make it even more generic by not bothering with registering every combination of contexts and types. 然后,您不必烦恼注册上下文和类型的每种组合,就可以使其变得更加通用。

builder.RegisterGeneric(typeof(UnitOfWork<>))
   .AsSelf()
   .InstancePerLifetimeScope();

And then CommentsService should take the exact type of unit of work because it, too, isn't able to use just any old unit of work interchangeably. 然后, CommentsService应该采用确切的工作单元类型,因为它也不能互换使用任何旧的工作单元。

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

Now you're actually using the type system and not breaking Liskov Substitution Principle. 现在,您实际上正在使用类型系统,并且没有违反Liskov替换原理。 Your life will be easier and you'll get what you want. 您的生活会更轻松,您会得到想要的。

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

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