带有StructureMap(IoC)的IQueryable存储库 - 我如何实现IDisposable?

时间:2010-09-08 07:02:22

标签: repository structuremap idisposable iqueryable objectcontext

如果我有以下存储库:

public IQueryable<User> Users()
{
   var db = new SqlDataContext();
   return db.Users;
}

我知道只有在触发查询时才会打开连接:

public class ServiceLayer
{
   public IRepository repo;

   public ServiceLayer(IRepository injectedRepo)
   {
       this.repo = injectedRepo;
   }

   public List<User> GetUsers()
   {
       return repo.Users().ToList(); // connection opened, query fired, connection closed. (or is it??)
   }
}

如果是这种情况,我还需要使我的Repository实现IDisposable吗?

Visual Studio Code Metrics当然认为我应该这样做。

我正在使用IQueryable因为我将查询控制到我的服务层(过滤器,分页等),所以请不要就我使用它的事实进行架构讨论。

BTW - SqlDataContext 是我的自定义类,它扩展了Entity Framework的ObjectContext类(所以我可以拥有POCO派对)。

所以问题 - 我真的必须实现IDisposable吗?

如果是这样,我不知道这是怎么可能的,因为每个方法共享相同的存储库实例。

修改

我正在使用Depedency Injection(StructureMap)将具体的存储库注入服务层。这个模式跟随app堆栈 - 我正在使用ASP.NET MVC,并且具体服务被注入到控制器中。

换句话说:

  1. 用户请求网址
  2. 创建控制器实例,该实例接收新的ServiceLayer实例,该实例是使用新的Repository实例创建的。
  3. 控制器调用服务上的方法(所有调用使用相同的存储库实例)
  4. 提供请求后,控制器就不见了。
  5. 我正在使用混合模式将依赖项注入到我的控制器中,根据StructureMap文档,这些实例会将实例存储在HttpContext.Current.Items中。

    所以,我不能这样做:

       using (var repo = new Repository())
       {
          return repo.Users().ToList();
       }
    

    因为这违背了DI的全部要点。

3 个答案:

答案 0 :(得分:3)

我会说你绝对应该。除非实体框架处理连接的方式与LinqToSql(我一直在使用的方式)完全不同,否则在使用连接时应该实现IDisposable。在事务成功完成后,连接可能会自动关闭。但如果没有成功完成会发生什么?实施IDisposable是一个很好的保障,确保您在完成任务后没有任何连接。一个更简单的原因是,实施IDisposable是最佳做法。

实现可以像在存储库类中一样简单:

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

然后,无论何时对存储库执行任何操作(例如,使用服务层),只需将所有内容都包含在using子句中。您也可以在单个using子句中执行多个“CRUD”操作,因此只有在完成所有操作后才能进行处理。

<强>更新

在我的服务层(我设计用于LinqToSql,但希望这适用于您的情况),我每次都新建一个新的存储库。为了实现可测试性,我在存储库提供程序(而不是存储库实例)中传递依赖项注入器。每次我需要一个新的存储库时,我都会将调用包装在using语句中,就像这样。

using (var repository = GetNewRepository())
{
    ...
}


public Repository<TDataContext, TEntity> GetNewRepository()
{
    return _repositoryProvider.GetNew<TDataContext, TEntity>();
}

如果你这样做,你可以模拟一切(所以你可以单独测试你的服务层),但仍然要确保你正确处理你的连接。

如果您确实需要使用单个存储库执行多项操作,则可以在基本服务类中添加以下内容:

public void ExecuteAndSave(Action<Repository<TDataContext, TEntity>> action)
{
    using (var repository = GetNewRepository())
    {
        action(repository);
        repository.Save();
    }
}

action可以是一系列CRUD操作或复杂查询,但是你知道如果你调用ExecuteAndSave(),当它完成后,你的存储库将被正确处理。

答案 1 :(得分:3)

与nhibernate一起使用的常见方法是在begin_request(或其他类似的生命周期事件)中创建会话(ObjectContext),然后将其置于end_request中。您可以将该代码放在HttpModule中。

您需要更改存储库,以便注入ObjectContext。您的存储库应该不再管理ObjectContext生命周期。

答案 2 :(得分:2)

编辑 - 从Ayende Rahien收到的建议

收到Ayende Rahien(Rhino Mocks,Raven,Hibernating Rhinos成名)的电子邮件回复。

这就是他所说的:

  

你的问题是你初始化   你的上下文是这样的:   _genericSqlServerContext = new GenericSqlServerContext(new   EntityConnection( “名称= EFProfDemoEntities”));

     

这意味着上下文没有   拥有实体连接,这意味着   它没有处置它。在   一般来说,它是非常可取的   有上下文创建   连接。你可以通过使用:    _genericSqlServerContext = new GenericSqlServerContext(“name = EFProfDemoEntities”);

哪个定义有意义 - 但是我会认为Disposing of SqlServerContext也会处理底层连接,猜猜我错了。

无论如何,这就是解决方案 - 现在一切都得到妥善处理。

所以我不再需要在存储库中使用

public ICollection<T> FindAll<T>(Expression<Func<T, bool>> predicate, int maxRows) where T : Foo
        {
            // dont need this anymore
            //using (var cr = ObjectFactory.GetInstance<IContentRepository>())
            return _fooRepository.Find().OfType<T>().Where(predicate).Take(maxRows).ToList();

在我的基础知识库中,我实现了IDisposable并简单地执行此操作:

Context.Dispose(); // Context is an instance of my custom sql context.

希望能帮助他人。