本文介绍了如何通过依赖注入来处理资源的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

 公共接口IMyRepository:IDisposable 
{
SomeClass GetById(int id );
}

使用实体框架的接口实现:

  public MyRepository:IMyRepository 
{
private MyDbContext _dbContext;

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

public SomeClass GetById(int id)
{
var query = from _dbContext
中的x,其中x.Id = id
选择x;
return x.FirstOrDefault();
}

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

无论如何,我正在使用StructureMap解析IMyRepository 。所以何时,何地以及如何应该调用我的dispose方法?

解决方案

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






虽然DI框架可以为您管理对象的生命周期,但有些甚至可以处置对象对它们使用完后,对于您来说,这会使对象处置变得过于隐含。之所以创建 IDisposable 接口,是因为需要确定性地清理资源。因此,在DI的背景下,我个人希望使此清理非常明确。明确说明后,基本上有两种选择:1.配置DI以返回瞬态对象并自行处理这些对象。 2.配置一个工厂,并指示工厂创建新实例。



与第一种方法相比,我更喜欢第二种方法,因为特别是在进行依赖注入时,您的代码不是尽可能干净。例如看下面的代码:

 公共密封类客户端:IDisposable 
{
private readonly IDependency依赖;

公共客户端(IDependency依赖)
{
此。依赖=依赖;
}

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

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

虽然此代码明确处理了依赖项,但它可能会引发有些读者对此感到有些惊讶,因为资源通常只应由资源所有者处置。显然, Client 在注入资源时成为资源的所有者。



因此,我赞成使用工厂。看看这个例子:

 公共密封类客户端
{
private readonly IDependencyFactory factory;

公共客户端(IDependencyFactory工厂)
{
this.factory = factory;
}

public void Do()
{
使用(var依赖= this.factory.CreateNew())
{
依赖。做一点事();
}
}
}

此示例完全相同行为与前面的示例相同,但是请参见 Client 类如何不再实现 IDisposable ,因为它创建了并以 Do 方法处理资源。



注入工厂是最明确的方法(路径最少)做到这一点。这就是为什么我更喜欢这种风格。缺点是您经常需要为工厂定义更多的类,但是我个人不介意。





RPM1984要求提供一个更具体的示例。



我没有存储库实现 IDisposable ,但是有一个工作单元实现 IDisposable ,控制/包含存储库并拥有知道如何创建新工作单元的工厂。考虑到这一点,上面的代码将如下所示:

 公共密封类客户
{
私有只读INorthwindUnitOfWorkFactory工厂;

公共客户端(INorthwindUnitOfWorkFactory工厂)
{
this.factory = factory;
}

public void Do()
{
using(NorthwindUnitOfWork db =
this.factory.CreateNew())
{
//客户是一个存储库。
var customer = db.Customers.GetById(1);

customer.Name = .NET垃圾;

db.SubmitChanges();
}
}
}

在我使用的设计中,并在中进行了描述,我使用了具体的 NorthwindUnitOfWork 类,该类包装 IDataMapper ,后者是通往基础LINQ提供程序(例如LINQ to SQL或Entity Framework)的网关。总之,设计如下:


  1. INorthwindUnitOfWorkFactory 注入到客户端。

  2. 该工厂的特定实现会创建具体的 NorthwindUnitOfWork 类,并注入特定于O / RM的 IliMapper 类。

  3. NorthwindUnitOfWork 实际上是围绕<$的类型安全包装器c $ c> IDataMapper 和 NorthwindUnitOfWork 请求 IDataMapper 获取存储库并将请求转发给

  4. IDataMapper 返回 Repository< T> 类和存储库实现 IQueryable< T> ,以允许客户端在存储库上使用LINQ。

  5. IDataMapper 包含对O / RM特定工作单元的引用(例如EF的 ObjectContext )。因此, IDataMapper 必须实现 IDisposable

这将导致以下设计:

 公共接口INorthwindUnitOfWorkFactory 
{
NorthwindUnitOfWork CreateNew();
}

公共接口IDataMapper:IDisposable
{
信息库< T> GetRepository< T>(),其中T:class;

void Save();
}

公共抽象类存储库< T> :IQueryable< T>
,其中T:类
{
private readonly IQueryable< T>查询

受保护的存储库(IQueryable< T>查询)
{
this.query = query;
}

public abstract void InsertOnSubmit(T实体);

public abstract void DeleteOnSubmit(T实体);

// IQueryable< T>成员省略。
}

NorthwindUnitOfWork 是一个包含特定存储库属性的具体类,例如 Customers Orders 等:

 公共密封类NorthwindUnitOfWork:IDisposable 
{
私有只读IDataMapper映射器;

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

//此处的存储库属性:
public Repository< Customer>客户
{
得到{返回this.mapper.GetRepository< Customer>(); }
}

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

剩下的是<$ c的具体实现$ c> INorthwindUnitOfWorkFactory 和 IDataMapper 的具体实现。这是一个用于Entity Framework的实体: CreateNew()
{
var db = new ObjectContext( name = NorthwindEntities);
db.DefaultContainerName = NorthwindEntities;
var mapper = new EntityFrameworkDataMapper(db);
返回新的NorthwindUnitOfWork(mapper);
}
}

EntityFrameworkDataMapper

 公共密封类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();
}

公共存储库< T> GetRepository< T>(),其中T:类
{
string setName = this.GetEntitySetName< T>();

var查询= this.context.CreateQuery< T>(setName);
返回新的EntityRepository< T>(query,setName);
}

私有字符串GetEntitySetName< T>()
{
EntityContainer容器=
this.context.MetadataWorkspace.GetEntityContainer(
this .context.DefaultContainerName,DataSpace.CSpace);

return(
从container.BaseEntitySets
中的item.ElementType.Name == typeof(T).Name
select item.Name).First( );
}

私有密封类EntityRepository< T>
:存储库T T:class
{
private readonly ObjectQuery< T>查询
私有只读字符串entitySetName;

public EntityRepository(ObjectQuery< T>查询,
字符串entitySetName):base(查询)
{
this.query = query;
this.entitySetName = entitySetName;
}

公共重写void InsertOnSubmit(T实体)
{
this.query.Context.AddObject(entitySetName,entity);
}

公共重写void DeleteOnSubmit(T实体)
{
this.query.Context.DeleteObject(entity);
}
}
}

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



更新2012年12月



这是我最初回答两年后写的更新。最近两年,我尝试设计正在使用的系统的方式发生了很大变化。尽管过去适合我,但在处理工作单位模式时,我不再喜欢使用工厂方法。相反,我只是直接将一个工作单元实例直接注入使用者。但是,这种设计对您来说是否可行,在很大程度上取决于系统的设计方式。如果您想了解更多有关此内容的信息,请查看我的最新Stackoverflow答案:


I'm using StructureMap to resolve references to my repository class. My repository interface implements IDisposable, e.g.

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

An implementation of the interface using 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();
    }
}

Anyway as mentioned I'm using StructureMap to resolve IMyRepository. So when, where and how should I call my dispose method?

解决方案

Warning: please note that my views have changed, and you should consider the followimg advise outdated. Please read the update at the end.


While DI frameworks can manage lifetime of objects for you and some could even dispose objects for you after you're done using with them, it makes object disposal just too implicit. The IDisposable interface is created because there was the need of deterministic clean-up of resources. Therefore, in the context of DI, I personally like to make this clean-up very explicit. When you make it explicit, you've got basically two options: 1. Configure the DI to return transient objects and dispose these objects yourself. 2. Configure a factory and instruct the factory to create new instances.

I favor the second approach over the first, because especially when doing Dependency Injection, your code isn't as clean as it could be. Look for instance at this code:

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();
    }
}

While this code explicitly disposes the dependency, it could raise some eyebrows to readers, because resources should normally only be disposed by the owner of the resource. Apparently, the Client became the owner of the resource, when it was injected.

Because of this, I favor the use of a factory. Look for instance at this example:

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();
        }
    }
}

This example has the exact same behavior as the previous example, but see how the Client class doesn't have to implement IDisposable anymore, because it creates and disposes the resource within the Do method.

Injecting a factory is the most explicit way (the path of least surprise) to do this. That's why I prefer this style. Downside of this is that you often need to define more classes (for your factories), but I personally don't mind.


RPM1984 asked for a more concrete example.

I would not have the repository implement IDisposable, but have a Unit of Work that implements IDisposable, controls/contains repositories and have a factory that knows how to create new unit of works. With that in mind, the above code would look like this:

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();
        }
    }
}

In the design I use, and have described here, I use a concrete NorthwindUnitOfWork class that wraps an IDataMapper that is the gateway to the underlying LINQ provider (such as LINQ to SQL or Entity Framework). In sumary, the design is as follows:

  1. An INorthwindUnitOfWorkFactory is injected in a client.
  2. The particular implementation of that factory creates a concrete NorthwindUnitOfWork class and injects a O/RM specific IDataMapper class into it.
  3. The NorthwindUnitOfWork is in fact a type-safe wrapper around the IDataMapper and the NorthwindUnitOfWork requests the IDataMapper for repositories and forwards requests to submit changes and dispose to the mapper.
  4. The IDataMapper returns Repository<T> classes and a repository implements IQueryable<T> to allow the client to use LINQ over the repository.
  5. The specific implementation of the IDataMapper holds a reference to the O/RM specific unit of work (for instance EF's ObjectContext). For that reason the IDataMapper must implement IDisposable.

This results in the following design:

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.
}

The NorthwindUnitOfWork is a concrete class that contains properties to specific repositories, such as Customers, Orders, etc:

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();
    }
}

What's left is an concrete implementation of the INorthwindUnitOfWorkFactory and a concrete implementation of the IDataMapper. Here's one for 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);
    }
}

And the 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);
        }
    }
}

You can find more information about this model here.

UPDATE December 2012

This an an update written two years after my original answer. The last two years much has changed in the way I try to design the systems I'm working on. Although it has suited me in the past, I don't like to use the factory approach anymore when dealing with the Unit of Work pattern. Instead I simply inject a Unit of Work instance into consumers directly. Whether this design is feasibly for you however, depends a lot on the way your system is designed. If you want to read more about this, please take a look at this newer Stackoverflow answer of mine: One DbContext per web request…why?

这篇关于如何通过依赖注入来处理资源的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-21 11:46