Подтвердить что ты не робот

Как распоряжаться ресурсами с помощью инъекции зависимостей

Я использую StructureMap для разрешения ссылок на мой класс репозитория. Мой интерфейс репозитория реализует IDisposable, например

public interface IMyRepository : IDisposable
{
  SomeClass GetById(int id);
}

Реализация интерфейса с использованием Entity Framework:

public MyRepository : IMyRepository
{
    private MyDbContext _dbContext;

    public MyDbContext()
    {
        _dbContext = new MyDbContext();
    }

    public SomeClass GetById(int id)
    {
        var query = from x in _dbContext
                    where x.Id = id
                    select x;
        return x.FirstOrDefault();
    }

    public void Dispose()
    {
        _dbContext.Dispose();
    }
}

В любом случае, как упоминалось, я использую StructureMap для разрешения IMyRepository. Итак, , когда, где и как следует вызвать метод dispose?

4b9b3361

Ответ 1

Предупреждение: обратите внимание, что мои взгляды изменились, и вы должны подумать о том, что рекомендации followimg устарели. Пожалуйста, прочитайте обновление в конце.


В то время как базы данных DI могут управлять временем жизни объектов для вас, а некоторые могут даже уничтожать объекты для вас после того, как вы закончите с ними, это делает удаление объектов слишком скрытым. Интерфейс IDisposable создается потому, что существует необходимость в детерминированной очистке ресурсов. Поэтому, в контексте DI, я лично хотел бы сделать эту очистку очень явной. Когда вы делаете это явным, у вас есть в основном два варианта: 1. Настройте DI, чтобы возвращать переходные объекты и самостоятельно распоряжаться этими объектами. 2. Настройте factory и проинструктируйте factory создать новые экземпляры.

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

public sealed class Client : IDisposable
{
    private readonly IDependency dependency;

    public Client(IDependency dependency)
    {
        this. dependency = dependency;
    }

    public void Do()
    {
        this.dependency.DoSomething();
    }

    public Dispose()
    {
        this.dependency.Dispose();
    }
}

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

Из-за этого я одобряю использование factory. Найдите пример в этом примере:

public sealed class Client
{
    private readonly IDependencyFactory factory;

    public Client(IDependencyFactory factory)
    {
        this.factory = factory;
    }

    public void Do()
    {
        using (var dependency = this.factory.CreateNew())
        {
            dependency.DoSomething();
        }
    }
}

Этот пример имеет то же самое поведение, что и в предыдущем примере, но посмотрите, как класс Client больше не должен реализовывать IDisposable, потому что он создает и распределяет ресурс в методе Do.

Инъекция factory является наиболее явным способом (путь наименьшего удивления) для этого. Вот почему я предпочитаю этот стиль. Недостатком этого является то, что вам часто приходится определять больше классов (для ваших заводов), но я лично не против.

<ч/" > RPM1984 попросил более конкретный пример.

У меня не было бы реализации репозитория IDisposable, но у меня есть Единица работы, которая реализует IDisposable, управляет/содержит репозитории и имеет factory, который знает, как создать новую единицу работ. Имея это в виду, приведенный выше код будет выглядеть так:

public sealed class Client
{
    private readonly INorthwindUnitOfWorkFactory factory;

    public Client(INorthwindUnitOfWorkFactory factory)
    {
        this.factory = factory;
    }

    public void Do()
    {
        using (NorthwindUnitOfWork db = 
            this.factory.CreateNew())
        {
            // 'Customers' is a repository.
            var customer = db.Customers.GetById(1);

            customer.Name = ".NET Junkie";

            db.SubmitChanges();
        }
    }
}

В проекте, который я использую, и описал здесь, я использую конкретный класс NorthwindUnitOfWork, который обертывает IDataMapper, который шлюз к базовому провайдеру LINQ (например, LINQ to SQL или Entity Framework). В сущности, дизайн выглядит следующим образом:

  • В клиенте вводится INorthwindUnitOfWorkFactory.
  • Конкретная реализация этого factory создает конкретный класс NorthwindUnitOfWork и вводит в него специальный класс IDataMapper O/RM.
  • NorthwindUnitOfWork на самом деле представляет собой безопасную для типа оболочку оболочку вокруг IDataMapper, а NorthwindUnitOfWork запрашивает IDataMapper для репозиториев и пересылает запросы на отправку изменений и распоряжается картографом.
  • IDataMapper возвращает Repository<T> классы, а репозиторий реализует IQueryable<T>, чтобы клиент мог использовать LINQ через репозиторий.
  • Конкретная реализация IDataMapper содержит ссылку на конкретную единицу работы O/RM (например, EF ObjectContext). По этой причине IDataMapper должен реализовать IDisposable.

Это приводит к следующему дизайну:

public interface INorthwindUnitOfWorkFactory
{
    NorthwindUnitOfWork CreateNew();
}

public interface IDataMapper : IDisposable
{
    Repository<T> GetRepository<T>() where T : class;

    void Save();
}

public abstract class Repository<T> : IQueryable<T>
    where T : class
{
    private readonly IQueryable<T> query;

    protected Repository(IQueryable<T> query)
    {
        this.query = query;
    }

    public abstract void InsertOnSubmit(T entity);

    public abstract void DeleteOnSubmit(T entity);

    // IQueryable<T> members omitted.
}

NorthwindUnitOfWork - это конкретный класс, который содержит свойства для конкретных репозиториев, таких как Customers, Orders и т.д.:

public sealed class NorthwindUnitOfWork : IDisposable 
{
    private readonly IDataMapper mapper;

    public NorthwindUnitOfWork(IDataMapper mapper)
    {
        this.mapper = mapper;
    }

    // Repository properties here:    
    public Repository<Customer> Customers
    {
        get { return this.mapper.GetRepository<Customer>(); }
    }

    public void Dispose()
    {
        this.mapper.Dispose();
    }
}

Остается конкретная реализация INorthwindUnitOfWorkFactory и конкретная реализация IDataMapper. Здесь один для Entity Framework:

public class EntityFrameworkNorthwindUnitOfWorkFactory
    : INorthwindUnitOfWorkFactory
{
    public NorthwindUnitOfWork CreateNew()
    {
        var db = new ObjectContext("name=NorthwindEntities");
        db.DefaultContainerName = "NorthwindEntities";
        var mapper = new EntityFrameworkDataMapper(db);
        return new NorthwindUnitOfWork(mapper);
    }
}

И EntityFrameworkDataMapper:

public sealed class EntityFrameworkDataMapper : IDataMapper
{
    private readonly ObjectContext context;

    public EntityFrameworkDataMapper(ObjectContext context)
    {
        this.context = context;
    }

    public void Save()
    {
        this.context.SaveChanges();
    }

    public void Dispose()
    {
        this.context.Dispose();
    }

    public Repository<T> GetRepository<T>() where T : class
    {
        string setName = this.GetEntitySetName<T>();

        var query = this.context.CreateQuery<T>(setName);
        return new EntityRepository<T>(query, setName);
    }

    private string GetEntitySetName<T>()
    {
        EntityContainer container =
            this.context.MetadataWorkspace.GetEntityContainer(
            this.context.DefaultContainerName, DataSpace.CSpace);

        return (
            from item in container.BaseEntitySets
            where item.ElementType.Name == typeof(T).Name
            select item.Name).First();
    }

    private sealed class EntityRepository<T>
        : Repository<T> where T : class
    {
        private readonly ObjectQuery<T> query;
        private readonly string entitySetName;

        public EntityRepository(ObjectQuery<T> query,
            string entitySetName) : base(query)
        {
            this.query = query;
            this.entitySetName = entitySetName;
        }

        public override void InsertOnSubmit(T entity)
        {
            this.query.Context.AddObject(entitySetName, entity);
        }

        public override void DeleteOnSubmit(T entity)
        {
            this.query.Context.DeleteObject(entity);
        }
    }
}

Дополнительную информацию об этой модели можно найти здесь.

ОБНОВЛЕНИЕ Декабрь 2012

Это обновление, написанное через два года после моего первоначального ответа. За последние два года многое изменилось в том, как я пытаюсь разработать системы, над которыми я работаю. Хотя мне это и нравилось в прошлом, мне больше не нравится использовать подход factory при работе с шаблоном Unit of Work. Вместо этого я просто вставляю экземпляр Единицы работы в потребителей напрямую. Независимо от того, что этот проект является для вас выполнимым, многое зависит от того, как ваша система разработана. Если вы хотите больше узнать об этом, пожалуйста, взгляните на этот новый ответ MyStackoverflow: Один DbContext для веб-запроса... почему?

Ответ 2

Если вы хотите это исправить, я бы посоветовал пару изменений:

1 - Не иметь частных экземпляров контекста данных в репозитории. Если вы работаете с несколькими репозиториями, тогда вы получите несколько контекстов.

2 - Чтобы решить вышеперечисленное, оберните контекст в Единице работы. Передайте единицу работы в Хранилища через ctor: public MyRepository(IUnitOfWork uow)

3 - Сделать единицу работы реализацией IDisposable. Когда запрос начинается, отдел работы должен быть "обновлен" и, следовательно, должен быть удален после завершения запроса. Репозиторий не должен реализовывать IDisposable, так как он не работает напрямую с ресурсами - это просто смягчает их. DataContext/Unit of Work должен реализовывать IDispoable.

4 - Предполагая, что вы используете веб-приложение, вам не нужно явно вызывать dispose - я repeat, вам не нужно явно вызывать метод dispose. StructureMap имеет метод под названием HttpContextBuildPolicy.DisposeAndClearAll();. То, что это делает, вызывает метод "Dispose" для любых объектов с областью действия HTTP, реализующих IDisposable. Вставьте этот вызов в Application_EndRequest (Global.asax). Кроме того, я считаю, что есть обновленный метод, называемый ReleaseAllHttpScopedObjects или что-то - не может запомнить имя.

Ответ 3

Вместо добавления Dispose в IMyRepository вы можете объявить IMyRepository следующим образом:

public interface IMyRepository: IDisposable
{
  SomeClass GetById(int id);
} 

Таким образом, вы гарантируете, что все репозитории будут иногда вызывать Dispose, и вы можете использовать шаблон С# "using" в объекте репозитория:

using (IMyRepository rep = GetMyRepository(...))
{
   ... do some work with rep
}