我已经实现了这个使用SendAsync方法发送电子邮件的Windows服务,每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();
}
}
问题:
因此,为了发送和处理电子邮件,我在一个带有tb_Email对象列表的简单循环中运行SendEmail方法,一旦发送每封电子邮件,我就必须更新数据库。 为此,我使用
email.ReadyToSend = false;
emailService.Update(email);
在我的SendCompleted事件中,由于我使用SendAsync,系统继续处理许多电子邮件,但SendCompleted事件可能会稍后触发每封电子邮件。 为了确保它使用唯一的单个dbContext,我在我的UoW实例上使用了一个using语句来获取更新方法。如果我将更新逻辑直接放在SendEmail方法中(这没有任何意义,因为我需要知道电子邮件是否成功发送),这样可以正常工作,但是如果我在几次成功之后将它放入事件中更新,它只是抛出
System.Data.Entity.Core.EntityException:&#39;底层提供程序在Open上失败。&#39;
我不明白当我为每项操作创建新的上下文时,怎么可能。
答案 0 :(得分:0)
很抱歉我必须自己回答,问题是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();
}
}
}