使用StructureMap(IoC)的IQueryable存储库 - 如何实现IDisposable?

5
如果我有以下存储库:
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 代码指标肯定认为我应该。
我正在使用 IQueryable,因为我将查询控制权交给了我的服务层(过滤、分页等),所以请不要在我使用它的事实上进行架构讨论。
顺便说一句 - SqlDataContext 是我的自定义类,它扩展了 Entity Framework 的 ObjectContext 类(这样我就可以拥有 POCO 对象)。
那么问题来了 - 我真的必须要实现 IDisposable 吗?
如果是这样,我不知道如何做到这一点,因为每个方法共享相同的 repository 实例。
编辑:
我正在使用依赖注入(StructureMap)将具体的 repository 注入到服务层中。这种模式沿着应用程序堆栈进行 - 我正在使用 ASP.NET MVC,具体服务被注入到 Controllers 中。
换句话说:
1. 用户请求 URL。 2. 创建 Controller 实例,接收一个新的 ServiceLayer 实例,该实例使用新的 Repository 实例创建。 3. Controller 调用服务的方法(所有调用都使用相同的 Repository 实例)。 4. 请求完成后,Controller 就消失了。
我正在使用混合模式将依赖项注入到我的 Controllers 中,根据 StructureMap 文档,这会导致实例存储在 HttpContext.Current.Items 中。
因此,我无法这样做:
   using (var repo = new Repository())
   {
      return repo.Users().ToList();
   }

由于这将打败依赖注入的初衷。

1
@Joshua Flanagan,@PHeiberg,@DanM,@jgauffin - 请看我对我的回答所做的修改。Ayende Rahien(这方面的专家)已经给出了答案。想让你们知道。 - RPM1984
2
+1 真的是一个非常好的问题 - 我今天也在问自己这个问题(使用StructureMap和Entity Framework 4)。不确定StructureMap是否会隐式地将IDisposables包装在using() {}中,或者我是否需要实现。老实说,从你的回答中我还是不确定。我正在使用构造函数注入,并希望找到一个同时涵盖存储库、服务构造函数和IDisposable实现的示例。 - Neil Fenwick
1
有一个方法称为“ReleaseAllScopedAndHttpVariables”或类似的东西 - 你需要从Application_EndRequest()中调用它。使用Hibernating Rhinos分析器确保所有内容都被处理掉。 - RPM1984
然而,这仅确保在请求之后处理对象。如果您想立即处理某些内容(就像我一样),则需要调用dispose。它确实有效-只需使用分析器来确保它有效。 - RPM1984
3个回答

3
我认为你肯定应该这样做。除非Entity Framework处理连接的方式与我一直在使用的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(),当所有操作完成后,您的存储库将被正确处理。


这就是我的观点 - 服务层方法使用共享的 _repository 实例。那么我该如何在方法中编写 using 语句呢?我需要每次都新建一个 repository。但问题在于,我正在使用 DI 将 repository 注入到服务层的构造函数中。因此陷入了进退两难的境地。=) - RPM1984
你是如何处理共享仓库的多线程问题的? - jgauffin
@jgauffin - 这不是一个“共享”存储库。该存储库存在于任何HTTP请求的生命周期内。这就是StructureMap的作用-它为每个HTTP请求提供控制器所需的服务对象,该服务对象又接收一个存储库对象。该存储库在对存储库的多个调用之间是共享的,但仅限于一个HTTP请求-因此只有一个线程。 - RPM1984
@RPM1984 - 好的,我不知道 :) 当请求超出范围时,StructureMap会如何处理这些实例?它会检查是否有任何服务实现了IDisposable吗? - jgauffin
@jgauffin - 当StructureMap“引导”实例时,您可以说对象的范围。最常见的是混合模式 - 这会导致StructureMap将对象粘贴到HttpContext.Current.Items中(或者我被引导相信如此)。我不知道的是,如果StructureMap还会调用实现IDisposable的任何类型的Dispose方法。也许我需要编辑这个问题,并考虑使用StructureMap来控制实例范围。 - RPM1984
显示剩余3条评论

3

在使用nhibernate时常用的方法是在begin_request(或其他类似的生命周期事件)中创建您的会话(ObjectContext),然后在end_request中处理它。您可以将该代码放入HttpModule中。

您需要更改Repository,使其注入ObjectContext。您的Repository应该不再管理ObjectContext的生命周期。


嗯,感谢提供这种替代方案——但我不确定是否习惯于在每个HTTP请求时打开新的数据库连接。并非每个HTTP请求都需要进行数据库调用,因此这将浪费资源。当我使用数据库连接时,我喜欢在最后一刻(即需要它们时)才打开它们。不过还是感谢您的回答。 - RPM1984
没关系,我找到了解决方案(请看我的回答)。还是谢谢。 - RPM1984
@PHeiberg - 同意。但是我没有采用上面的答案。每当用户请求页面时,都会创建一个新的控制器,该控制器创建一个新的服务,该服务创建一个新的存储库。每个“存储库”实例都使用SqlContext进行了初始化。这并不一定意味着一个“打开”的连接。它意味着一种“等待”状态的连接。当我发出查询(.ToList())时,它会“打开”连接,执行查询,然后关闭连接。尽管如此,我还是向一些专家发送了电子邮件,询问正确的解决方案。 - RPM1984
@PHeiberg - 感谢提供链接,内容非常有趣。但是,该文章似乎针对减少往返和优化查询(Hibernating Rhinos)。虽然我对此很感兴趣,但我更加关心如何设计StructureMap/IQueryable repos的初始架构。我的一个同事已经在EntityFramework论坛上发布了一个问题-希望他们能回复。如果他们回复的话,我会在这里更新的。 - RPM1984
@RPM1984 - 这篇文章没有展示任何实例架构,但指出为什么在一个请求中拥有多个上下文通常不是一个好主意(多个连接、没有共享的工作单元、多次往返、分布式事务等)。我期待着看到你能够获得的任何指导。 - PHeiberg
显示剩余3条评论

2

编辑 - 来自Ayende Rahien的建议

收到了来自Ayende Rahien(Rhino Mocks、Raven、Hibernating Rhinos等知名项目的创建者)的电子邮件回复。

他说:

你问题出在这里: _genericSqlServerContext = new GenericSqlServerContext(new EntityConnection("name=EFProfDemoEntities"));

这意味着上下文没有拥有实体连接,也就是说它没有释放它。一般来说,让上下文创建连接要好得多。你可以使用以下代码实现: _genericSqlServerContext = new GenericSqlServerContext("name=EFProfDemoEntities");

这绝对是有道理的 - 不过我本来以为Sql Server Context的释放也会释放底层连接,看来我错了。

无论如何,这就是解决方案 - 现在所有东西都能得到正确的释放。

所以我不再需要在存储库上使用using语句了。

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.

希望这能帮助其他人。

你启用了懒加载吗?由于我启用了懒加载,我无法关闭我的连接,遇到了问题。 - Praveen
1
@Praveen - 不,我禁用了它,并且急切地加载消费者需要的内容。你肯定会遇到问题,特别是如果你在HTTP请求线程之外运行查询(例如在你的MVC视图中)。所以要小心。 - RPM1984
那是我迄今为止收到的最好的建议,非常感谢!我确实有几个直接引用服务的视图。我已经重新设计了它们。但是,我仍然遇到问题。任何延迟加载的内容都会导致麻烦。有什么解决方法吗? - Praveen
@Praveen - 我建议你在提问时引用具体的问题,并包含代码示例。但是,是的,你的任何视图都不应该调用服务。视图最多只能使用 HTML 助手,不应该回调服务/控制器。 - RPM1984

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接