Предупреждение : обратите внимание, что мои взгляды изменились, и вы должны посчитать этот совет устаревшим.Пожалуйста, прочтите обновление в конце.
В то время как DI-фреймворки могут управлять временем жизни объектов для вас, а некоторые могут даже утилизировать объекты для вас после того, как вы поработаете с ними, это делает удаление объектов просто слишкомнеявный.Интерфейс IDisposable
создан из-за необходимости детерминированной очистки ресурсов.Поэтому, в контексте DI, я лично хотел бы сделать эту очистку очень явной.Когда вы делаете это явным образом, у вас есть в основном две опции: 1. Сконфигурируйте DI, чтобы он возвращал временные объекты и располагал эти объекты самостоятельно.2. Сконфигурируйте фабрику и проинструктируйте фабрику о создании новых экземпляров.
Я предпочитаю второй подход по сравнению с первым, потому что, особенно при выполнении Dependency Injection, ваш код не так чист, как мог бы.Посмотрите, например, на этот код:
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
стал владельцем ресурса, когда он был введен.
Из-за этого я предпочитаю использовать фабрику.Посмотрите, например, на этот пример:
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
.
Внедрение фабрики - самый явный способ (путь наименьшего удивления) сделать это.Вот почему я предпочитаю этот стиль.Недостатком этого является то, что вам часто нужно определять больше классов (для ваших фабрик), но я лично не против.
RPM1984 запросил более конкретный пример.
У меня не было бы репозитория, реализующего IDisposable
, но у меня была бы единица работы, которая реализует IDisposable
, контролирует / содержит репозитории и имела бы фабрику, которая знает, как создаватьновая единица работ.Имея это в виду, приведенный выше код будет выглядеть следующим образом:
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
. - Конкретная реализация этой фабрики создает конкретный класс
NorthwindUnitOfWork
и внедряетO / RM специфический IDataMapper
класс в нем. 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
Это обновление написано через два года после моего первоначального ответа. За последние два года многое изменилось в том, как я пытаюсь проектировать системы, над которыми я работаю. Хотя это подходило мне в прошлом, я больше не люблю использовать фабричный подход при работе с шаблоном Unit of Work. Вместо этого я просто внедряю экземпляр Unit of Work непосредственно в потребителей. Однако то, насколько это возможно для вас, во многом зависит от того, как спроектирована ваша система. Если вы хотите узнать больше об этом, взгляните на мой новый ответ Stackoverflow: Один DbContext на веб-запрос ... почему?