简体   繁体   English

实体框架核心在异步方法上失败

[英]Entity Framework core failing on async methods

I'm using async methods with EF Core - getting the context through the built in DI 我在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)
    ...

which is ran when someone upvotes an article. 当有人给文章投赞成票时运行。

Everything runs fine if this function gets ran one at a time (one after another). 如果一次运行一次(一个接一个),则一切运行正常。

When I hit this function several times at the same time, I get this exception: 当我同时多次点击此函数时,出现此异常:

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)

The breakpoint hits: var existing = await context.Votes.FirstOrDefaultAsync(x => x.UserId == userId && x.ArticleId == articleId); 断点命中: var existing = await context.Votes.FirstOrDefaultAsync(x => x.UserId == userId && x.ArticleId == articleId);

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

What are some possible solutions? 有哪些可能的解决方案?

Edit 1: This is how I'm setting up the context: In Startup.cs, I configure the context: 编辑1:这就是我设置上下文的方式:在Startup.cs中,我配置上下文:

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

And then I inject it in the constructor of the containing class: 然后将其注入包含类的构造函数中:

private ArticlesContext context;
private ILoggingApi loggingApi;

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

Edit 2: I'm awaiting all the way down to the controller via: 编辑2:我一直在等待通过以下方式到达控制器:

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

And then in the controller... 然后在控制器中...

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

Edit 3: 编辑3:

I've changed my services/repos to be transient instead of singletons, but now I'm running into another issue with : 我已经将服务/存储库更改为暂时的而不是单例的,但是现在我遇到了另一个问题:

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

It's the same async issue - but this time, HttpContext is null. 这是相同的异步问题-但这次HttpContext为空。 I'm injecting the context accessor via 我通过注入上下文访问器

public UserService(IUserRepository userRepository, IHttpContextAccessor httpContextAccessor)
{
    this.userRepository = userRepository;
    this.httpContextAccessor = httpContextAccessor;
}

Answer: IHttpContextAccessor needs to be registered as a singleton an not transient services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); 答:IHttpContextAccessor需要注册为单例而不是临时services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

Entity framework should be added to the services container using the Scoped lifetime, repo and services should be configured as transient so that a new instance is created and injected as needed and guarantees that instances are not reused. 应该使用“作用域”生存期将实体框架添加到服务容器中,将回购和服务配置为临时的,以便根据需要创建和注入新实例,并确保不重复使用实例。

EF should be scoped so that it is created on every request and disposed once the request ends. EF的范围应使其在每个请求上创建,并在请求结束后将其处置。

Following those guidelines I don't see a problem in storing the injected instances on the controller constructor. 遵循这些准则,将注入的实例存储在控制器构造函数中不会出现问题。 The controller should be instantiated on every request and disposed, along with all scoped injected instances, at the end of the request. 应该在每个请求上实例化控制器,并在请求结束时将其与所有范围内的注入实例一起处置。

This Microsoft docs page explains all this. 此Microsoft文档页面解释了所有这一切。

Some other code use same context at same time. 其他一些代码同时使用相同的context Check this question and answer . 检查此问题并回答

If class that contains Upvote method is singleton - check that you do not store context in constructor, instead you should obtain it from IServiceProvider ( HttpContext.RequestServices ) for each request (scope), or pass it as parameter. 如果包含Upvote方法的类为singleton-请检查是否没有在构造函数中存储context ,而是应针对每个请求(范围)从IServiceProviderHttpContext.RequestServices )获取context ,或将其作为参数传递。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM