在處理完控制器後寫入DB

asp.net-core-mvc entity-framework-core

情況

我們有一個控制器,用戶可以在其中提交任意數量的電子郵件地址,以邀請其他(潛在)成員作為朋友。如果在數據庫中找不到地址,我們會向該用戶發送電子郵件消息。由於用戶不必等待此過程完成以便繼續工作,因此這是異步完成的。

如果服務器響應緩慢,關閉或過載,發送電子郵件可能需要很長時間。電子郵件發件人應根據從電子郵件服務器收到的狀態更新數據庫,例如,當發生永久性故障時,例如如果地址不存在,將朋友請求設置為“錯誤”狀態。為此,電子郵件組件實現SendImmediateAsync(From,To,Subject,Content,Callback,UserArg) 。消息傳遞(或失敗)後,將使用有關傳遞狀態的某些參數調用回調。

當它最終調用委託時,DbContext對像已經被釋放(因為控制器也已經被處理)並且我無法使用new ApplicationDbContext()手動創建新的,因為沒有構造函數接受連接字符串。

處理完控制器後,如何長時間寫入數據庫?我還沒想出如何為自己手動創建一個DbContext對象。 ApplicationDbContext類型的對像被傳遞給Controller的構造函數,我希望我可以為自己實例化一個,但構造函數沒有我可以提供的參數(例如連接字符串)。我想避免手動創建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
    }
}

一般承認的答案

首先,當您使用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請求的線程。


熱門答案

為什麼不直接將dbContext作為userArgs傳遞給SendImmediateAsync?然後dbContext將不會被釋放,並且可以在您進行回調時傳回。我很確定這應該有效。



Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因