Избегайте одноэлементного репозитория (DryIoc) при использовании внедрения зависимостей.

Недавно я создал решение и решил попробовать контейнер DryIoC для обработки внедрения зависимостей. Теперь, как и во многих других решениях DI, которые я использовал, областью повторного использования объекта по умолчанию является transient. Однако это, похоже, создает проблему для реализации шаблона репозитория, который я использую, поскольку DryIoC (и многие другие решения) не может зарегистрировать привязку как переходную, если класс, на который ссылаются, реализует IDisposable. В результате я временно прибегнул к регистрации своих репозиториев с помощью Reuse.Singleton. Это определенно запах кода для меня, поэтому я надеялся, что кто-то может дать совет, как избежать этой ситуации - может быть, например, я плохо справляюсь с созданием репозитория.

Вот код, который я использую для создания контейнера IoC:

private static Container ConstructNewContainer()
{
    var container = new Container(Rules.Default);
    container.Register(Made.Of(() => SettingsFactory.CreateSettings()));    
    container.Register<IRepository<tblMailMessage>, MailMessageRepository>(Reuse.Singleton);
    container.Register<IRepository<ProcessedMailMessages>, ProcessedMailMessageRepository>(Reuse.Singleton);
    container.Register<IParser, EmailParser>();
    container.Register<IMonitor, DatabaseMonitor>();
    return container;
}

...и пример реализации репозитория:

public interface IRepository<T>
{
    void Insert(T objectToInsert);

    void Delete(int id);

    void Update(T objectToUpdate);

    void Save();

    T GetById(long id);

    IEnumerable<T> Get();

    T Last();

    bool Exists(int id);
}

public class MailMessageRepository : IRepository<tblMailMessage>, IDisposable
{
    private bool _disposed;
    private readonly CoreDataModel _model;

    public MailMessageRepository()
    {
        _model = new CoreDataModel();
    }

    public void Delete(int id)
    {
        var objectToDelete = _model.tblMailMessages.Find(id);
        if (objectToDelete != null) _model.tblMailMessages.Remove(objectToDelete);
    }

    public void Update(tblMailMessage objectToUpdate) => _model.Entry(objectToUpdate).State = EntityState.Modified;

    public void Save() => _model.SaveChanges();

    public IEnumerable<tblMailMessage> Get() => _model.tblMailMessages.ToList();

    public tblMailMessage Last() => _model.tblMailMessages.OrderByDescending(x => x.DateSubmitted).FirstOrDefault();

    public bool Exists(int id) => _model.tblMailMessages.SingleOrDefault(x => x.MailMessageID == id) != null;

    public void Insert(tblMailMessage objectToInsert) => _model.tblMailMessages.Add(objectToInsert);

    public tblMailMessage GetById(long id) => _model.tblMailMessages.SingleOrDefault(x => x.MailMessageID == id);

    #region Dispose

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (!disposing)
            {
                _model.Dispose();
            }
        }

        _disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    #endregion
}

person spuriousGeek    schedule 26.09.2017    source источник


Ответы (2)


Согласно документации, у вас есть 3 варианта:

  1. Запретить регистрацию одноразовой переходной службы. Поведение DryIoc по умолчанию.

    container.Register<X>(); // will throw exception  
    
  2. Разрешить регистрацию одноразового переходного процесса, но делегировать ответственность за удаление службы пользователю контейнера.

    container.Register<X>(setup: Setup.With(allowDisposableTransient: true));
    
    // or allow globally for all container registrations:
    var container = new Container(rules => rules.WithoutThrowOnRegisteringDisposableTransient());
    
    container.Register<X>(); // works, but dispose is up to User
    
  3. Для отслеживания (сохранения) одноразовой временной зависимости в области повторного использования ее владельца (если есть) или для отслеживания разрешенной одноразовой временной зависимости в текущей открытой области (если есть).

    container.Register<X>(setup: Setup.With(trackDisposableTransient: true));
    
    // or track globally for all container registrations:
    var container = new Container(rules => rules.WithTrackingDisposableTransients());
    
    // will be tracked in XUser parent in singleton scope and disposed with container as all singletons
    container.Register<XUser>(Reuse.Singleton);
    container.Register<X>();  
    
    // or tracking in open scope
    using (var scope = container.OpenScope())
        scope.Resolve<X>; // will be disposed on exiting of using block
    

Как вы можете видеть выше, поведение по умолчанию предполагает, что вы явно утилизируете при использовании временного образа жизни.

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

person NightOwl888    schedule 26.09.2017
comment
Большое спасибо за информацию! Не могли бы вы привести пару примеров контейнеров DI, которые могут справиться с этим «из коробки»? Причина, по которой я использовал DryIoC, заключается в том, что он обладает отличной производительностью и соответствует моим функциональным требованиям. - person spuriousGeek; 26.09.2017
comment
здесь есть список контейнеров .NET, но обратите внимание, что производительность редко является лучшим показателем того, что контейнер для использования. Большинство из них имеют схожие функции, нужно просто найти тот, который все еще активно поддерживается и соответствует вашим потребностям. - person NightOwl888; 26.09.2017

Документация здесь объясняет, почему одноразовый переходный процесс является проблемой и почему поведение DryIoc по умолчанию было выбрано таким образом. По сути, поведение состоит в том, чтобы информировать вас о проблеме, а не просто молча решать ее.

Что касается других контейнеров, нет особых предпочтений в отношении обработки конкретных одноразовых переходных процессов. Вот обсуждение, связанное с Microsoft.Extensions.DependencyInjection с участием Autofac, StructureMap и других разработчики контейнеров.

Кстати, сообщение об ошибке DryIoc содержит подсказку, как решить проблему.

person dadhi    schedule 28.09.2017