ASP.NET Session对象中的Entity Framework对象上下文?

5
我们有一个多层次的Asp.NET Web Forms应用程序。数据层有一个名为的类,它实现了接口,并具有我们的Entity Framework对象上下文作为私有字段。该类有许多公共方法返回各种实体集合,并在被处理时处理其ObjectContext。
由于我们一直面临着许多问题,我们决定在服务器上保持ObjectContext(或一个的实例)更长时间会是一个很大的优势。建议将一个实例保存在此帖子中的HttpContext.Current.Items集合中以每个Http请求有一个实例。
我想知道的是:将ObjectContext的实例存储在对象中会出现什么问题/担忧/问题?
  • 我假设当用户会话过期时,会话对象将被终止并设置为垃圾回收,因此实例将被正确处置。
  • 我假设大多数默认浏览器设置将允许我们的应用程序毫不犹豫地放置其SessionId cookie。
  • ObjectContext将处理的数据量不是很大,对于我们的良好服务器硬件来说不会成为问题,关于随时间的缓存和相对较少的并发用户。
这将是相对快速执行的操作,并且不会影响我们的许多现有单元测试。
我们将使用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];
}

欢呼。
2个回答

19
在会话状态中存储ObjectContext并不是我认为是一个好习惯,因为该类旨在封装工作单元模式-您加载一些数据(实体),修改它们,提交更改(这些更改由UOW跟踪),然后完成。 UOW对象不打算或设计成长寿命的。
也就是说,这可以做到而不会造成任何重大灾难,只需确保您了解幕后发生的事情。如果您计划这样做,请继续阅读,以便您知道自己正在进行什么,并了解权衡。
我假设当用户会话过期时,Session对象被最后确认并设置为垃圾回收,因此实例将被正确处理。这实际上是不准确的,或者至少在措辞方式上似乎如此。会话到期/注销不会立即导致任何项目被处理。它们将最终被处理,但这取决于垃圾回收器,您无法控制何时发生。这里最大的潜在问题是,如果您偶然手动在ObjectContext上打开连接,它不会自动关闭。如果您不小心,可能会泄漏数据库连接,这在常规单元测试/集成测试/现场测试中不会被发现。
ObjectContext将处理的数据量不是巨大的,并且相对较少的并发用户时间内不会对我们的良好服务器硬件造成问题,例如缓存。
请记住,增长是无限的。如果某个特定用户决定在您的网站上连续使用12个小时运行不同的查询,则上下文将不断变得越来越大。ObjectContext没有自己的内部“垃圾回收”,它不会清除长时间未使用的缓存/跟踪实体。如果您确定这不会成为一个问题,则可以忽略,但主要问题应该是您缺乏对情况的控制。

另一个问题是线程安全性。 ObjectContext 不是线程安全的。 会话访问通常是串行化的,以便一个请求将被阻塞,等待其会话状态,直到另一个针对同一会话的请求完成为止。 但是,如果有人决定进行优化,特别是页面级只读会话的优化,那么请求将不再持有独占锁,您可能会遇到各种竞争条件或重入问题。

最后一个问题当然是多用户并发的问题。一个ObjectContext会永远缓存它的实体,直到被处理掉。如果另一个用户在他自己的ObjectContext上更改了相同的实体,第一个ObjectContext的所有者将永远不会知道这种变化。这些陈旧数据问题可能非常难以调试,因为您实际上可以看到查询从数据库返回新鲜数据,但ObjectContext将其用已经存在于缓存中的旧陈旧数据覆盖。我认为,这可能是避免长时间使用ObjectContext实例的最重要原因;即使您认为已经编写了代码以从数据库中获取最新数据,ObjectContext也会决定它比您更聪明,并将旧实体交还给您。


如果你已经意识到了这些问题并采取了措施来减轻它们,那很好。但我的问题是,你为什么认为会话级别的 ObjectContext 是一个很好的想法呢?创建一个 ObjectContext 实际上是一个非常便宜的操作,因为元数据被缓存到整个 AppDomain 中。我敢打赌,你要么误以为它很昂贵,要么试图在几个不同的网页上实现复杂的有状态流程,后者的长期后果比简单地将 ObjectContext 放入会话中造成的任何具体伤害都要严重得多。
如果你无论如何都要这样做,只需确保你是出于正确的原因这样做,因为没有太多好的理由这样做。但正如我所说,这是完全可能的,你的应用程序不会因此而崩溃。

更新 - 如果有人因为“同一会话上的多个请求可能会导致线程安全问题”而考虑对此进行负评,请阅读 ASP.NET 会话状态概述 文档底部。不仅是会话状态的单个访问被序列化,任何获取会话的请求都会保持对会话的独占锁定,直到整个请求完成。除了我上面列出的一些优化之外,在默认配置下,不可能存在两个同时持有对同一个会话本地实例的 ObjectContext 引用的请求。

尽管出于上述原因,我仍然不会将 ObjectContext 存储在会话状态中,但除非您自己制造线程安全问题,否则这不是一个线程安全问题。


1
你关于线程安全的说法是不正确的。ObjectContext确实不是线程安全的,虽然会话访问被序列化,但同一用户发出的两个不同请求很可能同时使用同一个实例。你可能会遇到这样的情况:第一个请求在你验证实体状态后立即调用SaveChanges,而第二个请求正在改变某个实体。这可能会导致各种可怕的事情发生,比如数据库中的无效状态和永久损坏的ObjectContext实例。 - Steven
3
请阅读此处的MSDN文档有关会话状态的内容:http://msdn.microsoft.com/en-us/library/ms178581.aspx。它说:“……如果使用相同的SessionID值进行了两个并发请求,则第一个请求将独占会话信息。第二个请求只有在第一个请求完成后才能执行。” 请取消投票,除非程序员的优化(我已经清楚地概述了)打破了这种独占性,否则没有任何风险。 - Aaronaught
2
澄清一下:序列化的不仅仅是对话状态的访问,而是整个请求。这是一个至关重要的区别。在第一个请求完全执行完成之前,第二个请求无法访问会话状态。如果同一会话上的HTTP请求使用了会话,则它们将被序列化(实际上是单线程)。 - Aaronaught
2
Aaronaught,我必须道歉。你的长篇回答实际上非常准确,你非常清楚地、正确地阐述了拥有会话ObjectContext的所有缺点,我完全同意你的观点。在你的回答中,你已经阐明了当更改默认设置时线程安全性的问题,因此我的第一条评论应该被忽略... - Steven
1
这么长时间后又回来看到了这个,再次感谢你出色的答案! - andrej351
显示剩余2条评论

3
您应该每个请求使用一个ObjectContext,不应将其存储在Session中。长时间存储的ObjectContext很容易破坏数据:
1. 如果您在ObjectContext中插入不违反规则的数据,但在数据库中违反规则怎么办?如果插入违反规则的行,则会从上下文中删除它吗?图像情况:您使用一个上下文,突然有一个请求更改一个表中的数据,在另一个表中添加行,然后调用SaveChanges()。其中一个更改引发约束违规错误。如何清理它?清理上下文不容易,最好在下一个请求中获取新的。
2. 如果有人从数据库中删除数据,而它仍然在上下文中怎么办?ObjectContext缓存数据,并且不时地查看是否仍然存在或是否已更改:)
3. 如果有人更改web.config并且会话丢失怎么办?似乎您希望依赖Session存储有关已登录用户的信息。表单身份验证cookie是更可靠的存储此信息的位置。会话可能在许多情况下丢失。
ObjectContext设计为短暂的,最好在需要时在请求中创建它,并在结束时处理它。
如果每个请求的上下文对您不起作用,那么您可能做错了什么,但不要通过使用Session使其更糟。

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