我在与 Appfabric 相同的机器上使用 Web 应用程序和 Windows 服务。两个应用程序都重用相同的 DAL 代码 (dll),它基于 EF(实体框架)代码优先并访问 Appfabric 中的相同缓存。作为Quartz.Net的一部分,windows 服务中的代码作为 Job 实现
Web 应用程序必须支持多个请求,并且 Windows 服务多个线程(调度程序和事件)。对于这两者,共享的 DAL dll 为每个 http 会话和线程 ContextID 创建一个 DbContext 对象,或者只为后面的线程 ContextID 创建一个 DbContext 对象。DAL 使用此处的 EFCachingProviders 。此外,我的 EF 解决方案在映射中使用带有时间戳列和 IsRowVersion 的乐观并发。
如此处所述,拥有二级缓存的好处是可以跨进程访问原始状态的表示!但这似乎对我不起作用,我在用例中得到“OptimisticConcurrencyException”,如下所示:
- 重启缓存集群,重启windows服务,重启iis -> clean slate :)
- 使用 web 应用程序 (firefox),我参考现有对象 B 插入一个新对象 A。我可以在数据库中看到新行。一切都好。
- 在另一个浏览器 (chrome) = 新会话中使用 webapp,我可以看到新对象。
- 接下来,Windows 服务尝试进行一些后台处理并尝试更新对象 B。这会导致“OptimisticConcurrencyException”。显然,Windows 服务中的进程持有一个带有日期行版本的对象 B 版本。
- 如果我重新启动 Windows 服务,它会再次尝试相同的逻辑并且毫无例外地工作......
所以这两个应用程序都是多线程的,使用相同的 DAL 代码,连接到相同的数据库,以及相同的缓存集群和相同的缓存。我希望更新和插入在 appfabric 缓存中。我希望 Windows 服务的 EF 上下文使用最新信息。不知何故,它似乎是保存旧信息的一级缓存......或者其他东西出了问题。
请指教...
更新
好的,经过挖掘,我修复了我的 Windows 服务的更新问题。每个查询 DAL 的 Manager 对象都使用一个绑定到其进程 ID + 线程 ID 的 DbContext。因此,在我的 Quartz Job 的 Execute 函数中,所有 Manager(不同对象类型)应该共享由第一个 Manager 创建的相同 DbContext。
问题是,在函数完成后,DbContext 没有被释放(这在基于 HTTP 会话的 DbContext 管理器中自动发生)。所以下次执行 Job 时,找到并使用了相同的 DbContext,到那时它已经过时了(旧的一级缓存???)。二级缓存应该不是问题,因为它是共享的并且应该包含最新的对象......如果有的话。
所以这部分是固定的。
新问题
因此,网络应用程序创建了一个新对象 A,更新了现有对象 B,Windows 服务现在可以正常工作并且能够毫无问题地更新现有(更改的)对象 B。
问题:当我刷新 web 应用程序时,它看不到对象 B 的更改(通过 Windows 服务)......
因此,如果 web 应用程序将计数更改为 5,10 分钟后 Windows 服务将计数更改为 6,并且我在同一或新窗口/浏览器中打开网络应用程序,我仍然看到 5,而不是 6!
重新启动 webapp (iis) 没有帮助,iisreset 也没有帮助。当我做 Restart-CacheCluster.... 它工作并显示 6....
所以看起来该项目在缓存中。Windows 服务会更新它,但不会使该项目失效,该项目是旧的并由 webapp 使用....
或者......虽然是同一个对象,但 webapp 在缓存中有自己的条目,而 win-app 有自己的条目(确实会失效)......
哪一个?
解决方案
我自己解决了这个问题。看来,EF 包装器使用查询字符串作为将项目存储在缓存中的键。因此,引用数据库中相同数据的 2 个不同查询(无论它们来自 2 个共享相同分布式缓存或相同应用程序的不同应用程序都无关紧要)将具有不同的键(不同的查询字符串),因此缓存中的位置也不同。也许不是这样的黑白,而是这样的……
我不认为在内部使用某种算法来检查查询是否触及现有的缓存对象。
这导致了我的问题,我的 Windows 服务进行了更新,而 webapp 仍然从缓存中看到旧的,这只能通过执行 Restart-CacheCluster 命令来解决。
那么我是如何解决这个问题的:我的 Windows 服务是由 Quartz 调度程序触发的批处理作业。完成后我清除整个缓存:
private void InvalidateCache()
{
try
{
DataCache myCache = ...
foreach (String region in myCache.GetSystemRegions())
{
myCache.ClearRegion(region);
}
}
catch (Exception ex)
{
eventLog.WriteEntry("InvalidateCache exception : " + ex.Message);
}
}