如何使用依赖注入来处理资源
如何使用依赖注入来处理资源
我正在使用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方法?
如果你想做得正确,我建议你做一些改变:
1-不要在仓库中有数据上下文的私有实例。如果你正在使用多个仓库,则最终会有多个上下文。
2-为了解决上述问题-在工作单元中包装上下文。通过ctor将工作单元传递给仓库:public MyRepository(IUnitOfWork uow)
3-让工作单元实现IDisposable。工作单元应在请求开始时“newed up”,因此应在请求完成时进行处置。仓库不应实现IDisposable,因为它不直接使用资源-它只是在减轻资源。DataContext /工作单元应实现IDispoable。
4-假设您正在使用Web应用程序,则不需要显式调用dispose-我重复一遍,您不需要显式调用dispose方法。StructureMap有一个名为HttpContextBuildPolicy.DisposeAndClearAll();
的方法。它的作用是调用实现IDisposable的任何HTTP范围对象的“Dispose”方法。将此调用放入Application_EndRequest
(Global.asax)。此外-我相信还有一种更新的方法,称为ReleaseAllHttpScopedObjects或其他名称-无法记住名称。
警告:请注意,我的观点已经改变,您应该认为以下建议已过时。请查看此答案以获取更新后的观点:https://stackoverflow.com/a/30287923/264697
虽然 DI 框架可以为您管理对象的生命周期,一些框架甚至可以在您使用完对象后为您销毁对象,但这使得对象的处理过于隐式化。因此,IDisposable
接口的创建是因为有必要对资源进行确定性清理。因此,在 DI 上下文中,我个人喜欢使这种清理非常明确化。当您明确指定时,基本上有两个选择: 1. 配置 DI 来返回瞬态对象,并自己处理这些对象的释放。2. 配置工厂并指示工厂创建新实例。
我更喜欢第二种方法,因为特别是在进行依赖注入时,您的代码并不像它本来应该那么干净。例如,请参考以下代码:
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
类包装了一个作为底层 LINQ 提供程序的通道 (比如LINQ to SQL 或 Entity Framework) 的 IDataMapper
。总之,该设计如下:
- 在客户端中注入一个
INorthwindUnitOfWorkFactory
。 - 那个工厂的特定实现创建一个具体的
NorthwindUnitOfWork
类,并将 O/RM 特定的IDataMapper
类注入其中。 NorthwindUnitOfWork
实际上是一个类型安全的IDataMapper
包装器,NorthwindUnitOfWork
向IDataMapper
请求存储库,并将请求转发到提交更改和处理程序的映射器。IDataMapper
返回Repository
类,并且存储库实现IQueryable
,允许客户端在存储库上使用 LINQ。IDataMapper
的特定实现将引用传递给 O/RM 特定的工作单元(例如 EF 的ObjectContext
)。因此,IDataMapper
必须实现IDisposable
。
这导致以下设计:
public interface INorthwindUnitOfWorkFactory { NorthwindUnitOfWork CreateNew(); } public interface IDataMapper : IDisposable { RepositoryGetRepository () where T : class; void Save(); } public abstract class Repository : IQueryable where T : class { private readonly IQueryable query; protected Repository(IQueryable query) { this.query = query; } public abstract void InsertOnSubmit(T entity); public abstract void DeleteOnSubmit(T entity); // IQueryable 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 RepositoryCustomers { get { return this.mapper.GetRepository (); } } 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 RepositoryGetRepository () where T : class { string setName = this.GetEntitySetName (); var query = this.context.CreateQuery (setName); return new EntityRepository (query, setName); } private string GetEntitySetName () { 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 : Repository where T : class { private readonly ObjectQuery query; private readonly string entitySetName; public EntityRepository(ObjectQuery 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年12月更新
这是我原始答案两年后的一个更新。过去两年中,我在处理工作单元模式时尝试设计系统的方式发生了许多变化。虽然过去很适合我,但现在我不再喜欢使用工厂方法来处理工作单元模式。相反,我直接将工作单元实例注入到消费者中。然而,是否可行取决于系统设计的方式。如果您想了解更多信息,请查看我最新的Stackoverflow答案:为什么每个Web请求一个DbContext?