Вторая операция началась в этом контексте перед предыдущей - PullRequest
0 голосов
/ 07 января 2020

У меня распространенная ошибка при использовании EntityFramework, но я не могу выяснить причину. Ошибка:

"Вторая операция началась в этом контексте перед завершением предыдущей операции. Обычно это вызвано тем, что разные потоки используют один и тот же экземпляр DbContext. Для получения дополнительной информации о том, как избежать проблем с многопоточностью DbContext, см. https://go.microsoft.com/fwlink/?linkid=2097913. "

Я использую промежуточное ПО GraphQL для обработки запросов.

Отслеживание стека:

at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
   at Test.DataAccess.Intranet.Repository.Admin.UserModule.Query.GetUserDataQuery.Execute(String username) in C:\Projects\GIT Projects\SWDi\Intranet\Test.Backend\Test.DataAccess.Intranet\Repository\Modules\Admin\UserManagement\Query\Implementation\GetUserDataQuery.cs:line 34
   at Test.Web.Base.Services.UserService.GetUserContext(HttpContext context) in C:\Projects\GIT Projects\Test\Intranet\Test.Backend\Test.Web.Base\Services\UserService.cs:line 55
   at Test.API.Middleware.GraphQLMiddleware.InvokeAsync(HttpContext httpContext) in C:\Projects\GIT Projects\Test\Intranet\Test.Backend\Test.Web.API\Middleware\GraphQLMiddleware.cs:line 71
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Test.Web.API.Middleware.NtlmAndAnonymousSetupMiddleware.Invoke(HttpContext context) in C:\Projects\GIT Projects\Test\Intranet\SWDi.Backend\Test.Web.API\Middleware\NtlmAndAnonymousSetupMiddleware.cs:line 39
   at Test.API.Middleware.ExceptionMiddleware.Invoke(HttpContext context) in C:\Projects\GIT Projects\Test\Intranet\Test.Backend\Test.Web.API\Middleware\ExceptionMiddleware.cs:line 40"

Здесь я обрабатываю POST-запросы в GraphQLMiddleware.cs:

public async Task InvokeAsync(HttpContext httpContext)
        {
            if (
                (httpContext.Request.Path.StartsWithSegments(CATCHURLPATH) || httpContext.Request.PathBase.StartsWithSegments(CATCHURLPATH))
                && string.Equals(httpContext.Request.Method, CATCHURLMETHOD, StringComparison.OrdinalIgnoreCase))
            {
                string body;

                var userContext = await this.userService.GetUserContext(httpContext);
                using (var streamReader = new StreamReader(httpContext.Request.Body))
                {
                    body = await streamReader.ReadToEndAsync();
                    var request = JsonConvert.DeserializeObject<GraphQLQueryBase>(body);
                    var result = await this.executor.ExecuteAsync(doc =>
                    {
                        doc.ThrowOnUnhandledException = true;
                        doc.Schema = this.schema;
                        doc.Query = request.Query;
                        doc.Inputs = request.Variables.ToInputs();
                        doc.ExposeExceptions = true;
                        doc.UserContext = new Dictionary<string, object>()
                        {
                            { "UserId", userContext.UserId },
                            { "Username", userContext.Username },
                        };
                    }).ConfigureAwait(false);

#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
                    Task.Factory.StartNew(() => this.queryLogMutation.Execute(userContext.UserId, request.Query));
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed

                    httpContext.Response.ContentType = "application/json";
                    await this.writer.WriteAsync(httpContext.Response.Body, result);
                }
            }
            else
            {
                await this.next(httpContext);
            }
        }

Как видите, я звоню на userService.GetUserContext метод, который реализован как

public class UserService : IUserService
{
    private const string CACHEKEY = "userContext_";
    private readonly IMemoryCache cache;
    private readonly IGetUserDataQuery getUserByUsernameQuery;

    /// <summary>
    /// Initializes a new instance of the <see cref="UserService"/> class.
    /// </summary>
    /// <param name="cache">Get the cache from DI.</param>
    /// <param name="getUserByUsernameQuery">The Data Repository from DI.</param>
    public UserService(IMemoryCache cache, IGetUserDataQuery getUserByUsernameQuery)
    {
        this.cache = cache;
        this.getUserByUsernameQuery = getUserByUsernameQuery;
    }

    /// <summary>
    /// Created the user context information.
    /// </summary>
    /// <param name="context">The current Httpcontext.</param>
    /// <returns>The user context.</returns>
    public async Task<UserContext> GetUserContext(HttpContext context)
    {
        UserContext returnContext;

        var username = this.GetUsername(context);

        if (!this.cache.TryGetValue(CACHEKEY + username, out returnContext))
        {
            var dbuser = await this.getUserByUsernameQuery.Execute(username);
            if (dbuser == null)
            {
                throw new NullReferenceException("Username " + username + " not found in user table. Please run AD update or insert user manually.");
            }

            returnContext = new UserContext()
            {
                Username = username,
                UserContextObject = context.User,
                UserId = dbuser.UserId,
            };

            this.cache.Set(CACHEKEY + username, returnContext);
        }

        return returnContext;
    }

    /// <summary>
    /// Splits the username out of a Domain//Username.
    /// </summary>
    /// <param name="context">The current Http Context.</param>
    /// <returns>The username of the given http context identity.</returns>
    internal string GetUsername(HttpContext context)
    {
        return context.User.Identities.First().Name.Split("\\")[1];
    }
}

И, наконец, запрос getUserByUsername:

public class GetUserDataQuery : IGetUserDataQuery
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="GetUserDataQuery"/> class.
        /// </summary>
        /// <param name="context">Intranet Context by DI.</param>
        public GetUserDataQuery(IntranetContext context)
        {
            this.Context = context;
        }

        private IntranetContext Context { get; }

        /// <summary>
        /// Get the Intranet user context using the users (AD) username.
        /// </summary>
        /// <param name="username">The username.</param>
        /// <returns>The DB user context.</returns>
        public async Task<MainUserModel> Execute(string username)
        {
            var userInformation = await this.Context.VUser.Where(x => x.Username == username).SingleOrDefaultAsync();
            var userRights = await this.Context.UserModule.Where(x => x.UserId == userInformation.UserId).Include(x => x.RefModule).ToListAsync();
            var userFunctions = await this.Context.UserModuleFunction.Where(x => x.UserId == userInformation.UserId).Include(x => x.RefModuleFunction).ToListAsync();
            var memberList = await this.Context.UserGroupMember.Where(x => x.UserId == userInformation.UserId).Include(x => x.UserGroup).ToListAsync();
            var isCashier = await this.Context.UserCashbox.Where(x => x.UserId == userInformation.UserId).AnyAsync();
            return new MainUserModel(userInformation, userFunctions, userRights, memberList, isCashier);
        }
    }

Несмотря на то, что я сделал dbContext кратковременным, проверил на наличие забытых асинхронных / ожидающих ключевых слов или даже вложенные асинхронные сообщения, ошибка все еще происходит. Если вам нужна дополнительная информация или есть идеи, пожалуйста, дайте мне знать.

...