我正在从MVC控制器发送电子邮件。
[HttpPost]
public async Task<ActionResult> Send(SendModel model)
{
var userId = HttpContext.User.Identity.GetUserId();
// This method takes current user ID and retrieves the user from the DB
var thisUser = await GetThisApplicationUserAsync();
if (thisUser.FeedbackSendLimit <= 0) return RedirectToActionWithMessage(MessageType.Error, "You can't send emails anymore! You have exceeded your send limit!", "Send");
// Handling the case when the passed model is invalid
if (!ModelState.IsValid) return View(model);
using (var transaction = _dbContext.Database.BeginTransaction())
{
// _dbContext is a DbContext initialized in the controller's constructor
var companiesToSend = _dbContext
.Companies
.Where(x => model.CompanyIds.Contains(x.Id))
.ToArray();
try
{
// Each user has a limit of emails they can send monthly
thisUser.FeedbackSendLimit -= 1;
// Each company has a limit of how many emails we can address them as well
foreach (var company in companiesToSend)
{
company.FeedbackCounter -= 1;
}
var newSend = new FeedbackSend
{
Id = Guid.NewGuid(),
UserId = userId,
SentAt = DateTime.Now,
Companies = companiesToSend
};
_dbContext.FeedbackSends.Add(newSend);
await _dbContext.SaveChangesAsync();
// This generates an HTML email and sends it to specified email address
await SendFeedbackEmailAsync(model.ToEmail, thisUser, companiesToSend);
transaction.Commit();
}
catch (Exception e)
{
transaction.Rollback();
return RedirectToActionWithMessage(MessageType.Error, "An error occurred while trying to send feedback", "Send");
}
}
return RedirectToActionWithMessage(MessageType.Success, "Sent successfully", "Send");
}
这里有两个问题:
1.我真的需要在这里进行交易吗?在这种情况下使用_dbContext.SaveChanges()
是否足够?如果SendFeedbackEmailAsync
失败并且没有发送电子邮件,我使用事务处理将所有内容还原。
transaction.Commit()
似乎没有更新thisUser.FeedbackSendLimit
。我是否应该在交易using
块中检索用户以使其正常工作?技术:
答案 0 :(得分:0)
您是否需要显式事务:否。如果Try块完成并调用SaveChanges,则更改将作为一项有效事务提交。如果捕获到异常,则不会发生SaveChanges,因此在处理Context时将回滚事务。
为什么不保存您的用户更改?这很可能是因为您的用户是由GetThisApplicationUserAsync()方法中的另一个DbContext实例加载的,或者是由AsNoTracking()加载的或以其他方式分离的。
检索数据并执行更新时,请在单个DbContext实例的范围内进行操作。
using (var context = new MyDbContext())
{
var thisUser = context.Users.Where(x => x.Id == userId).Single();
var companiesToSend = context.Companies
.Where(x => model.CompanyIds.Contains(x.Id))
.ToArray();
//....
从那里,当调用上下文SaveChanges时,上下文会跟踪该用户并将其持久化。处理该问题的较丑方法是检查上下文是否跟踪thisUser(否)或上下文跟踪具有相同PK的用户(否,如果有新的DbContext),如果不是,则将该用户附加到该用户上下文,但是首先需要将用户实例从它可能仍附加到的任何DbContext实例中分离出来。凌乱的。
我不喜欢初始化模块级DbContext,而是确保实例被实例化并放置在所需的范围内。模块级上下文使得难以预测方法链,其中某些方法决定调用SaveChanges时可能会无意中保存更改,并导致显式事务的奇数放置,从而试图规范行为。使用块使事情变得容易得多。如果要DI上下文,那么我建议考虑使用Repository模式和/或DbContextFactory / UnitOfWork依赖项来启用对结果(存储库)的模拟或对DbContext(工厂)的模拟。
我的首选模式是存储库(非通用)/ w Mehdime的工作单元的DbContextScopeFactory / Locator模式。