2016-12-21 85 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