Как внедрить коллекцию с открытыми универсальными типами для обслуживания с Autofac - PullRequest
1 голос
/ 09 апреля 2019

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

Краткая справка

У меня есть DbWrapperCollection, который я использую для хранения DbWrapper<TInput. TOutput> (поскольку TInput и TOutput будут различаться, коллекция на самом деле представляет собой просто списокуниверсальные «контейнеры», содержащие универсальный объект как объект, а также вход и выход как System.Types - см. мою реализацию ниже)

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

По сути, я хочу сделать следующее:

builder.RegisterType<SaveApplDbWrapper>().As<DbWrapper<AppleDto, SavedAppleDbEntity>>()
    .Named<string>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>().As<IUcHandler<OrangeDto, OrangeDbEntity>>()
    .Named<string>("fruitService");
builder.RegisterType<SaveMelon>().As<IUcHandler<MelonDto, MelonDbEntity>>()
    .Named<string>("appleService");

builder.Register(c => new FruitService(c.ResolveNamed("appleService")))
    .As<IDbMapperService>();

Моя проблема

Как вы можете видеть выше, я специально пропустил ожидаемые параметры типа при вызове ResolveNamed().Это потому, что я, будучи новичком в autofac (и в некоторой степени, в дженериках), я не знаю, существуют ли какие-либо стратегии для добавления списка открытых универсальных DbWrappers и откладывания закрытия моего универсального типа.

Ниже я попытаюсь объяснить, какие стратегии я исследовал для решения этой проблемы, а также мою реализацию

Мои собственные исследования

То, как я это вижуЯ мог бы создать неуниверсальный базовый класс для своих оболочек и сохранить их как этот базовый класс, делегировав ответственность за разрешение исходного универсального типа этому базовому классу, или отказаться от своей идеи коллекции оболочек в пользу конкретных параметров в моем конструкторе службы (скучно- и не совместимо с моей композитной реализацией).

Учитывая популярность составного шаблона, я бы сказал, что я не первый человек с решением, подобным составному шаблону с «общими листами», желающим использовать DI и IoC.

Я планирую использовать мой fruitService следующим образом:

myFruitService.GetDbMapper<MyFruitDto, DbEntityForThatSaidFruit(myOrange);

Служба ищет в своем DbMapperCollection, находит преобразователь с предоставленными аргументами типа и вызывает его реализацию Save ().;

Реализация пока

Для любопытных, вот мои реализации:

DbWrappers:

class SaveApplebWrapper : DbWrapper<TInput, TOutput>
// and plenty more wrapppers for any fruit imaginable

Служба:

public abstract class DbMapperService : IDbMapperService
{
    public IWrapperCollection Wrappers { get; set; }

    protected BestandService(IWrapperCollection wrappers)
    {
        Wrappers = wrappers;
    }

    public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
    {
        return Wrappers.GetWrapper<TInput, TResult>();
    }
}

Мои вспомогательные классы WrapperCollection:

public struct WrapperKey
{
    public static WrapperKey NewWrapperKey <TInput, TResult>()
    {
        return new WrapperKey { InputType = typeof(TInput), ResultType = typeof(TResult) };
    }

    public Type InputType { get; set; }
    public Type ResultType { get; set; }
}

public struct WrapperContainer
{
    public WrapperContainer(object wrapper) : this()
    {
        Wrapper= wrapper;
    }

    public object Wrapper{ get; set; }

    public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
    {
        return Wrapper as DbWrapper<TInput, TResult>;
    }
}

И моя коллекция WrapperCollection:

public class UcWrapperCollection : Dictionary<WrapperKey, WrapperContainer>,
    IDbWrapperCollection
{
    public void AddWrapper<TInput, TResult>(UcHandler<TInput, TResult> handler)
    {
        Add(WrapperKey.NewWrapperKey<TInput, TResult>(), new WrapperContainer(handler));
    }

    public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
    {
        var key = WrapperKey.NewWrapperKey<TInput, TResult>();
        return this[key].GetWrapper<TInput, TResult>();
    }
}

Вопросы, на которые я смотрел без удачи

Некоторые вопросы, на которые я смотрел, ни один из которых не показался мне подходящим (хотя мою проблему потенциально можно решить с помощью универсального делегата, я не думаю, что это очень оптимальное решение для моей проблемы.

  • Внедрение параметров универсального типа с помощьюAutofac
  • Autofac. Как внедрить открытый универсальный делегат в конструкторе
  • Как внедрить фабрику универсальных типов с помощью Autofac
  • Autofac с вложенными открытыми универсальными элементами

1 Ответ

2 голосов
/ 10 апреля 2019

Я не думаю, что вы сможете делать то, что вы хотите. Извините, возможно, не тот ответ, который вам нужен. Я покажу вам, почему, и, возможно, некоторые обходные пути, но наличие произвольной коллекции закрытых генериков, которые не закрываются до тех пор, пока разрешение не станет на самом деле вещью.

Давайте на секунду проигнорируем DI и просто рассмотрим FruitService, который я не вижу в этом вопросе, но который мы видим в использовании здесь:

builder.Register(c => new FruitService(c.ResolveNamed("appleService")))
    .As<IDbMapperService>();

Обратите внимание, мы видим, что FruitService реализует IDbMapperService, потому что он зарегистрирован как этот интерфейс.

Кроме того, мы можем видеть, что FruitService выглядит так, как будто это требует некоторого набора вещей, поскольку в примере регистрации есть две вещи, названные одинаковыми.

builder.RegisterType<SaveApplDbWrapper>().As<DbWrapper<AppleDto, SavedAppleDbEntity>>()
    .Named<string>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>().As<IUcHandler<OrangeDto, OrangeDbEntity>>()
    .Named<string>("fruitService");

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

Чтобы сделать его более конкретным и обойти часть Autofac, которая, на мой взгляд, не имеет отношения к более крупной проблеме, давайте рассмотрим это так:

var wrapper = new[] { CreateWrapper("appleService"), CreateHandler("appleService") };
var service = new FruitService(wrapper);

Предположим, CreateWrapper и CreateHandler оба берут строку и, магическим образом, создают соответствующие типы оболочки / обработчика. Неважно, как это происходит.

Здесь следует рассмотреть две вещи, которые тесно связаны:

  • Какой тип параметра в конструкторе FruitService?
  • Что вы ожидаете CreateWrapper("appleService") и CreateHandler("appleService"), чтобы вернуться?

Здесь я вижу только два варианта.

Вариант 1: Использовать object.

Если нет общего базового класса, то все должно быть object.

public class FruitService : IDBMapperService
{
  private readonly IEnumerable<object> _wrappers;
  public FruitService(IEnumerable<object>wrapper)
  {
    this._wrapper = wrapper;
  }

  public object GetWrapper<TInput, TResult>()
  {
    object foundWrapper = null;
    // Search through the collection using a lot of reflection
    // to find the right wrapper, then
    return foundWrapper;
  }
}

Не ясно, что DbWrapper<TInput, TResult> можно привести к IUcHandler<TInput, TResult>, поэтому вы даже не можете на это полагаться. Там нет общности.

Но скажем, есть общий базовый класс.

Вариант 2: Используйте общий базовый класс

Кажется, уже существует понятие DbWrapper<TInput, TResult>. Важно отметить, что даже если у вас есть этот общий род, как только вы закроете его, это два разных типа. DbWrapper<AppleDto, SavedAppleDbEntity> не может быть преобразовано в DbWrapper<OrangeDto, SavedOrangeDbEntity>. Дженерики больше похожи на «шаблоны классов», чем на базовые классы. Они не одно и то же.

Вы не можете, например, сделать:

var collection = new DbWrapper<,>[]
{
  new DbWrapper<AppleDto, SavedAppleDbEntity>(),
  new DbWrapper<OrangeDto, SavedOrangeDbEntity>()
};

Однако, если у вас общий интерфейс или базовый класс, вы можете сделать ...

var collection = new IDbWrapper[]
{
  new DbWrapper<AppleDto, SavedAppleDbEntity>(),
  new DbWrapper<OrangeDto, SavedOrangeDbEntity>()
};

Но это означало бы, что вы можете переключиться на это и, якобы, использовать общий интерфейс.

public class FruitService : IDBMapperService
{
  private readonly IEnumerable<object> _wrappers;
  public FruitService(IEnumerable<object>wrapper)
  {
    this._wrapper = wrapper;
  }

  public IDbWrapper GetWrapper<TInput, TResult>()
  {
    IDbWrapper foundWrapper = null;
    // Search through the collection using a lot of reflection
    // to find the right wrapper, then
    return foundWrapper;

    // IDbWrapper could expose those `TInput` and `TResult`
    // types as properties on the interface, so the reflection
    // could be super simple and way more straight LINQ.
  }
}

Ваш потребляющий код может просто взять IDbWrapper и вызвать неуниверсальные методы для достижения цели.

Возвращаем его в автофактуру ...

Помните, я упоминал, что ключ к определению того, что должны возвращать методы Create; или что ожидает конструктор FruitService? Тот. Это в пики.

Вы можете зарегистрировать все как ключевые объекты.

builder.RegisterType<SaveApplDbWrapper>()
       .Named<object>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>()
       .Named<object>("fruitService");
builder.RegisterType<SaveMelon>()
       .Named<object>("appleService");

builder
  .Register(c => new FruitService(c.ResolveNamed<IEnumerable<object>>("appleService")))
  .As<IDbMapperService>();

Операции Resolve в Autofac являются методами создания из моего примера. Там нет магии там; это просто создание объектов. Вам все еще нужно знать, какой тип вы хотите предоставить.

Или вы можете использовать общий базовый класс.

builder.RegisterType<SaveApplDbWrapper>()
       .Named<IDbWrapper>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>()
       .Named<IDbWrapper>("fruitService");
builder.RegisterType<SaveMelon>()
       .Named<IDbWrapper>("appleService");

builder
  .Register(c => new FruitService(c.ResolveNamed<IEnumerable<IDbWrapper>>("appleService")))
  .As<IDbMapperService>();

Если вы не возражаете смешивать систему DI с FruitService, вы можете сделать что-то вроде этого:

public class FruitService
{
  private readonly ILifetimeScope _scope;
  public FruitService(ILifetimeScope scope)
  {
    this._scope = scope;
  }

  public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
  {
    var type = typeof(DbWrapper<TInput, TResult>);
    var wrapper = this._lifetimeScope.Resolve(type);
    return wrapper;
  }
}

Вы должны были бы зарегистрировать вещи без их имен и As a DbWrapper, но это сработало бы, если бы все основывалось на этом.

builder.RegisterType<SaveApplDbWrapper>()
       .As<DbWrapper<AppleDto, SavedAppleDbEntity>>();
// Must be DbWrapper, can also be other things...
builder.RegisterType<SaveOrangeDbWrapper>()
       .As<IUcHandler<OrangeDto, OrangeDbEntity>>()
       .As<DbWrapper<OrangeDto, OrangeDbEntity>>();
builder.RegisterType<SaveMelon>()
       .As<DbWrapper<MelonDto, MelonDbEntity>>()
       .As<IUcHandler<MelonDto, MelonDbEntity>>();

builder.RegisterType<FruitService>()
       .As<IDbMapperService>();

При разрешении IDbMapperService конструктор FruitService получит ссылку на область действия времени жизни, из которой он был разрешен. Все обертки будут разрешены из той же области.

Людям, как правило, не нравится смешивать ссылки IoC в своем коде, как это, но это единственный способ, как я вижу, вы избавитесь от необходимости возиться с отражением или повышением или понижением во всем.

Удачи!

...