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

Fix*_*xer 26 structuremap dispose entity-framework dependency-injection

我正在使用StructureMap来解析对我的存储库类的引用.我的存储库接口实现了IDisposable,例如

public interface IMyRepository : IDisposable
{
  SomeClass GetById(int id);
}
Run Code Online (Sandbox Code Playgroud)

使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

无论如何提到我正在使用StructureMap来解析IMyRepository.那么何时,何地以及如何调用我的处理方法?

Ste*_*ven 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然此代码明确地处理了依赖项,但它可能会引起读者的注意,因为资源通常只应由资源所有者处理.显然,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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

此示例与前一个示例具有完全相同的行为,但是看看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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在我使用的设计中,并在此处描述,我使用了一个具体的NorthwindUnitOfWork类,它包装了一个IDataMapper底层LINQ提供程序的网关(例如LINQ to SQL或Entity Framework).在sumary中,设计如下:

  1. An INorthwindUnitOfWorkFactory注入客户端.
  2. 该工厂的特定实现创建了一个具体NorthwindUnitOfWork类,并在其中注入一个O/RM特定的IDataMapper类.
  3. NorthwindUnitOfWork事实上,它是一个类型安全的包装器IDataMapper,它NorthwindUnitOfWork请求IDataMapper存储库并转发请求以提交更改并将其部署到映射器.
  4. IDataMapper收益Repository<T>类和库工具IQueryable<T>允许客户端使用LINQ在库中.
  5. 该具体实现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.
}
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

剩下的是具体实施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);
    }
}
Run Code Online (Sandbox Code Playgroud)

而且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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以在此处找到有关此模型的更多信息.

更新2012年12月

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


RPM*_*984 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或其他东西 - 不记得名字.