【问题标题】:ASP.NET Core - caching users from IdentityASP.NET Core - 从身份缓存用户
【发布时间】:2019-03-25 18:41:48
【问题描述】:

我正在使用标准的 ASP.NET Core 2.1 程序,并且我一直在考虑这样一个问题,即我的许多控制器方法都需要检索当前登录的用户。

我注意到 asp.net 核心身份代码使用 DBSet 来保存实体,并且随后对它的调用应该从内存中的本地实体读取而不是访问数据库,但似乎每次,我的代码需要读取数据库(我知道我正在运行 SQL Profiler,并看到针对 AspNetUsers 的选择查询正在使用 Id 作为键运行)

我知道有很多方法可以设置 Identity,并且它改变了版本,可能我做的不对,或者这里有一个可以解决的基本问题。

我在 startup.cs 的 ConfigureServices 中设置了默认的 EF 和 Identity 存储:

services.AddDbContext<MyDBContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MyDBContext")));
services.AddIdentity<CustomIdentity, Models.Role>().AddDefaultTokenProviders().AddEntityFrameworkStores<MyDBContext>();

并在每个控制器方法中读取用户:

var user = await _userManager.GetUserAsync(HttpContext.User);

在身份代码中,该方法似乎调用了 UserStore FindByIdAsync 方法,该方法调用了用户 DBSet 上的 FindAsyncEF performance paper 说:

It’s important to note that two different ObjectContext instances will have two different ObjectStateManager instances, meaning that they have separate object caches.

那么这里可能出了什么问题,为什么 ASP.NET Core 在 Userstore 中的 EF 调用没有使用实体的本地 DBSet 有什么建议吗?还是我想错了 - 每次调用控制器时,都会创建一个新的 EF 上下文?

【问题讨论】:

  • ef dbcontext 默认为作用域生命周期,这意味着它会在每个请求结束时自动释放。您不应该尝试将其保留更长时间。为什么需要这么多从数据库中获取用户?诸如用户电子邮件、ID、姓名和角色之类的东西可以是被序列化并持久化到 auth cookie 中的声明,所以如果你只需要这些东西,你就不需要经常为用户访问数据库。您可以访问 HttpContext.User,它是由 auth 中间件自动从 auth cookie 反序列化的声明主体。
  • @JoeAudette 我很害怕,为什么还要在 Usermanager 中读取明确的 DBset!我需要与用户一起存储的各种属性,将它们全部转换为声明并不正确。
  • 如果您认为您需要访问的内容不适合声明,那么您可以将它们缓存在内存或会话状态中,以避免在每次请求时都访问数据库。所有事情都需要权衡。在不具体了解您需要哪些数据的情况下,我无法就其中的部分或全部是否适合作为声明提出任何建议。
  • @JoeAudette 只是与用户关联的自定义数据,它们确实需要被缓存,我希望将它们与用户放在一起会更容易,因为我们有一个 usermanager 对象而不是通常的 EF 调用.我将不得不为这些想出一个缓存机制。

标签: asp.net-core asp.net-identity entity-framework-core


【解决方案1】:

关于用户存储中 ASP.NET Core 的 EF 调用不使用实体的本地 DBSet 的任何建议?

实际上,FindAsync确实这样做了。引用 msdn(强调我的)...

异步查找具有给定主键值的实体。 如果上下文中存在具有给定主键值的实体,则立即返回它而不向商店发出请求。 否则,向商店发出请求以获取具有给定主键值的实体键值和该实体(如果找到)附加到上下文并返回。如果在上下文或存储中没有找到实体,则返回 null。

因此,您无法避免对对象的每个请求的初始读取。但是同一请求中的后续读取不会查询存储。这是你在疯狂的微优化之外所能做的最好的事情

【讨论】:

  • 谢谢,但正如 Joe Audette 提醒我的那样 - dbcontext 的范围适用于每个调用,因此我对 DBSet 的唯一调用永远不会保留在下一个查询中。
  • 没错,但在每个请求中,调用FindAsync 时都会使用本地数据库集
【解决方案2】:

是的。控制器在每个请求中都会被实例化和销毁,无论是同一用户还是不同的用户发出请求。此外,上下文是请求范围的,因此它也随着每个请求被实例化和销毁。如果您在同一请求期间多次查询同一用户,它将尝试使用实体缓存进行后续查询,但您可能不会这样做。

也就是说,这是一个过早优化的教科书示例。从数据库中查询用户是一种非常快速的查询。这只是一个关于主键的简单选择语句 - 就数据库查询而言,它不会变得更快或更简单。如果您使用内存缓存,您可能可以节省几毫秒,但这需要考虑一整套注意事项,特别是要小心按用户隔离缓存,这样您就不会意外地为错误引入错误的数据用户。更重要的是,由于多种原因,内存缓存存在问题,因此在生产中使用分布式缓存更为典型。一旦你到达那里,缓存并不会真正为像这样的简单查询买任何东西,因为你只是从分布式缓存存储(甚至可能是 SQL Server 之类的数据库)而不是你的数据库中获取它。只有缓存复杂和/或缓慢的查询才有意义,因为只有这样从缓存中检索它实际上最终会比再次访问数据库更快。

长话短说,不要害怕查询数据库。这就是它的用途。它是您的数据源,因此如果您需要数据,请进行查询。一旦您的网站开始运行,您就可以分析或以其他方式监控性能,如果您发现查询缓慢或过多,然后您可以开始寻找优化方法。在它真正成为问题之前不要担心。

【讨论】:

  • 虽然分布式缓存在其他方面(至少对我来说)被称为数据库 :-) 本地缓存对性能有很大的影响,这就是我在这里所追求的。我认为我的问题是将“用户”数据与身份数据混合在一起,所以我将把它分成 2 份,并在某种会话中缓存用户资料。
  • 会话使用分布式缓存,所以再次,只需将一个数据库查询切换为另一个。说真的,只要查询用户就可以了,不用担心。
  • @ChrisPratt 这不是真的 - 会话选项之一(默认选项)是使用内存作为 IDistributedCache 存储。
  • 内存会话存储是默认的,因为它不需要配置。您实际上不应该在生产中使用它。我们一直在讨论这个问题,但简单明了的事实是,您应该只从数据库中查询用户。缓存、会话等都是all 过大的,如果你真的正确设置了你的应用程序,它们不会比直接查询用户更好。你在这上面旋转你的轮子。
猜你喜欢
  • 2021-06-23
  • 1970-01-01
  • 2017-10-30
  • 2022-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-01
相关资源
最近更新 更多