c# - 我真的需要在这里创建一个新的交易吗?
问题描述
我正在从 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
块中检索用户以使其正常工作吗?
技术:
- 实体框架 6.0
- ASP.NET MVC 5
解决方案
您是否需要显式事务:不需要。如果您的 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();
//....
从那里调用 Context SaveChanges 时,该用户被 Context 跟踪并将被持久化。处理它的更丑陋的方法是检查 thisUser 是否被上下文跟踪(否)或者具有相同 PK 的用户是否被上下文跟踪(否,如果是新的 DbContext),如果不是,则将用户附加到那个上下文,但是用户实例需要首先与它可能仍附加到的任何 DbContext 实例分离。凌乱。
我不喜欢初始化模块级 DbContext,而是确保实例在所需范围内被实例化和处理。模块级上下文使得预测方法链变得更加困难,当某些方法决定调用 SaveChanges 时,可能会无意中保存更改,并导致显式事务的奇怪放置等尝试和规范化行为。使用块使这更容易。如果您想 DI 上下文,那么我建议考虑使用存储库模式和/或 DbContextFactory/UnitOfWork 依赖项来启用模拟结果(存储库)或模拟 DbContext(工厂)。
我的首选模式是 Repository (non-generic) /w Mehdime 工作单元的 DbContextScopeFactory/Locator 模式。
推荐阅读
- python - 寻找 RSS 和 R-Squared
- android - 如果 Bluestacks App Player 上没有“启用 USB 调试”选项,如何使用 ADB?
- python - Ipython 在使用 sympy 绘图后冻结
- c - 即使未分配所有 scanf() 值也执行程序
- postgresql - PostgreSQL 安全本地 (pg_hba.conf )
- matlab - Matlab太多输入参数以及如何设置默认值
- java - setText firebase TimeStamp 到 Android 中的 TextView
- typescript - 过滤 HTTP 标头并将它们在正文中 POST 到下游目的地
- powershell - 从 powershell 输出中获取特定的进程 ID
- signalr - SignalR,如果集线器类保留在其他库项目中,如何调用集线器连接?