ASP.NET会话对象中的实体框架对象上下文?

and*_*351 4 asp.net session entity-framework httpcontext objectcontext

我们有一个多层的Asp.NET Web窗体应用程序.数据层有一个名为DataAccessimpements 的类,IDisposable并且我们将Entity Framework Object Context的实例作为私有字段.该类有许多公共方法返回各种实体集合,并在处理时将处置其对象上下文.

由于我们一直面临的许多问题,我们认为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];
}
Run Code Online (Sandbox Code Playgroud)

干杯.

Aar*_*ght 18

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

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


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

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

对象上下文将要处理的数据量不是很大,并且不会对我们体面的服务器硬件造成问题,关于随时间缓存和相对较少的并发用户.

请记住,增长是无限的.如果特定用户决定使用您的网站连续12个小时全天运行不同的查询,那么上下文将不断变得越来越大.An ObjectContext没有自己的内部"垃圾收集",它不会清除长时间未使用的缓存/跟踪实体.如果你相信,这不会是根据您的使用情况再细的问题,而应该是困扰你的主要事情是你所欠缺的事实控制了局面.


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

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


如果您意识到所有这些问题并已采取措施来缓解这些问题,那很好.但我的问题是,为什么你认为会话级别ObjectContext是一个好主意?创建ObjectContext一个非常便宜的操作,因为元数据是为整个AppDomain缓存的.我猜赌你要么错误地认为它很贵,或者你试图在几个不同的网页上实施复杂的有状态进程,而后者的长期后果远比任何具体的都要糟糕.通过简单地ObjectContext进入会话可能会造成伤害.

无论如何你要继续这样做,只要确保你出于正确的理由这样做,因为没有很多理由这样做.但是,正如我所说,这绝对是可能的,你的应用程序不会因此而爆炸.


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

ObjectContext由于上面列出的几个原因,我仍然不会在会话状态中存储,但除非您将其设置为一个,否则它不是线程安全问题.

  • @Steven:请在这里阅读有关会话状态的MSDN文档:http://msdn.microsoft.com/en-us/library/ms178581.aspx它说,我引用,"...如果有两个并发请求在同一个会话中(通过使用相同的SessionID值),第一个请求获得对会话信息的独占访问权.第二个请求仅在第一个请求完成后才执行." 请删除downvote,这里没有风险,除非程序员优化(我清楚地概述)打破了这种排他性. (2认同)
  • 澄清一下:这不仅仅是对序列化的会话状态的访问,而是整个 **request** 被序列化。这是一个关键的区别。在第一个请求*完全完成执行*之前,第二个请求无法访问会话状态。如果同一会话上的 HTTP 请求完全使用会话,则它们会被序列化(实际上是单线程)。 (2认同)
  • Aaronaught,我必须道歉。你的长答案实际上非常准确,你非常清楚和正确地说明了拥有会话 ObjectContext 的所有缺点,我完全同意你的看法。在您的回答中,您已经说明了更改默认设置时线程安全的问题,因此应忽略我的第一条评论... (2认同)