实体框架子项的插入顺序

时间:2019-08-01 16:47:25

标签: c# entity-framework entity-framework-6

我有这样的结构

public class Son {
  public string Name {get;set;} 
  public int Age {get;set;}
}

public class Daughter {
  public string Name {get;set;} 
  public int Age {get;set;}
}

public class Parent {
  public Daughter[] Daughters {get;set;}    
  public Son[] Sons {get;set;}
}

有FK父母->儿子和父母->女儿

当前,在父对象上执行Context.SaveChanges()时,它先保存Parent,然后再保存Daughter,然后再保存Son。我需要它来保存女儿之前的儿子,因为我们有一个数据库触发器,可以根据女儿进行儿子的验证(如果不符合要求,则会拒绝整个事情)

此触发显然超出了EF的知识。

我如何指定Sons依赖于EF中的子级,以便首先插入Sons;还是可以定义插入顺序的规范或属性?

PS:不要过多地考虑人为的示例(例如为什么我们不将其保存在名为“ Children”的事物下)。现实世界中的例子要复杂得多,但要想在女儿之前先救儿子

1 个答案:

答案 0 :(得分:0)

我喜欢挑战!

首先声明:我不是触发器的粉丝,也不是要求将插入顺序变得重要的要求。我的第一个练习是穷尽所有选项以消除此类要求。

至少在添加实体(例如具有一个或多个子代和一个或多个子代的父代)后,我发现可以稍作修改,然后根据实体名称,插入顺序始终按字母顺序排列。例如,对于名为“父母”,“女儿”和“儿子”的实体,插入始终是“父”>“子”>“子”。属性,配置,插入或表名的顺序与操作无关,但是将实体类“ Son”重命名为“ ASon”会导致在Sons之前插入Sons。我不知道这是否可以继续进行编辑,但这是可以考虑的,而且不要太客气。 (尽管肯定会在系统中很好地记录这样的内容,以防有人质疑命名约定以便在其他内容之前插入内容。)

也就是说,进入有趣的娱乐行业!

使用名为ASon的Son实体在Sons之前强制Sons,可以使EF颠倒该插入顺序:

        using (var context = new ParentDbContext())
        {
            var parent = context.Parents.Create();
            parent.Name = "Steve";

            parent.Daughters.Add(new Daughter { Name = "Elise" });
            parent.Daughters.Add(new Daughter { Name = "Susan" });
            parent.Sons.Add(new ASon { Name = "Jason" });
            parent.Sons.Add(new ASon { Name = "Duke" });
            context.Parents.Add(parent);
            context.SaveChanges();
        }

开箱即用的是插入的父母,儿子,儿子,女儿,女儿。

要扭转这种情况,我覆盖了SaveChanges,寻找我们的儿子将保存推迟到其他情况之后:

    public override int SaveChanges()
    {
        var trackedStates = new[] { EntityState.Added, EntityState.Modified };
        var trackedParentIds = ChangeTracker.Entries<Parent>().Where(x => trackedStates.Contains(x.State)).Select(x => x.Entity.ParentId).ToList();
        var addedSons = ChangeTracker.Entries<ASon>().Where(x => x.State == EntityState.Added).ToList();
        var modifiedSons = ChangeTracker.Entries<ASon>().Where(x => x.State == EntityState.Modified).ToList();

        int tempid = -1;
        int modifiedParentCount = addedSons.Select(x => x.Entity.Parent.ParentId)
            .Where(x => trackedParentIds.Contains(x))
            .Count();
        List<Tuple<Parent, ASon>> associatedSons = new List<Tuple<Parent, ASon>>();
        modifiedSons.ForEach(x => { x.State = EntityState.Unchanged; });
        addedSons.ForEach(x =>
        {
            x.Entity.SonId = tempid--;
            associatedSons.Add(new Tuple<Parent, ASon>(x.Entity.Parent, x.Entity));
            x.Entity.Parent.Sons.Remove(x.Entity);
            x.State = EntityState.Unchanged;
        });

        var result = base.SaveChanges();

        addedSons.ForEach(x => { x.Entity.Parent = associatedSons.Single(a => a.Item2 == x.Entity).Item1; x.State = EntityState.Added; });
        modifiedSons.ForEach(x => { x.State = EntityState.Modified; });

        result += base.SaveChanges() - modifiedParentCount;
        return result;
    }

这是怎么做的: 第一步很容易,我们找到了添加和修改的儿子。我们还计算了父母的父母,既有变子又有增子。完成后,这些将被重复计算。

对于修改后的儿子,我们将其状态设置为“不变”。 对于新增的儿子,我们需要做一些肮脏的工作。我们需要给他们一个临时的唯一ID,因为要将它们标记为Unchanged,EF仍然希望跟踪其ID,并且当您添加2个儿子时,它将在此处失败。请注意,当我们将它们添加回去时,它们将从“标识”列中接收适当的ID,而不是这些临时的负数。我们还跟踪元组中已添加的儿子与他们各自的父母的关联,因为我们需要暂时将这些儿子从其父母中删除。最后,添加的儿子也标记为未更改。 现在我们将其称为基本SaveChanges,它将保存我们的父母和女儿。 对于已修改的儿子,我们只需要将状态更新回已修改。 对于我们添加的儿子,我们使用保存的关联将他们重新分配给他们的父母,然后将他们的状态重新设置为添加。 我们再次调用基本SaveChanges,将受影响的行数附加到第一次运行中,然后减去重复的父引用。 (由于修改而已经计入的父母)

粗略的位正在调整两次保存的结果计数,这可能不是100%准确,但是仅当您碰巧使用SaveChanges的结果时,这应该是一个问题。我不能说我曾经非常重视返回值:)

希望这会给您一些想法。