5

我们有一个多层的 Asp.NET Web 窗体应用程序。数据层有一个名为的类DataAccess,它实现IDisposable并具有我们的实体框架对象上下文的实例作为私有字段。该类有许多返回各种实体集合的公共方法,并在释放时释放其对象上下文。

由于我们一直面临的许多问题,我们认为将对象上下文(或 的实例DataAccess)在服务器上的范围内保持更长时间将是一个很大的优势。有人建议在这篇文章HttpContext.Current.Items的集合中保留一个实例,以便每个 Http 请求都有一个实例。

我想知道的是:将我们的对象上下文的实例存储在对象中会产生什么问题/关注/问题HttpContext.Current.Session????

  • 我假设 Session 对象已完成并在用户会话到期时设置为垃圾收集,因此实例将被正确处理。
  • 我假设大多数默认浏览器设置将让我们的应用程序毫无疑虑地放置其 SessionId cookie。
  • 对象上下文将处理的数据量并不巨大,并且不会对我们体面的服务器硬件构成问题,随着时间的推移缓存和相对较少的并发用户。

这将相对较快地实施,并且不会影响我们许多现有的单元测试。

我们将使用 AutoFac 和 ServiceProvider 类来提供实例。当需要 ObjectContext 的实例时,它将由类似于以下的代码返回:

private static Entities GetEntities(IContext context)
{
    if (HttpContext.Current == null)
    {
        return new Entities();
    }

    if (HttpContext.Current.Session[entitiesKeyString] == null)
    {
        HttpContext.Current.Session[entitiesKeyString] = new Entities();
    }

    return (Entities)HttpContext.Current.Session[entitiesKeyString];
}

干杯。

4

2 回答 2

19

在会话状态中存储一个ObjectContext我认为不是一个好的做法,因为该类旨在封装一个工作单元模式 - 您加载一些数据(实体),修改它们,提交您的更改(这是由 UOW 跟踪),然后你就完成了。UOW 对象并非旨在或设计为长期存在的。

也就是说,它可以在不造成任何重大灾难的情况下完成,你只需要确保你了解幕后发生的事情。 如果您打算这样做,请继续阅读,以便您知道自己要做什么并了解权衡取舍。


我假设 Session 对象已完成并在用户会话到期时设置为垃圾收集,因此实例将被正确处理。

这实际上是不准确的,或者至少似乎是基于它的措辞方式。会话到期/注销不会立即导致任何项目被处置。它们最终将被最终确定/处置,但这取决于垃圾收集器,您无法控制它何时发生。这里最大的潜在问题是,如果您碰巧在 上手动打开了一个连接,该连接ObjectContext不会自动关闭 - 如果您不小心,您最终可能会泄漏数据库连接,这是常规单元无法发现的测试/集成测试/现场测试。

对象上下文将处理的数据量并不巨大,并且不会对我们体面的服务器硬件构成问题,随着时间的推移缓存和相对较少的并发用户。

请记住,增长是无限的。如果特定用户决定连续 12 小时使用您的网站,整天运行不同的查询,那么上下文将变得越来越大。AnObjectContext没有自己的内部“垃圾收集”,它不会清除长时间未使用的缓存/跟踪实体。如果您确定根据您的用例这不会成为问题,那很好,但应该困扰您的主要事情是您缺乏对情况的控制


另一个问题是线程安全。 ObjectContext不是线程安全的。会话访问通常是序列化的,因此一个请求将阻塞等待其会话状态,直到对同一会话的另一个请求完成。但是,如果有人决定稍后进行优化,特别是页面级只读会话的​​优化,请求将不再持有排他锁,您最终可能会遇到各种竞争条件或重入问题.

最后但并非最不重要的当然是多用户并发问题。AnObjectContext永远缓存它的实体,直到它被释放。如果另一个用户自己更改了相同的实体ObjectContext,则第一个用户的所有者ObjectContext永远不会发现该更改。这些陈旧的数据问题可能非常难以调试,因为您实际上可以看到查询进入数据库并返回新数据,但是ObjectContext会用缓存中已经存在的旧数据覆盖它。在我看来,这可能是避免长期ObjectContext实例的最重要原因;即使您认为您已经对其进行了编码以从数据库中获取最新数据,ObjectContext将决定它比你更聪明,并把旧实体交还给你。


如果您了解所有这些问题并已采取措施缓解这些问题,那很好。但我的问题是,为什么您认为会话级别ObjectContext是一个好主意?创建一个ObjectContext确实是一个非常便宜的操作,因为元数据是为整个 AppDomain 缓存的。我敢打赌,要么你错误地认为它很昂贵,要么你试图在几个不同的网页上实现复杂的有状态进程,而后者的长期后果比任何特定的要糟糕得多简单地ObjectContext在会话中添加一个可能会造成的伤害。

如果你要继续去做,只要确保你这样做是出于正确的理由,因为没有很多好的理由去做。但是,正如我所说,这绝对是可能的,并且您的应用程序不会因此而崩溃。


更新- 对于其他考虑否决这一点的人,因为“同一会话上的多个请求可能导致线程安全问题”,请阅读ASP.NET 会话状态概述文档的底部。被序列化的不仅仅是会话状态的个人访问;任何获取会话的请求都会对会话保持独占锁,直到整个请求完成才释放。除了我上面列出的一些优化之外,在默认配置中不可能有两个同时请求持有对同一会话本地实例的引用ObjectContext

由于上面列出的几个原因,我仍然不会ObjectContext在会话状态中存储一个,但这不是线程安全问题,除非你把它做成一个。

于 2010-03-05T05:37:01.680 回答
3

您应该为每个请求使用一个 ObjectContext,不应将其存储为 Session。很容易破坏 ObjectContext 中长期存储的数据:

  1. 如果插入的数据不违反 ObjectContext 中的规则,但违反了数据库中的规则,该怎么办?如果您插入违反规则的行,您会从上下文中删除它吗?图像情况:您使用一个上下文,突然您请求更改一个表中的数据,将行添加到另一个表,然后调用 SaveChanges()。其中一项更改引发约束冲突错误。你怎么清理它?清理上下文并不容易,在下一个请求中获取新上下文更容易。

  2. 如果有人从数据库中删除数据,而数据仍在上下文中怎么办?ObjectContext 缓存数据,并且不会时不时检查它是否仍然存在或它们是否已更改:)

  3. 如果有人更改了 web.config 并且 Session 丢失了怎么办?似乎您想依靠 Session 来存储有关登录用户的信息。表单身份验证 cookie 是存储这些信息的更可靠的地方。在许多情况下,会话可能会丢失。

ObjectContext 被设计为短暂的,最好在需要时在请求中创建它并在它结束时处理它。

如果每个请求的上下文对您不起作用,那么您可能做错了什么,但不要使用 Session 使情况变得更糟。

于 2010-03-05T04:41:51.450 回答