Правильная обработка DbContexts в ASP.NET основной WebApi

здесь небольшая путаница. Я не уверен, правильно ли я обрабатываю свой DbContext по всему WebApi. У меня есть некоторые контроллеры, которые выполняют некоторые операции над моей БД (вставки/обновления с EF), и после выполнения этих действий я запускаю событие. В моих EventArgs (у меня есть пользовательский класс, который наследуется от EventArgs) передаю мой DbContext и я использую его в обработчике событий для регистрации этих операций (в основном я просто регистрирую аутентифицированные запросы API пользователя).

в обработчике событий когда я пытаюсь зафиксировать свои изменения (await SaveChangesAsync) я получаю сообщение об ошибке : "использование удаленного объекта...etc " в основном замечая меня, что в первый раз я использую await в своем async void (огонь и забыть) я уведомляю вызывающего абонента, чтобы избавиться от объекта Dbcontext.

не используя async works и единственное обходное решение, которое я mangaged, чтобы потушить, - это создание другого экземпляра DbContext путем получения SQLConnectionString EventArgs переданного DbContext.

перед публикацией I сделал небольшое исследование, основанное на моей проблеме Entity Framework избавляется от асинхронных контроллеров в Web api / MVC

вот как я передаю параметры моему OnRequestCompletedEvent

OnRequestCompleted(dbContext: dbContext,requestJson: JsonConvert.SerializeObject);

это OnRequestCompleted() декларация

 protected virtual void OnRequestCompleted(int typeOfQuery,PartnerFiscalNumberContext dbContext,string requestJson,string appId)
        {
       RequestCompleted?.Invoke(this,new MiningResultEventArgs()
          {
            TypeOfQuery = typeOfQuery,
            DbContext   = dbContext,
            RequestJson = requestJson,
            AppId = appId
          });
        }

и вот как я обрабатываю и использую мой dbContext

var appId = miningResultEventArgs.AppId;
var requestJson = miningResultEventArgs.RequestJson;
var typeOfQuery = miningResultEventArgs.TypeOfQuery;
var requestType =  miningResultEventArgs.DbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery).Result;
var apiUserRequester =  miningResultEventArgs.DbContext.ApiUsers.FirstAsync(x => x.AppId == appId).Result;

var apiRequest = new ApiUserRequest()
{
    ApiUser = apiUserRequester,
    RequestJson = requestJson,
    RequestType = requestType
};

miningResultEventArgs.DbContext.ApiUserRequests.Add(apiRequest);
await miningResultEventArgs.DbContext.SaveChangesAsync();

С помощью SaveChanges вместо SaveChangesAsync все работает. Моя единственная идея-создать другой dbContext, передав предыдущий DbContext Строка подключения SQL

var dbOptions = new DbContextOptionsBuilder<PartnerFiscalNumberContext>();
dbOptions.UseSqlServer(miningResultEventArgs.DbContext.Database.GetDbConnection().ConnectionString);

    using (var dbContext = new PartnerFiscalNumberContext(dbOptions.Options))
    {
        var appId = miningResultEventArgs.AppId;
        var requestJson = miningResultEventArgs.RequestJson;
        var typeOfQuery = miningResultEventArgs.TypeOfQuery;


        var requestType = await dbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery);
        var apiUserRequester = await dbContext.ApiUsers.FirstAsync(x => x.AppId == appId);

        var apiRequest = new ApiUserRequest()
        {
            ApiUser = apiUserRequester,
            RequestJson = requestJson,
            RequestType = requestType
        };

        dbContext.ApiUserRequests.Add(apiRequest);
        await dbContext.SaveChangesAsync();
    }

последний фрагмент кода - это всего лишь небольшой тест для проверки моего предположения, в основном я должен передать строку подключения SQL вместо объекта DbContext.

я не уверен (с точки зрения лучшей практики), должен ли я передать строку подключения и создать новый объект dbContext (и утилизировать его с помощью using пункт) или если я должен использовать/иметь другое мышление по этому вопросу.

из того, что я знаю, используя DbContext должно быть сделано для ограниченного набора операций, а не для нескольких целей.

редактировать 01

я собираюсь более подробно рассказать, что я делал внизу.

я думаю, у меня есть идея, почему эта ошибка происходит.

у меня есть 2 контроллера Тот, который получает JSON, и после его де-сериализации я возвращаю JSON вызывающему и другому контроллеру, который получает JSON, который инкапсулирует список объектов, которые я повторяю в асинхронный способ, возвращающий Ok() статус.

контроллеры объявляются как async Task<IActionResult> и оба имеют async выполнение 2 аналогичных методов.

первый, который возвращает JSON выполняет этот метод

await ProcessFiscalNo(requestFiscalView.FiscalNo, dbContext);

второй (тот, который вызывает эту ошибку)

foreach (string t in requestFiscalBulkView.FiscalNoList)
       await ProcessFiscalNo(t, dbContext);

оба метода (определенные ранее) запускают событие OnOperationComplete() В рамках этого метода я выполняю код с начала моего сообщения. В ProcessFiscalNo метод я не использую контексты использования и не удаляю переменную dbContext. В рамках этого метода я совершаю только 2 основных действия: обновление существующей строки sql или ее вставка. Для редактирования контекстов я выбираю строку и помечаю строку измененной меткой, делая это

dbContext.Entry(partnerFiscalNumber).State = EntityState.Modified;

или вставив строку

dbContext.FiscalNumbers.Add(partnerFiscalNumber);

и, наконец, я выполняю await dbContext.SaveChangesAsync();

ошибка всегда срабатывает в EventHandler ( один подробный @ the начало потока) во время await dbContext.SaveChangedAsync() что довольно странно, так как 2 строки до этого я жду чтения на моей БД с EF.

 var requestType = await dbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery);
 var apiUserRequester = await dbContext.ApiUsers.FirstAsync(x => x.AppId == appId);

 dbContext.ApiUserRequests.Add(new ApiUserRequest() { ApiUser = apiUserRequester, RequestJson = requestJson, RequestType = requestType });

  //this throws the error
 await dbContext.SaveChangesAsync();

по какой-то причине вызов await в обработчике событий уведомляет вызывающего абонента об удалении

1 ответов


относительно того, почему вы получаете ошибку

как указано в комментариях @set-fu DbContext is не потокобезопасными.

В дополнение к этому, так как есть нет явного менеджмент жизни DbContext ваш DbContext будет удален, когда сборщик мусора сочтет нужным.

судя по вашему контексту, и Ваше упоминание о запрос области DbContext и Я полагаю, вы DI ваш DbContext в конструкторе вашего контроллера. И поскольку ваш DbContext является областью запроса, он будет удален, как только ваш запрос закончится,

но поскольку вы уже уволили и забыли свои onrequestcompleted события, нет никакой гарантии, что ваш DbContext не будет удален.

С этого момента тот факт, что один из наших методов преуспевает, а другой терпит неудачу, я думаю, провидец "удачи". Один метод может быть быстрее, чем другой, и завершает до сборщик мусора удаляет DbContext.

что вы можете сделать, это изменить тип возвращаемого события

async void

до

async Task<T>

таким образом, вы можете ждать свой RequestCompleted задание внутри вашего контроллера, чтобы закончить, и это гарантирует вам, что ваш контроллер / DbContext не будет удален, пока ваш RequestCompleted задание выполнено.

о правильная обработка DbContexts

здесь есть две противоречащие рекомендации microsoft, и многие люди используют DbContexts в совершенно расходящейся манере.

  1. одна рекомендация - "Dispose DbContexts как только возможно" потому что наличие живого DbContext занимает ценные ресурсы, такие как db соединений и т. д....
  2. другие государства, которые один DbContext на запрос очень рекомендуется

они противоречат друг другу , потому что если Ваш запрос делает много несвязанного с материалом Db, то ваш DbContext сохраняется без причины. Таким образом, это пустая трата, чтобы сохранить ваш DbContext живым, пока ваш запрос просто ждет случайных вещей, чтобы сделать...

так много людей, которые следуют Правило 1 имеют свои DbContexts внутри их "шаблон репозитория" и create новый экземпляр на запрос базы данных

        public User GetUser(int id)
        {
          User usr = null;
          using (Context db = new Context())
          {
              usr = db.Users.Find(id);
          }
          return usr;
         }

Они просто получают свои данные и распоряжаются контекстом как можно скорее. Это считается много люди приемлемая практика. Хотя это имеет преимущества, занимая ваши ресурсы БД за минимальное время, он явно жертвует всеми UnitOfWork и "кэширование" candy EF может предложить.

таким образом, рекомендация Microsoft об использовании контекста 1 Db для каждого запроса ясно основанный на факте что ваше UnitOfWork scoped не познее 1 запрос.

но во многих случаях, и я считаю твоем случае это не так. Я считаю ведение журнала отдельная UnitOfWork, таким образом, имея новый DbContext для ведения журнала после запроса, полностью приемлема (и это практика, которую я также использую).

пример из моего проекта у меня есть 3 DbContexts в 1 запросе на 3 единицы работы.

  1. Do Работа
  2. Писать Журналы
  3. отправка писем администраторам.