Add()方法为代码优先实体框架中的链接模型添加重复行

Ais*_*iva 6 .net c# sql-server asp.net-mvc entity-framework

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

[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");
}
Run Code Online (Sandbox Code Playgroud)

以下是AddNewLoan()方法:

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

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这是用于的代码 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

它在成功添加一个行Loans表,但它也加入行LoanProductBorrower表,因为我在第一代码注释显示。

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

更新

我在这里遇到类似的问题,但功能上却相反:实体未使用Code-First方法更新

我认为这两个原因具有相同的变更跟踪。但是一个正在添加另一个并没有更新。

Ste*_* Py 5

以下代码似乎有些奇怪:

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);
Run Code Online (Sandbox Code Playgroud)

您正在视图模型上设置实体引用,然后调用自动映射器。ViewModels不应包含实体引用,而自动映射器应有效地忽略任何引用的实体,而仅映射正在创建的实体结构。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;
Run Code Online (Sandbox Code Playgroud)

编辑:

接下来要检查的是您的服务正在使用完全相同的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);
}    
Run Code Online (Sandbox Code Playgroud)

如果确定所有服务都提供了相同的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");
}
Run Code Online (Sandbox Code Playgroud)

撒上一些异常处理,然后完成并撒粉。没有分层的服务抽象。在这里,您可以通过使用诸如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");
}
Run Code Online (Sandbox Code Playgroud)

在我的情况下,我利用一个使用DbContextScopeLocator的存储库模式来解决它的ContextScope以获得DbContext。仓库管理数据的获取,并确保为实体的创建提供了创建完整且有效的实体所需的所有必需数据。我选择每个控制器一个存储库,而不是像每个实体那样使用通用模式或每个服务的存储库/服务,因为IMO可以更好地管理“单一责任原则”,因为代码仅具有一个更改的理由(它为控制器提供服务,而不是在许多服务之间共享)可能有不同关注点的控制器)。单元测试可以模拟存储库以提供预期的数据状态。回购方法将返回,IQueryable以便使用者逻辑可以确定其如何使用数据。


Ais*_*iva 3

最后,在 @GertArnold 共享的链接的帮助下,每个产品创建都会创建重复的数据类型

由于我的所有模型都继承一个BaseModel类,因此我修改了我的Insert方法,如下所示:

public virtual void Insert(TEntity entity, params BaseModel[] unchangedModels)
{
    if (entity == null)
        throw new ArgumentNullException(nameof(entity));

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

        Entities.Add(entity);

        if (unchangedModels != null)
        {
            foreach (var model in unchangedModels)
            {
                _context.Entry(model).State = EntityState.Unchanged;
            }
        }

        _context.SaveChanges();
    }
    catch (DbUpdateException exception)
    {
        throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
    }
}
Run Code Online (Sandbox Code Playgroud)

并这样称呼它:

_LoanService.Insert(loan, loan.LoanProduct, loan.Borrower);
Run Code Online (Sandbox Code Playgroud)