EF Core:从跟踪中排除已写入的项目

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

我有两个具有多对多关系的实体。

class Action
{
   public int Id {get;set;}

   public List<Case> Cases{ get; set; } = new();
} 

class Case 
{
   public int Id {get;set;}

   public string Value { get; set; }

   public List<Action> Actions { get; set; } = new();
} 

我正在执行业务逻辑并收集列表操作,其中每个操作都是一个将写入数据库的新实例。与此同时,我定义了一些属于打开数据库上下文的操作的案例。如果数据库中已存在案例,我只是将其链接到操作,但如果不存在,我将创建一个新实例,而不将其写入数据库并链接到操作。

var result = new List<Case>();
using (var context = new MyDBContext())
            {
                foreach (var item in cases)
                {
                    var caseFromDB = context.Cases.FirstOrDefault(x => x.Value == item);
                    if (caseFromDB != null)
                    {
                        result.Add(caseFromDB);
                        return result;
                    }
                    else
                        result.Add(new CaseNumber { Value = item });
                }
            }

action.Cases.AddRange(result);

完成所有计算,我将打开新的数据库上下文并将所有操作写入数据库。收到错误:“无法跟踪实体类型“Case”的实例,因为已跟踪具有相同 {'Id'} 键值的另一个实例......”

我的代码如下:

using (var context = new MyDBContext())
            {
                foreach (var action in actions)
                {
                    context.Actionss.Add(action);
                }
                context.SaveChanges();
            }

我尝试删除已添加到数据库的案例,但我只是丢失了记录之间的关系。

请建议如何解决此问题。谢谢!

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

这种方法存在一些问题。首先,在处理相关实体时,您应该始终寻求使用相同的 DbContext 实例,使用一个 DbContext 加载案例,然后使用与案例实例关联的不同 DbContext 整体保存操作并不理想,因为有些案例来自数据库,而另一些则来自数据库。不。

然而,你问题的症结将是由这个逻辑引起的:

if (caseFromDB != null)
{
    result.Add(caseFromDB);
    return result;
}
else
    result.Add(new CaseNumber { Value = item });

基于这些案例被添加到一个操作中并且第二个代码示例正在执行并保存多个操作的事实,这将导致您将未知的新案例编号“添加”到两个不同的操作中的大问题。即使调用此方法读取每个操作的现有案例也会出现问题,因为每个操作的案例将由不同的 DbContext 实例作为单独的实例加载。例如,如果您有 2 个操作,则第一个操作会加载案例 #1 和 #2,并添加 #3。第二个操作想要加载案例#1 并添加案例#3。当您的“操作”DbContext 去保存这两个操作时,您最终会得到对案例 #1 和案例 #3 的 2 次引用。您可能会在尝试保存现有案例 # 时遇到其他问题,因为案例 #1 将是此 DbContext 不知道的独立实例,并且它将尝试插入新行,要么创建重复行,要么抱怨违反约束。即使只处理新案例 (#3),第一个操作将会成功,并且 DbContext 将开始跟踪它,但第二个操作将会失败,因为它的 #3 是相同数据的不同实例。

基本上不要在DbContext实例之间拆分操作。最坏的情况是,如果您不使用由容器管理的生命周期范围的注入 DbContext 实例,并且希望使用单独的方法来处理加载/关联情况和保存操作,请将这些方法外部管理的单个 DbContext 传递到这些方法中使用,而不是而不是到处实例化新的 DbContext 实例。作为使 EF 的生活变得更加简单的一般经验法则,实体的传递不应超出加载或持久化它的 DbContext 的范围。

因此,例如,如果我有一个要保存的新操作列表,并且有一个要与这些操作关联的案例列表,理想情况下我应该在单个 DbContext 的范围内执行此操作,并且确保新项目始终在处理具有相同的参考。

我们可以进行的一项优化来查找现有案例,是将它们加载到一个数据库查询中:

using (var context = new MyDbContext())
{
    var cases = context.Cases
        .Where(x => caseValues.Contains(x.Value))
        .ToList();
    var newCases = cases
        .Except(cases.Select(x => x.Value))
        .Select(x => new Case { Value = x })
        .ToList();
    cases.AddRange(newCases);
    
    foreach(var action in actions)
    {
        action.Cases.AddRange(cases);
        context.Actions.Add(action);
    }
    context.SaveChanges();
}

现在假设要添加的所有适用操作共享相同的案例列表。如果每个动作适用的情况不同,那么就需要进行一些调整。然而,这里的关键区别是我们想要处理单个 DbContext,因此跟踪引用指向相同 case 值的相同对象实例,无论是从数据库加载还是作为新条目创建。在本例中,我加载可以从数据库中匹配的所有案例,然后通过删除从数据库中找到的值并将它们投影到新案例中来查找新项目的值。这样,对于给定的案例值,将只有一个对案例实体的引用,无论是现有的还是新的。

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