2016-12-21 54 views
0

我使用的是EF核心异步方法 - 让背景下,通过内置的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) 
    ... 

这是跑,当有人upvotes的文章。

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

当我打这个函数多次在同一时间,我得到这个异常:

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:

我已经改变了我的服务/回购来是短暂的,而不是单身,但我现在m遇到另一个问题:

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

这是相同的异步问题 - 但这个t ime,HttpContext为null。 我通过

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

回答注入的背景下访问:IHttpContextAccessor需要注册为单身的不是暂时的 services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

+0

你能说明你是如何在容器上注册上下文的吗? – Klinger

+0

这感觉就像你在调用异步方法而没有等待它们让它们并行运行? – Pawel

+0

@Klinger我更新了编辑的答案 –

回答

1

实体框架应该使用Scoped生命周期添加到服务容器中,回购和服务应该配置为临时的,这样就可以根据需要创建并注入新实例,并确保实例不会被重用。

EF的范围应该是在每个请求上创建并在请求结束后处理。

遵循这些准则,我没有看到在控制器构造函数中存储注入实例的问题。控制器应该在每个请求中实例化,并在请求结束时处理,以及所有范围内注入的实例。

This Microsoft docs page explains all this.

+0

我已将我的所有服务和回购转换为瞬态。如何将ef切换到作用域?我试图找到它,但是我很想念它 –

+0

您在更改后测试过吗? – Klinger

+0

因此它贯穿原始错误,但现在有另一个问题。你可以看看编辑#3吗? –

0

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

如果包含Upvote方法类是单 - 检查你不存储在构造context,而应该从IServiceProviderHttpContext.RequestServices)每个请求(范围)获得它,或者把它作为参数。

+0

我可以通过将类配置为scoped或transient来解决此问题吗?这个类不需要是单身人士,那么最好的解决办法是什么? –

+0

向他们注册“范围”应该足够了。在一个HTTP请求期间,您将能够在所有“地方”接收相同的实例,并且这不会与来自其他/并行请求的实例混合。 – Dmitry