设计 – 如何用依赖注入来处理资源

我使用StructureMap来解析对我的存储库类的引用。我的存储库接口实现了IDisposable,例如
public interface 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 x in _dbContext
                    where x.Id = id
                    select x;
        return x.FirstOrDefault();
    }

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

反正如上所述,我使用StructureMap来解析IMyRepository。那么什么时候,我应该怎么称呼我的处置方式?

警告:请注意,我的意见已经改变了,你应该考虑过时的后续建议。请阅读最后的更新。

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

虽然这个代码明确地排除了依赖,但是它可能会给读者带来一些眉毛,因为资源通常只能由资源所有者处理。显然,当注入资源时,客户成为资源的所有者。

因此,我赞成使用工厂。在这个例子中查找例子:

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

在我使用的设计中,并描述了here,我使用了一个具体的NorthwindUnitOfWork类,它包含作为底层LINQ提供者(如LINQ to sql或Entity Framework)的网关的IDataMapper。总而言之,设计如下:

> 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是一个具体的类,其中包含特定存储库的属性,如客户,订单等:

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:

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

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

2012年12月更新

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

相关文章

适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法...
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题...
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结...
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容...