Не удается разрешить ключевой универсальный параметр зарегистрированного типа с помощью Autofac - PullRequest
0 голосов
/ 23 октября 2018

Этот вопрос, если что-то вроде расширения или замены моего предыдущего вопроса Невозможно разрешить службу AutoFac Keyed с KeyFilterAttribute не работает

Итак, у меня есть универсальный шаблон UnitOfWork.В моем предыдущем вопросе интерфейс IUnitOfWork был универсальным интерфейсом.Однако я понял, что универсальный параметр типа TContext из IUnitOfWork бесполезен.Поэтому я решил лишить свой IUnitOfWork универсального параметра.

Однако проблема, упомянутая в моем предыдущем вопросе, все еще существует - неправильный тип DbContext назначается свойству _dbContext объекта UnitOfWork.С помощью этого кода я не могу явно указать контекст зависимости сервиса, поскольку он больше не является универсальным интерфейсом.Таким образом, единственное решение состоит в том, чтобы зависеть от службы с ключом 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();

КомментарииКласс обслуживания:

public class CommentsService : ICommentsService
{
        private readonly IUnitOfWork _reconciliationUoW;

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

1 Ответ

0 голосов
/ 23 октября 2018

Вам придется использовать все ключевые службы, если вы не собираетесь делать IDbContext родовым.Есть несколько вещей, которые вы должны учитывать при разработке глубоких уровней вещей в DI.

Примечание: это FAQ на сайте 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 типы одинаково, тогда возникает проблема проектирования.

Концепция 3: Серьезно, потребители интерфейсов не знают о базовых реализациях

В какой-то момент, пытаясь выяснить, как заставить эту работу работать в стеке, вы подумаете: «Эй, я мог бы передать параметр в класс единицы работы и получитькоторый прошел весь путь вниз по стеку разрешения и установил какой-то «контекст», который ... "НЕТ. Это также часто задаваемые вопросы. Вы не можете сделать это, потому что, опять же, вы не должны знать или заботиться о том, какова вся цепочка стека разрешений.Если вам это нужно, вы не используете DI или «инвертирующий контроль» - вы также можете обновить все по-старому.

-

Опять же, это FAQ на сайте 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)
  { /* ... */ }
}

Теперь вы фактически используете систему типов и не нарушаете принцип подстановки Лискова.Ваша жизнь станет проще, и вы получите то, что хотите.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...