UoW 和 DbContext 在事件中失败

UoW and DbContext fail in an event

我已经实现了这个 Windows 服务,它使用 SendAsync 方法发送电子邮件,每 30 秒一次,每批发送 20 封。我使用的是 EF6 和 SQL Server 2016。这是代码

EmailRepository.cs

public class EmailRepository : IEmailRepository
{
    private BBEntities db = null;


    public EmailRepository(BBEntities db)
    {
        this.db = db;
    }

    public IEnumerable<tb_Email> SelectAll(int batchAge, int batchSize)
    {
        DateTime tDate = DateTime.Now.AddMinutes(batchAge);
        return db.tb_Email.Where(x => x.ReadyToSend.Equals(true) & x.DateSent.Equals(null) & x.DateCreated >= tDate).OrderBy(x => x.DateCreated).Take(batchSize);
    }

    public tb_Email SelectByID(Guid id)
    {
        return db.tb_Email.Find(id);
    }

    public void Update(tb_Email obj)
    {
        db.Entry(obj).State = EntityState.Modified;
    }

    #region IDisposable Support
    private bool disposedValue = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                db.Dispose();
            }
            disposedValue = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    #endregion
}

UnitOfWork.cs

public class UnitOfWork : IUnitOfWork
{

    private readonly BBEntities ctx = new BBEntities();
    private IEmailRepository emailRepository;

    public IEmailRepository EmailRepository
    {
        get
        {

            if (this.emailRepository == null)
            {
                this.emailRepository = new EmailRepository(ctx);
            }
            return emailRepository;
        }
    }

    public void Dispose()
    {
        this.ctx.Dispose();
    }


    public void Commit()
    {
        this.ctx.SaveChanges();
    }

}

EmailService.cs

public class EmailService : IEmailService
{

    private IUnitOfWork unitOfWork;

    public EmailService()
    {
        unitOfWork = new UnitOfWork();
    }

    public List<tb_Email> SelectAll(int batchAge, int batchSize)
    {
        return unitOfWork.EmailRepository.SelectAll(batchAge, batchSize).ToList();
    }

    public tb_Email SelectByID(Guid id)
    {
            return unitOfWork.EmailRepository.SelectByID(id);
    }

    public void Update(tb_Email obj)
    {
        using (unitOfWork = new UnitOfWork())
        {
            unitOfWork.EmailRepository.Update(obj);
            unitOfWork.Commit();
        }
    }

}

SMTPService.cs

   public class SMTPService : ISMTPService
    {

        SmtpClient client;
        MailMessage newMessage;
        EmailService emailService;
        IEventLoggerService MailCheckerLog;


        public SMTPService()
        {
            emailService = new EmailService();
            MailCheckerLog = new EventLoggerService();

        }



        public void SendEmail(tb_Email email)
        {

            try
            {// rest of the code .....

                newMessage = new MailMessage();

                newMessage.Headers.Add("X-Email_Id", email.Id.ToString());


                client.SendCompleted += (sender, e) => SendCompletedCallback(sender, e);

                tb_Email userState = email;


                //
                // if I put the update database logic here, it works fine
                //


                client.SendAsync(newMessage, userState);

            }
            catch (Exception e)
            {
                MailCheckerLog.log("Error in SendComplete event handler - Exception: " + e.Message.ToString() + " -- InnerException: " + e.InnerException.Message, EventLogEntryType.Error);
                client.Dispose();
                newMessage.Dispose();
                throw;
            }

        }
        void SendCompletedCallback(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {

            tb_Email email = (tb_Email)e.UserState;
            Console.WriteLine("----------------------------------" + emailID.Id);
            email.ReadyToSend = false;
            emailService.Update(email);

            client.Dispose();
            newMessage.Dispose();

        }

    }

问题:

因此,为了发送和处理电子邮件,我 运行 SendEmail 方法在一个简单的循环中使用 tb_Email 对象列表,发送每封电子邮件后,我必须更新数据库。 为此,我使用

        email.ReadyToSend = false;
        emailService.Update(email);

在我的 SendCompleted 事件中,由于我使用的是 SendAsync,系统会继续处理许多电子邮件,但是 SendCompleted 事件可能会稍后针对每封电子邮件触发。 为了确保它使用唯一且单一的 dbContext,我在我的 UoW 实例上使用 using 语句来更新方法。如果我直接将我的更新逻辑放在 SendEmail 方法中(这没有任何意义,因为我需要知道电子邮件是否已成功发送),这很好用,但是如果我在几次成功更新后将它放在事件中,它只是抛出

System.Data.Entity.Core.EntityException: 'The underlying provider failed on Open.'

我不明白当我实际为每个操作创建新上下文时怎么可能。

抱歉,我必须自己回答,问题是UoW变量仍在被其他线程使用,所以解决方案是在update方法中为using语句声明一个新变量,如下所示

public class EmailService : IEmailService
{

    private IUnitOfWork unitOfWork;

    public EmailService()
    {
        unitOfWork = new UnitOfWork();
    }

    public List<tb_Email> SelectAll(int batchAge, int batchSize)
    {
        return unitOfWork.EmailRepository.SelectAll(batchAge, batchSize).ToList();
    }

    public tb_Email SelectByID(Guid id)
    {
            return unitOfWork.EmailRepository.SelectByID(id);
    }

    public void Update(tb_Email obj)
    {
        IUnitOfWork unitOfWorkUpdate;
        using (unitOfWorkUpdate = new UnitOfWork())
        {
            unitOfWorkUpdate.EmailRepository.Update(obj);
            unitOfWorkUpdate.Commit();
        }
    }

}