尝试更新实体内的列表总是给我一个错误

问题描述 投票:0回答:1

我正在使用领域驱动设计。两个实体之间存在关系:

费用和员工

费用有员工名单

费用:

private List<Employee> _Employees { get; set; } = new List<Employee>();
public List<Employee> Employees => _Employees;

员工没有提及费用。我不需要按员工查看费用。

当我创建费用时,我可以添加员工。我可以通过在员工存储库中使用它来删除员工。

public void RemoveRange(List<Employee> Employee)
{
   _context.Set<Employee>().RemoveRange(Employee);
}

但是在更新费用时,我收到错误:“数据库操作预计会影响 1 行,但实际上影响了 0 行;此后数据可能已被修改或删除”

先获取费用进行跟踪,然后更改员工:

public async Task<Expense?> Get(Guid id)
{
        return await _context.Set<Expense>()
            .Where(x => x.Id == id)
            .Include(x => x.Employees)
            .FirstOrDefaultAsync();
}
...
var expense = await _repoExpense.Get(expenseId);
foreach (var Employee in m.Value.Employees)
{
   var Emplyoee= Employee.Create(Employee.EmployeeName,, Employee.EmployeeId, Employee.CompanyName);
   expense.AddEmployee(Emplyoee);
}
await _unitOfWork.SaveChangesAsync(ct); //error line

这个 foreach 与我在使用员工创建新费用时使用的完全相同,并且它有效。只是更新的时候不行。

我尝试过其他方法,但错误始终相同。我不太关心List Employee之前的数据。我所做的就是删除所有记录并添加新记录。创建新费用时效果很好。当我获取现有费用、清除其员工并添加新员工时……它会崩溃。即使我只是添加并且之前不删除...它也会损坏。

c# .net entity-framework .net-7.0
1个回答
0
投票

更新对相关一对多关系的引用时,不应尝试采用替换集合的方法。虽然从编码角度来看它可能更简单,但从 EF/数据库角度来看它是最糟糕的。费用与员工关系之类的东西通常是多对多关系,其中费用引用员工,而不是封装员工。要概述差异,请使用“客户”和“订单”之类的内容。一个客户包含许多订单,但每个订单只属于一个客户。不同的客户可以订购相同的商品,但每个订单都是数据库中的唯一行。一项费用可能会引用许多员工,但实际上这些相同的员工应该能够被不同的费用引用,不是吗?如果您试图将现有员工与新费用关联起来,同时又告诉 EF 该关系是一对多(父子)而不是多对多,那么您可能会遇到麻烦。 (关联)一对多在 Employee 表上有一个 ExpenseId,这意味着一名员工只能与一项费用关联。要允许多个费用引用同一员工,您需要定义类似 ExpenseEmployee 表的内容并告诉 EF 这是多对多关系。这仍然可以是单向引用,其中 Expense 包含Employees 集合,但 Employee 上没有 Expenses 集合。

您的示例代码也将无法编译,因为您对迭代和新员工使用相同的变量名称“Employee”,您的示例也不会清除员工列表。它通常有助于发布实际代码,而不是重新解释/简化,因为重新哈希的代码可能不包含实际问题。

例如,而不是这样的:

var expense = await _repoExpense.Get(expenseId);
expense.Employees.Clear();
foreach (var employee in m.Value.Employees)
{
   var newEmplyoee= Employee.Create(employee.EmployeeName, employee.EmployeeId, employee.CompanyName);
   expense.Employees.Add(Emplyoee);
}

最好更明确地做到这一点。确定需要删除和添加哪些员工,然后进行调整:

var expense = await _repoExpense.Get(expenseId);
var existingEmployeeIds = expense.Employees.Select(x => x.EmployeeId).ToList();
var updatedEmployeeIds = m.Value.Employees.Select(x => x.EmployeeId).ToList();

var employeeIdsToAdd = updatedEmployeeIds.Except(existingEmployeeIds).ToList();
var employeeIdsToRemove = existingEmployeeIds.Except(updatedEmployeeIds).ToList();

接下来,删除取消引用的员工很容易:

if(employeeIdsToRemove.Any())
{
    var employeesToRemove = expense.Employees
        .Where(x => exmployeeIdsToRemove.Contains(x.EmployeeId))
        .ToList();
    foreach(var employee in employeesToRemove)
        expense.Employees.Remove(employee);
}

最后,添加对要添加的员工的引用。在这里,我们处理对现有员工记录的引用,而不是创建新员工,因此我们需要确保关联 DbContext 将识别为现有员工记录的实体。如果您使用 ID 和其他字段

new
查找员工,EF 会将其视为
INSERT
,因为它不会跟踪该实例,也不会识别它应该是现有引用。所以我们找那些员工:

if (employeeIdsToAdd.Any())
{
     var employeesToAdd = await _context.Employees
         .Where(x => employeeIdsToAdd.Contains(x.EmployeeId))
         .ToListAsync();
     foreach(var employee in employeesToAdd)
         expense.Employees.Add(employee);
}

await _context.SaveChangesAsync();

...就是这样。现在这提出了一个问题,即您的存储库模式。您将需要从 DbContext 中获取已添加 ID 的员工。在这种情况下,使用 EF 上的存储库模式可能会适得其反,因为 EF 已经以

DbSet
的形式提供了存储库。最坏的情况是,如果您只有 Get() 方法来获取 ID,则您将不得不迭代要添加的 Id 并添加每个 Id,或者引入带有
params int[] ids
的 Get 方法,以便能够获取多个 Id按 ID 行。

© www.soinside.com 2019 - 2024. All rights reserved.