2016-06-07 2 views
0

Ситуации

У нас есть контроллер, где пользователи могут представить любое количество адресов электронной почты, чтобы пригласить других (потенциальные) членов в качестве друзей. Если адрес не найден в базе данных, мы отправляем сообщение E-Mail этому пользователю. Поскольку пользователю не нужно ждать завершения этого процесса, чтобы продолжить работу, это выполняется асинхронно.Запись в БД после контроллера была захоронена

Отправка электронных писем может занять много времени, если серверы будут реагировать медленно, вниз или перегружены. Отправитель E-Mail должен обновить базу данных в соответствии с состоянием, полученным с сервера электронной почты, так что, например, установка запроса друга в состояние «Ошибка», когда происходит постоянный сбой, например, если адрес не существует. Для этой цели компонент E-Mail реализует функцию SendImmediateAsync(From,To,Subject,Content,Callback,UserArg). После того, как сообщение было доставлено (или оно не выполнено), Callback вызывается с определенными аргументами о состоянии доставки.

Когда он в конечном итоге вызывает делегата, объект DbContext уже удален (поскольку контроллер был слишком), и я не могу вручную создать новый, используя new ApplicationDbContext(), потому что нет конструктора, который принимает строку подключения.

Вопрос

Как я могу писать в базу данных долго после того, как контроллер был расположен? Я еще не понял, как вручную создать объект DbContext для себя. Объект типа ApplicationDbContext передается конструктору контроллера, и я надеялся, что смогу создать его для себя, но у конструктора нет аргументов, которые я могу предоставить (например, строка подключения). Я хочу избегать вручную создавать SQL-соединение и собирать инструкции INSERT вручную и предпочел бы работать с уже созданной моделью сущности.

Код

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

[Authorize] 
public class MembersController : Controller 
{ 
    private ApplicationDbContext _context; 

    public MembersController(ApplicationDbContext context) 
    { 
     _context = context; 
    } 

    [HttpPost] 
    [ValidateAntiForgeryToken] 
    public IActionResult Friends() 
    { 
     MailHandler.SendImmediateAsync(FROM,TO,SUBJECT,CONTENT, 
      delegate (Guid G, object any) 
      { 
       //THIS IS NOT WORKING BECAUSE _context IS DISPOSED 
       var ctx = _context; 

       Guid Result = (Guid)any; //user supplied argument 

       if (G != Guid.Empty) 
       { 
        ctx.MailConfirmation.Add(new MailConfirmation() 
        { 
         EntryId = Result, 
         For = EntryFor.FriendRequest, 
         Id = G 
        }); 

        if (G == MailHandler.ErrorGuid) 
        { 
         var frq = _context.FriendRequest.SingleOrDefault(m => m.Id == Result); 
         frq.Status = FriendStatus.Error; 
         ctx.Update(frq); 
        } 
        ctx.SaveChanges(); 
       } 
      }, req.Id); 
     //rendering view 
    } 
} 

ответ

0

Прежде всего, когда вы используете EF Core с впрыском зависимостей ASP.NET Core, каждый экземпляр DbContext имеет область действия по запросу, если только вы не указали иначе в «.AddDBContext». Это означает, что после завершения HTTP-запроса вы не должны пытаться повторно использовать экземпляр DbContext. См. https://docs.asp.net/en/latest/fundamentals/dependency-injection.html#service-lifetimes-and-registration-options

DbContextOptions, с другой стороны, являются одноточечными и могут быть повторно использованы в разных запросах.

Если вам нужно закрыть HTTP-запрос и выполнить действие после этого, вам необходимо создать новую область DbContext и управлять ее жизнью.

Во-вторых, вы можете перегрузить базовый конструктор DbContext и напрямую перейти в DbContextOptions. См. https://docs.efproject.net/en/latest/miscellaneous/configuring-dbcontext.html

Вместе это решение может выглядеть.

public class MembersController : Controller 
{ 
    private DbContextOptions<ApplicationDbContext> _options; 

    public MembersController(DbContextOptions<ApplicationDbContext> options) 
    { 
     _options = options; 
    } 

    [HttpPost] 
    [ValidateAntiForgeryToken] 
    public IActionResult Friends() 
    { 
     MailHandler.SendImmediateAsync(FROM,TO,SUBJECT,CONTENT, CreateDelegate(_options) req.Id); 
    } 

    private static Action<Guid, object> CreateDelegate(DbContextOptions<ApplicationDbContext> options) 
    { 
     return (G, any) => 
     { 
      using (var context = new ApplicationDbContext(options)) 
      { 
       //do work 
       context.SaveChanges(); 
      } 
     }; 
    } 
} 

public class ApplicationDbContext : DbContext 
{ 
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base (options) { } 

    // the rest of your stuff 
} 

Это предполагает, конечно, что ваш класс «MailHandler» правильно используя параллелизм для запуска делегата, чтобы он не блокировать поток обработки запроса HTTP.

+0

Такой вид работы. Проблема заключается в том, что я не могу изменить «ApplicationDbContext» для наследования из DbContext или разрывов сайта по какой-либо причине. Я закончил это: создайте «AsyncDbContext», который в основном имеет тот же контент, что и «ApplicationDbContext», но наследует от «DbContext». Затем я сменил конструктор моего контроллера-члена на: 'public MembersController (DbContextOptions options, ApplicationDbContext context)', потому что я до сих пор не могу вручную создать экземпляр AppicationDbContext. Таким образом, я получаю оба. Контекст для запроса и объекта options для асинхронных контекстов. – AyrA

+0

Что наследует ваше определение ApplicationDbContext? – natemcmaster

+0

ef создает его как «открытый класс ApplicationDbContext: IdentityDbContext ' – AyrA

0

Почему бы просто не передать dbContext как userArgs в SendImmediateAsync? Тогда dbContext не будет удаляться и может быть передан обратно при обратном вызове. Я уверен, что это сработает.

+0

Я пробовал и не работал. Я думаю, что инфраструктура Entity явно использует ее с контроллером. – AyrA

+0

Вау, это довольно интересно. Внутри контроллера вы можете создать новый объект dbContext, о котором контроллер ничего не знает и не передает его в userArgs? Возможно, это сработает? –

+0

Нет, я не могу. Как вы видите, конструктор получает объект типа «ApplicationDbContext». Когда я пытаюсь создать свой собственный с помощью 'new ApplicationDbContext()', он будет создан, но как только вы захотите прочитать или записать из базы данных, вы получите эту ошибку: _No поставщики баз данных настроены. Настройте поставщика базы данных путем переопределения OnConfiguring в вашем классе DbContext или в методе AddDbContext при настройке services._ ** Примечание: ** ApplicationDbContext - это тип, созданный самой структурой сущности и имеющий только пустой конструктор – AyrA

Смежные вопросы