下面的代码看起来有点奇怪:
var loanViewModel = loanEditorViewModel.LoanViewModel;
loanViewModel.LoanProduct = LoanProductService.GetLoanProductById(loanViewModel.LoanProductId); // <-- don't want to add to this table in database
loanViewModel.Borrower = BorrowerService.GetBorrowerById(loanViewModel.BorrowerId); //<-- don't want to add to this table in database
Models.Loans.Loan loan = AutoMapper.Mapper.Map<Models.Loans.Loan>(loanEditorViewModel.LoanViewModel);
您正在视图模型上设置实体引用,然后调用 automapper。 ViewModel 不应保存实体引用,并且自动映射器应有效地忽略任何引用的实体,而仅映射正在创建的实体结构。 Automapper 将根据传入的数据创建新实例。
相反,类似这样的事情应该按预期工作:
// Assuming these will throw if not found? Otherwise assert that these were returned.
var loanProduct = LoanProductService.GetLoanProductById(loanViewModel.LoanProductId);
var borrower = BorrowerService.GetBorrowerById(loanViewModel.BorrowerId);
Models.Loans.Loan loan = AutoMapper.Mapper.Map<Models.Loans.Loan>(loanEditorViewModel.LoanViewModel);
loan.LoanProduct = loanProduct;
loan.Borrower = borrower;
Edit:
接下来要检查的是您的服务是否使用完全相同的 DbContext 引用。您是否将依赖注入与 IoC 容器(例如 Autofac 或 Unity)一起使用?如果是这样,请确保将 DbContext 设置为注册为“每个请求实例”或类似的生命周期范围。如果服务有效地新建了一个新的 DbContext,那么 LoanService DbContext 将不知道由另一个服务的 DbContext 获取的产品和借款人的实例。
如果您没有使用 DI 库,那么您应该考虑添加一个。否则,您将需要更新服务以在每次调用时接受单个 DbContext,或者利用工作单元模式(例如 Mehdime 的 DbContextScope)来促进服务从工作单元解析其 DbContext。
例如确保相同的 DbContext:
using (var context = new MyDbContext())
{
var loanProduct = LoanProductService.GetLoanProductById(context, loanViewModel.LoanProductId);
var borrower = BorrowerService.GetBorrowerById(context, loanViewModel.BorrowerId);
Models.Loans.Loan loan = AutoMapper.Mapper.Map<Models.Loans.Loan>(loanEditorViewModel.LoanViewModel);
loan.LoanProduct = loanProduct;
loan.Borrower = borrower;
LoanService.AddNewLoan(context, loan);
}
如果您确定所有服务都提供相同的 DbContext 实例,那么您的 Entities.Add() 方法中可能会发生一些奇怪的情况。老实说,您的解决方案看起来对像 CRUD 创建和关联操作这样简单的事情有太多抽象。这看起来像是一个不从最简单的解决方案开始就对 DRY 进行过早代码优化的情况。代码可以更简单地只限定 DbContext 的范围、获取适用的实体、创建新实例、关联、添加到 DbSet 和 SaveChanges。抽象出对基本操作(例如通过 ID 获取引用)的调用没有任何好处。
public ActionResult Add(Models.ViewModels.Loans.LoanEditorViewModel loanEditorViewModel)
{
if (!ModelState.IsValid)
return View(loanEditorViewModel);
var loanViewModel = loanEditorViewModel.LoanViewModel;
using (var context = new AppContext())
{
var loanProduct = context.LoanProducts.Single(x => x.LoanProductId ==
loanViewModel.LoanProductId);
var borrower = context.Borrowers.Single(x => x.BorrowerId == loanViewModel.BorrowerId);
var loan = AutoMapper.Mapper.Map<Loan>(loanEditorViewModel.LoanViewModel);
loan.LoanProduct = loanProduct;
loan.Borrower = borrower;
context.SaveChanges();
}
return RedirectToAction("Index");
}
撒上一些异常处理,就完成了并除尘。没有分层的服务抽象。从那里,您可以通过使用 Autofac 等 IoC 容器来管理上下文和/或引入存储库/服务层 /w UoW 模式,从而使操作可测试。上述内容将作为该行动的最低可行解决方案。任何抽象等都应该在之后应用。在涂抹油之前用铅笔勾勒出草图。 :)
使用 Mehdime 的 DbContextScope 它看起来像:
public ActionResult Add(Models.ViewModels.Loans.LoanEditorViewModel loanEditorViewModel)
{
if (!ModelState.IsValid)
return View(loanEditorViewModel);
var loanViewModel = loanEditorViewModel.LoanViewModel;
using (var contextScope = ContextScopeFactory.Create())
{
var loanProduct = LoanRepository.GetLoanProductById( loanViewModel.LoanProductId).Single();
var borrower = LoanRepository.GetBorrowerById(loanViewModel.BorrowerId);
var loan = LoanRepository.CreateLoan(loanViewModel, loanProduct, borrower).Single();
contextScope.SaveChanges();
}
return RedirectToAction("Index");
}
就我而言,我利用存储库模式,该模式使用 DbContextScopeLocator 来解析它的 ContextScope 以获取 DbContext。 Repo 管理数据的获取并确保为实体的创建提供创建完整且有效的实体所需的所有必需数据。我选择每个控制器一个存储库,而不是像每个实体的通用模式或存储库/服务之类的东西,因为在我看来,这可以更好地管理单一责任原则,因为代码只有一个更改的理由(它服务于控制器,而不是在许多控制器之间共享)具有潜在不同关注点的控制者)。单元测试可以模拟存储库以提供预期的数据状态。回购获取方法返回IQueryable
以便消费者逻辑可以确定它想要如何消费数据。