gpt4 book ai didi

c# - Add() 方法在代码优先 Entity Framework 中为链接模型添加重复行

转载 作者:太空狗 更新时间:2023-10-29 21:03:51 27 4
gpt4 key购买 nike

以下是向数据库添加贷款请求的操作:

[HttpPost]
public ActionResult Add(Models.ViewModels.Loans.LoanEditorViewModel loanEditorViewModel)
{
if (!ModelState.IsValid)
return View(loanEditorViewModel);

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);
loanService.AddNewLoan(loan);
return RedirectToAction("Index");
}

以下是 AddNewLoan() 方法:

public int AddNewLoan(Models.Loans.Loan loan)
{
loan.LoanStatus = Models.Loans.LoanStatus.PENDING;
_LoanService.Insert(loan);

return 0;
}

下面是Insert()

的代码
public virtual void Insert(TEntity entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity));

try
{
entity.DateCreated = entity.DateUpdated = DateTime.Now;
entity.CreatedBy = entity.UpdatedBy = GetCurrentUser();

Entities.Add(entity);
context.SaveChanges();
}
catch (DbUpdateException exception)
{
throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
}
}

它在 Loans 表中成功添加了一行,但它也在 LoanProductBorrower 表中添加了行,如我在第一个代码中所示评论。

我检查了多次调用此操作和 Insert 方法的可能性,但它们被调用了一次。

更新

我面临着类似的问题,但在这里的功能问题相反:Entity not updating using Code-First approach

我认为这两者具有相同的更改跟踪原因。但是一个是添加另一个不是更新。

最佳答案

下面的代码看起来有点奇怪:

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);

您正在 View 模型上设置实体引用,然后调用自动映射器。 ViewModel 不应该持有实体引用,automapper 应该有效地忽略任何引用的实体并且只映射正在创建的实体结构。 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;

编辑:

接下来要检查的是您的服务是否使用完全相同的 DbContext 引用。您是否在 Autofac 或 Unity 等 IoC 容器中使用依赖注入(inject)?如果是这样,请确保将 DbContext 设置为注册为 Instance Per Request 或类似的生命周期范围。如果服务有效地新建了一个新的 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 管理获取数据并确保为实体的创建提供创建完整有效实体所需的所有必需数据。我选择每个 Controller 一个存储库,而不是像通用模式或每个实体存储库/服务之类的东西,因为 IMO 这更好地管理单一职责原则,因为代码只有一个更改原因(它服务于 Controller ,而不是在许多 Controller 之间共享具有潜在不同关注点的 Controller )。单元测试可以模拟存储库以提供预期的数据状态。 Repo get 方法返回 IQueryable,以便使用者逻辑可以确定它要如何使用数据。

关于c# - Add() 方法在代码优先 Entity Framework 中为链接模型添加重复行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56382511/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com