原来的答案被删除了,因为这是浪费时间。
您的帖子和进行中的 cmets 是 the XY Problem 的完美示例。
你说:
我真的需要一个解决问题的方法,而不是架构
如果架构是问题怎么办?
你提出的问题
您实施的违反至少六种最佳实践的缓存解决方案(令人惊讶!)在您的脸上炸开了锅。您已经设法通过壮观的(不是以一种好的方式)hack 来阻止它再次爆炸,但您想知道如何以不需要如此壮观的 hack 的方式来做到这一点。
你遇到的问题
您需要缓存一些数据,因为每次请求都访问数据库的成本太高。
提供的答案
使用外键代替导航属性
这是一个完全有效的答案,而且令人惊讶的是,这是一个最佳实践。导航属性可以在您重新生成实体数据模型中的代码时随时更改,并且通常是模棱两可的。稍加努力,您就可以使用它,而不必再担心 EF 对对象关系的处理。
缓存模型而不是实体对象
另一个有效的答案,并且需要最少的实际工作量。 MVC 应用程序通常需要在视图模型和实体对象之间进行一些冗余,如果您编写过适当的多层应用程序,您实际上会淹没在冗余对象中。并且没有人会意外地将这些对象再次添加到 DbContext 中——因为它们不能。
批评
您提供的有用信息很少。据我所知,您从一开始就采取的方法是错误的。
首先,在 App_Start 将整个表转储到内存中充其量只是一个临时解决方案。如果表太大而无法满足每个请求,那么它太大而无法满足 App_Start。如果在人们使用您的应用程序时出现重要问题并且您需要尽快部署错误修复,会发生什么情况?当您的表变得真正很大并且您在尝试将它们转储到内存时开始从 EF 获得超时时会发生什么?如果你的 95% 的用户真的只需要你转储到内存中的那个大表的 10%,会发生什么?您的 Web/缓存服务器上的内存是否足以容纳不断增加的表大小?多长时间?
其次,实体对象在其原始 DbContext 被释放后不应保留在任何地方。实体对象在其 DbContext 处于范围内时以一种方便的方式运行,而在超出范围时成为麻烦的 POCO。我说很麻烦,因为 DbContext 对更改跟踪所做的“魔术”往往会使不熟悉 EF 内部工作的人误以为实体对象直接连接到数据库中的表行。您遇到的问题完美地说明了这一点。
第三,您似乎需要删除整个表并将其重新转储到内存中,即使您只更新单行中的单个列也是如此。这对 Web 服务器上的内存和 CPU 以及 Azure SQL 实例都是极大的浪费。当少量数据出现错误并需要快速更新时会发生什么?如果您的一项夜间更新作业失败但您需要在早上获得新数据怎么办?
您现在可能不会担心这些事情,但您的解决方案在您面前爆炸应该至少会引发一些危险信号。在过去几年我从事的项目中,我不得不处理尽可能多的缓存,我在这里所说的一切都来自经验。
建议的解决方案 - 按需缓存
如果您在组织代码方面付出了一些努力,那么您对数据库的所有 CRUD 操作都应该在专门的帮助类中,我称之为存储库。您的控制器调用其专用存储库(StuffController - StuffRepository),接收模型并将该模型绑定到视图,有点像这样:
public class StuffController : Controller
{
private MyDbContext _db;
private StuffRepository _repo;
public StuffController()
{
_db = new MyDbContext();
_repo = new StuffRepository(_db);
}
// ...
public ActionResult Details(int id)
{
var model = _repo.ReadDetails(id);
// ...
return View(model);
}
protected override void Dispose(bool disposing)
{
_db.Dispose();
base.Dispose(disposing);
}
}
按需缓存的作用是以这样一种方式包装对存储库的调用,如果该方法的结果已经在缓存中并且它不是陈旧的,它将从缓存中返回它。否则它会命中数据库。
这是一个简化的(可能是非功能性的)CacheWrapper 类示例,以便您了解它的作用,使用 HttpRuntime.Cache:
public static class CacheWrapper
{
private static List<string> _keys = new List<string>();
public static List<string> Keys
{
get { lock(_keys) { return _keys.ToList(); } }
}
public static T Fetch<T>(string key, Func<T> dlgt, bool refresh = false) where T : class
{
var result = HttpRuntime.Cache.Get(key) as T;
if(result != null && !refresh) return result;
lock(HttpRuntime.Cache)
{
lock(_keys)
{
_keys.Add(key);
}
result = dlgt();
HttpRuntime.Cache.Add(key, result, /* some other params */);
}
return result;
}
}
以及从控制器调用事物的新方法:
public ActionResult Details(int id)
{
var model = CacheWrapper.Fetch("StuffDetails_" + id, () => _repo.ReadDetails(id));
// ...
return View(model);
}
正如我们所说,它的一个稍微复杂一点的版本正在公共 Web 应用程序上进行生产,并且运行良好。