实体框架核心在异步方法

时间:2016-12-21 03:03:47

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

我正在使用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 => x.UserId == userId && 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为null。 我正在通过

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

答案:IHttpContextAccessor需要注册为单例而非瞬态 services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

2 个答案:

答案 0 :(得分:3)

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

EF应该作为范围,以便在每个请求上创建它,并在请求结束后处理。

遵循这些指导原则,我没有看到将注入的实例存储在控制器构造函数上的问题。控制器应在每个请求上实例化,并在请求结束时与所有作用域注入的实例一起处理。

This Microsoft docs page explains all this.

答案 1 :(得分:0)

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

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