4

I recently created a solution and thought I would try out the DryIoC container to handle dependency injection. Now, as with many other DI solutions that I have used, the default scope for object reuse is transient. This however seems to be posing a problem for the implementation of the repository pattern that I am using since DryIoC (and many other solutions) cannot register a binding as transient if the referenced class implements IDisposable. As a result, I have temporarily resorted to registering my repositories with Reuse.Singleton. This is definitely a code smell for me, so I was hoping that someone might have some advice on how to avoid this situation - it might be that I am doing a poor job of creating a repository for example.

Here is the code that I use to create the IoC container:

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

...and an example repository implementation:

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
}
LuckyLikey
  • 3,504
  • 1
  • 31
  • 54
spuriousGeek
  • 131
  • 1
  • 3
  • 9

2 Answers2

4

According to the documentation, you have 3 options:

  1. Disallow to register disposable transient service. The default DryIoc behavior.

     container.Register<X>(); // will throw exception  
    
  2. Allow to register disposable transient, but delegate the responsibility of disposing the service to container User.

     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. To track (store) disposable transient dependency in its owner reuse scope (if any), or to track resolved disposable transient in current Open Scope (if any).

     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
    

As you can see above, the default behavior expects you to explicitly dispose when using transient lifestyle.

But they left out the 4th option, which is to find another DI container. I have never used DryIoC, but this seems like too much to worry about that you don't have to with other containers. Normally, choosing the correct lifetime is what determines when to dispose an instance.

dadhi
  • 4,807
  • 19
  • 25
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • 1
    Thank you very much for the information! Could you provide a couple of examples of DI containers that can deal with this out-of-the-box? The reason that I used DryIoC is because it has great performance and meets my functional requirements. – spuriousGeek Sep 26 '17 at 14:55
  • There is a list of .NET containers [here](https://github.com/danielpalme/IocPerformance), but do note that performance is rarely the best gauge of what container to use. Most have similar features, it is just a matter of finding one that is still being actively supported that suits your needs. – NightOwl888 Sep 26 '17 at 15:49
  • fixed the link in the answer – dadhi Feb 16 '22 at 06:49
4

The documentation here explains why disposable transient is the problem and why DryIoc default behavior was selected this way. Basically, the behavior is to inform you about the problem and not just silently go with it.

Regarding other containers, there is no strong preference to particular disposable transients handling. Here is discussion related to Microsoft.Extensions.DependencyInjection with participation of Autofac, StructureMap and other containers developers.

Btw, DryIoc error message contains the tip how to opt-in the problem.

dadhi
  • 4,807
  • 19
  • 25