如何使用依赖注入来处理资源

时间:2010-12-19 15:37:54

标签: entity-framework dependency-injection structuremap dispose

我正在使用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方法吗?

3 个答案:

答案 0 :(得分:22)

警告:请注意我的观点已经改变,您应该考虑跟进建议过时。请在最后阅读更新。


虽然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,因为它在{{1}内创建和处理资源方法。

注入工厂是最明确的方式(最少惊喜的路径)。这就是为什么我更喜欢这种风格。缺点是你经常需要定义更多的课程(对你的工厂来说),但我个人并不介意。

<小时/> RPM1984要求一个更具体的例子。

我不会拥有存储库实现Do,而是拥有一个实现IDisposable的工作单元,控制/包含存储库并拥有一个知道如何创建新工作单元的工厂。考虑到这一点,上面的代码看起来像这样:

IDisposable

在我使用的设计中,并描述了here,我使用了一个具体的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或实体框架)。在sumary中,设计如下:

  1. 在客户端注入IDataMapper
  2. 该工厂的特定实现创建了一个具体的INorthwindUnitOfWorkFactory类,并在其中注入了一个O / RM特定的NorthwindUnitOfWork类。
  3. IDataMapper实际上是NorthwindUnitOfWork周围的类型安全包装,IDataMapper请求NorthwindUnitOfWork存储库并转发请求以提交更改并将其置于映射器。
  4. IDataMapper返回IDataMapper个类,并且存储库实现Repository<T>以允许客户端在存储库上使用LINQ。
  5. IQueryable<T>的具体实现包含对O / RM特定工作单元的引用(例如EF的IDataMapper)。因此,ObjectContext必须实施IDataMapper
  6. 这导致以下设计:

    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. } 是一个具体类,包含特定存储库的属性,例如NorthwindUnitOfWorkCustomers等:

    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

    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

    您可以找到有关此模型的更多信息here

    2012年12月更新

    这是我原来回答两年后写的更新。过去两年我尝试设计我正在研究的系统的方式发生了很大的变化。虽然它过去适合我,但我不喜欢在处理工作单元模式时再使用工厂方法。相反,我只是直接向消费​​者注入一个工作单元实例。然而,这种设计是否适合您,取决于您的系统设计方式。如果您想了解更多相关信息,请查看我的新Stackoverflow答案:One DbContext per web request…why?

答案 1 :(得分:11)

如果你想做对,我会建议你做一些改变:

1 - 存储库中没有数据上下文的私有实例。如果您使用多个存储库,那么您将最终得到多个上下文。

2 - 要解决上述问题 - 将背景包装在工作单元中。通过ctor:public MyRepository(IUnitOfWork uow)

将工作单元传递到存储库

3 - 使工作单元具有IDisposable。工作单元应在请求开始时“新建”,因此应在请求完成时予以处理。存储库不应该实现IDisposable,因为它不直接使用资源 - 它只是减轻它们。 DataContext / Unit of Work应该实现IDispoable。

4 - 假设您使用的是Web应用程序,则无需显式调用dispose - 我重复一遍,您不需要明确调用您的dispose方法。 StructureMap有一个名为HttpContextBuildPolicy.DisposeAndClearAll();的方法。这样做是在任何实现IDisposable的HTTP范围对象上调用“Dispose”方法。将此调用贴在Application_EndRequest(Global.asax)中。此外 - 我相信有一个更新的方法,称为ReleaseAllHttpScopedObjects或其他东西 - 不记得名称。

答案 2 :(得分:0)

您可以像这样声明IMyRepository,而不是将Dispose添加到IMyRepository:

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

这样,您确保所有存储库有时会调用Dispose,并且您可以在Repository对象上使用C#“using”模式:

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