【问题标题】:Entity Framework core failing on async methods实体框架核心在异步方法上失败
【发布时间】:2016-12-21 03:03:47
【问题描述】:

我在 EF Core 中使用异步方法 - 通过内置 DI 获取上下文

 public async Task<bool> Upvote(int userId, int articleId)
{
    var article = await context.Articles
                               .FirstOrDefaultAsync(x => x.Id == articleId);
    if (article == null)
    {
        return false;
    }

    var existing = await context.Votes
                                .FirstOrDefaultAsync(x => x.UserId == userId
                                                       && x.ArticleId == articleId);
    if (existing != null)
    ...

当有人点赞一篇文章时运行。

如果这个函数一次运行一个(一个接一个),一切运行良好。

当我同时多次点击这个函数时,我得到了这个异常:

fail: Microsoft.EntityFrameworkCore.Query.Internal.MySqlQueryCompilationContextFactory[1]
      An exception occurred in the database while iterating the results of a query.
      System.NullReferenceException: Object reference not set to an instance of an object.
         at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable.AsyncEnumerator.<BufferAllAsync>d__12.MoveNext()
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)

断点命中: var existing = await context.Votes.FirstOrDefaultAsync(x =&gt; x.UserId == userId &amp;&amp; x.ArticleId == articleId);

我也收到此错误:Message [string]:"A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe."

有哪些可能的解决方案?

编辑 1: 这就是我设置上下文的方式: 在 Startup.cs 中,我配置了上下文:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ArticlesContext>(options => 
        options.UseMySql(Configuration.GetConnectionString("ArticlesDB")));
...

然后我将它注入到包含类的构造函数中:

private ArticlesContext context;
private ILoggingApi loggingApi;

public VoteRepository(ArticlesContext context, ILoggingApi loggingApi)
{
    this.context = context;
    this.loggingApi = loggingApi;
}

编辑 2: 我一直在等待通过以下方式到达控制器:

public async Task<bool> Upvote(int articleId)
{
    return await this.votesRepository.Upvote(userId, articleId);
}

然后在控制器中...

[HttpPost]
[Route("upvote")]
public async Task<IActionResult> Upvote([FromBody]int articleId)
{
    var success = await votesService.Upvote(articleId);
    return new ObjectResult(success);
}

编辑 3:

我已将我的服务/存储库更改为瞬态而不是单例,但现在我遇到了另一个问题:

public int getCurrentUserId()
{
    if (!httpContextAccessor.HttpContext.User.HasClaim(c => c.Type == "UserId"))
    {
        return -1;
    }

这是同样的异步问题 - 但这次,HttpContext 为空。 我通过

注入上下文访问器
public UserService(IUserRepository userRepository, IHttpContextAccessor httpContextAccessor)
{
    this.userRepository = userRepository;
    this.httpContextAccessor = httpContextAccessor;
}

Answer: IHttpContextAccessor 需要注册为单例而非瞬态 services.AddSingleton&lt;IHttpContextAccessor, HttpContextAccessor&gt;();

【问题讨论】:

  • 你能说明你是如何在容器上注册你的上下文的吗?
  • 这感觉就像你在调用异步方法而不等待它们让它们并行运行?
  • @Klinger 我已经通过编辑更新了答案
  • @Pawel 我不确定你的意思,但我已经在编辑#2 中发布了代码路径
  • 我看到了与此类似的问题 - 我最终不得不放弃 Async 以使用特定方法,因为它是唯一可行的方法。我所有的 DI 注册都是暂时的。我只能在测试服务器上重现这个问题(它很慢,并且有一个巨大的数据库表可供读取)。失败的 API 调用会同时进行 2 个查询并将它们组合在一起(按设计)。可能是问题的原因,但仍然使用异步没有明显的解决方案。

标签: c# entity-framework async-await entity-framework-core


【解决方案1】:

应使用 Scoped 生命周期将实体框架添加到服务容器中,应将 repo 和服务配置为瞬态,以便根据需要创建和注入新实例,并保证实例不会被重用。

EF 应该有范围,以便在每个请求上创建它并在请求结束后释放它。

按照这些准则,我认为将注入的实例存储在控制器构造函数上没有问题。控制器应该在每个请求上实例化,并在请求结束时连同所有作用域注入的实例一起处理。

This Microsoft docs page explains all this.

【讨论】:

  • 我已将所有服务和存储库切换为临时的。如何将 ef 切换为范围?我正在寻找它,但不知何故我错过了它
  • 更改后你测试了吗?
  • 所以它运行了原始错误,但现在还有另一个问题。你能看看编辑#3吗?
  • @ShermanS 你注册访问者了吗?这篇 SO 帖子可能会有所帮助:stackoverflow.com/questions/31243068/access-httpcontext-current
  • 我成功了 - 我将 IHttpContextAccessor 注册为单例而不是瞬态。
【解决方案2】:

其他一些代码同时使用相同的context。检查this question and answer

如果包含Upvote 方法的类是单例的 - 请检查您是否没有在构造函数中存储context,而是应该从IServiceProvider (HttpContext.RequestServices) 为每个请求(范围)获取它,或者传递它作为参数。

【讨论】:

  • 我可以通过将类配置为作用域或瞬态来解决这个问题吗?这个类不需要是一个单例,那么最好的方法是什么?
  • 用“范围”注册它们就足够了。在一个 HTTP 请求期间,您将能够在所有“地方”接收相同的实例,这不会与来自其他/并行请求的实例混合。
猜你喜欢
  • 2020-05-16
  • 2020-07-19
  • 2017-04-02
  • 2017-08-16
  • 2019-02-25
  • 2021-12-13
  • 1970-01-01
  • 2021-07-14
相关资源
最近更新 更多