由于一个或多个外键属性不可为空,因此无法更改该关系


192

在实体上获取GetById()并将子实体的集合设置为来自MVC视图的新列表时,出现此错误。

操作失败:由于一个或多个外键属性不可为空,因此无法更改该关系。对关系进行更改时,相关的外键属性将设置为空值。如果外键不支持空值,则必须定义新的关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象。

我不太明白这一行:

由于一个或多个外键属性不可为空,因此无法更改该关系。

为什么要更改2个实体之间的关系?在整个应用程序的整个生命周期中,它应保持不变。

发生异常的代码是简单地将集合中的已修改子类分配给现有父类。希望这将满足删除子类,添加新类和修改的要求。我本以为实体框架可以解决这个问题。

代码行可以简化为:

var thisParent = _repo.GetById(1);
thisParent.ChildItems = modifiedParent.ChildItems();
_repo.Save();

我在下面的文章中使用解决方案2找到了我的答案购买,基本上,我创建了一个添加主键的子表以引用父表(因此它有2个主键(父表的外键和ID)用于子表)c-sharpcorner.com/UploadFile/ff2f08/…–
yougotiger

@jaffa,我在这里找到了答案stackoverflow.com/questions/22858491/…–
antonio,

Answers:


159

您应该删除旧的子项 thisParent.ChildItems手动。实体框架无法为您做到这一点。最终,它不能决定要处理旧的子项-是要丢弃它们还是要保留并将其分配给其他父实体。您必须告诉Entity Framework您的决定。但是您必须做出以下两个决定之一,因为子实体在没有引用数据库中任何父项的情况下就无法单独居住(由于外键约束)。基本上,这就是例外情况所说的。

编辑

如果可以添加,更新和删除子项,我该怎么办:

public void UpdateEntity(ParentItem parent)
{
    // Load original parent including the child item collection
    var originalParent = _dbContext.ParentItems
        .Where(p => p.ID == parent.ID)
        .Include(p => p.ChildItems)
        .SingleOrDefault();
    // We assume that the parent is still in the DB and don't check for null

    // Update scalar properties of parent,
    // can be omitted if we don't expect changes of the scalar properties
    var parentEntry = _dbContext.Entry(originalParent);
    parentEntry.CurrentValues.SetValues(parent);

    foreach (var childItem in parent.ChildItems)
    {
        var originalChildItem = originalParent.ChildItems
            .Where(c => c.ID == childItem.ID && c.ID != 0)
            .SingleOrDefault();
        // Is original child item with same ID in DB?
        if (originalChildItem != null)
        {
            // Yes -> Update scalar properties of child item
            var childEntry = _dbContext.Entry(originalChildItem);
            childEntry.CurrentValues.SetValues(childItem);
        }
        else
        {
            // No -> It's a new child item -> Insert
            childItem.ID = 0;
            originalParent.ChildItems.Add(childItem);
        }
    }

    // Don't consider the child items we have just added above.
    // (We need to make a copy of the list by using .ToList() because
    // _dbContext.ChildItems.Remove in this loop does not only delete
    // from the context but also from the child collection. Without making
    // the copy we would modify the collection we are just interating
    // through - which is forbidden and would lead to an exception.)
    foreach (var originalChildItem in
                 originalParent.ChildItems.Where(c => c.ID != 0).ToList())
    {
        // Are there child items in the DB which are NOT in the
        // new child item collection anymore?
        if (!parent.ChildItems.Any(c => c.ID == originalChildItem.ID))
            // Yes -> It's a deleted child item -> Delete
            _dbContext.ChildItems.Remove(originalChildItem);
    }

    _dbContext.SaveChanges();
}

注意:这未经测试。假设子项集合的类型为ICollection。(我通常有IList,然后代码看起来有些不同。)我还剥离了所有存储库抽象,以使其保持简单。

我不知道这是否是一个很好的解决方案,但我认为必须遵循这些思路进行一些艰苦的工作,以处理导航集合中的所有更改。我也很高兴看到更简单的方法。


那么,如果仅更改某些内容怎么办?这是否意味着我仍然必须删除它们并再次添加它们?
jaffa 2011年

@Jon:不,您当然也可以更新现有项目。我添加了一个示例,说明如何更新子集合,请参见上面的“编辑”部分。
Slauma 2011年

@Slauma:大声笑,如果我知道您将要修改您的答案,我不会写我的答案……
Ladislav Mrnka 2011年

@Ladislav:不,不,我很高兴你写了自己的答案。现在至少我知道这不是完全的废话,而且我在上面所做的事情太复杂了。
Slauma 2011年

1
我在foreach中检索originalChildItem时会添加一个条件:... Where(c => c.ID == childItem.ID && c.ID!= 0)否则,如果childItem.ID会返回新添加的子代== 0
perfect_element

116

您遇到此问题的原因是由于组合聚合之间的差异。

在组合中,子对象在创建父对象时创建,并在其父对象销毁时被销毁。因此,其生命周期由其父对象控制。例如,博客文章及其评论。如果帖子被删除,则其评论也应删除。对不存在的帖子发表评论是没有意义的。订单和订单项目相同。

在聚合中,子对象可以不考虑其父对象而存在。如果父对象被销毁,则子对象仍然存在,因为以后可能会将其添加到其他父对象中。例如:播放列表与该播放列表中的歌曲之间的关系。如果播放列表已删除,则不应删除歌曲。可以将它们添加到其他播放列表。

实体框架区分聚合关系和组合关系的方式如下:

  • 对于合成:期望子对象具有一个复合主键(ParentID,ChildID)。这是设计使然,因为孩子的ID应该在其父母的范围内。

  • 对于聚合:期望子对象中的外键属性为可空。

因此,出现此问题的原因是因为您如何在子表中设置主键。它应该是复合的,但不是。因此,Entity Framework将此关联视为聚合,这意味着,当删除或清除子对象时,它不会删除子记录。它将简单地删除关联并将相应的外键列设置为NULL(以便这些子记录以后可以与其他父记录关联)。由于您的列不允许使用NULL,因此您会得到提到的异常。

解决方案:

1-如果您有强烈的理由不想使用组合键,则需要显式删除子对象。这可以比之前建议的解决方案更简单:

context.Children.RemoveRange(parent.Children);

2-否则,通过在子表上设置适当的主键,代码将变得更有意义:

parent.Children.Clear();

9
我发现这种解释最有帮助。
Booji Boy

7
关于组合与聚合以及实体框架如何关联的很好的解释。
蝶ry

#1是解决此问题所需的最少代码。谢谢!
ryanulit '16

73

这是一个很大的问题。您的代码中实际发生的是:

  • Parent从数据库加载并获得附加的实体
  • 您用新的独立儿童集合替换其儿童集合
  • 您可以保存更改,但是在此操作过程中,所有子级都被视为已添加,因为EF直到现在还不知道它们。因此,EF尝试将null设置为旧孩子的外键,然后插入所有新孩子=>重复的行。

现在,解决方案确实取决于您要做什么以及您想如何做?

如果您使用的是ASP.NET MVC,则可以尝试使用UpdateModel或TryUpdateModel

如果您只想手动更新现有的子代,则可以执行以下操作:

foreach (var child in modifiedParent.ChildItems)
{
    context.Childs.Attach(child); 
    context.Entry(child).State = EntityState.Modified;
}

context.SaveChanges();

实际上不需要附加(将状态设置为Modified也将附加实体),但我喜欢它,因为它使过程更加明显。

如果要修改现有的,删除现有的并插入新的子代,则必须执行以下操作:

var parent = context.Parents.GetById(1); // Make sure that childs are loaded as well
foreach(var child in modifiedParent.ChildItems)
{
    var attachedChild = FindChild(parent, child.Id);
    if (attachedChild != null)
    {
        // Existing child - apply new values
        context.Entry(attachedChild).CurrentValues.SetValues(child);
    }
    else
    {
        // New child
        // Don't insert original object. It will attach whole detached graph
        parent.ChildItems.Add(child.Clone());
    }
}

// Now you must delete all entities present in parent.ChildItems but missing
// in modifiedParent.ChildItems
// ToList should make copy of the collection because we can't modify collection
// iterated by foreach
foreach(var child in parent.ChildItems.ToList())
{
    var detachedChild = FindChild(modifiedParent, child.Id);
    if (detachedChild == null)
    {
        parent.ChildItems.Remove(child);
        context.Childs.Remove(child); 
    }
}

context.SaveChanges();

1
但是您对使用有个有趣的看法.Clone()。您是否想到a ChildItem还有其他子导航属性?但是在那种情况下,我们是否不希望整个子图都附加到上下文,因为如果子代本身是新的,我们希望所有子子代都是新对象?(嗯,每个模型的模型可能有所不同,但让我们假设子孩子是“依赖”孩子的,就像孩子是从父母那里得到的。)
Slauma 2011年

它可能需要“智能”克隆。
Ladislav Mrnka,2011年

1
如果您不想在自己的环境中拥有儿童收藏,该怎么办?http://stackoverflow.com/questions/20233994/do-i-need-to-create-a-dbset-for-every-table-so-that-i-can-persist-child-entitie
Kirsten Greed 2013年

1
parent.ChildItems.Remove(child); context.Childs.Remove(child); 感谢您修复此双重删除问题。为什么我们都需要移除?为什么只从parent.ChildItems中删除,因为孩子只是孩子而已?
费尔南多·托雷斯

40

对于相同的错误,我发现答案更为有用。似乎EF在您删除时不喜欢它,它更喜欢删除。

您可以像这样删除记录附加的记录集合。

order.OrderDetails.ToList().ForEach(s => db.Entry(s).State = EntityState.Deleted);

在该示例中,所有附加到订单的明细记录的状态都设置为“删除”。(准备添加回更新的详细信息,作为订单更新的一部分)


我相信这是正确的答案。
desmati '18

逻辑和直接的解决方案。
sairfan '18

19

我不知道为什么其他两个答案如此受欢迎!

我相信您假设ORM框架应该处理它是正确的-毕竟,这就是它所承诺的。否则,您的域模型会因持久性问题而损坏。如果正确设置级联设置,NHibernate会很高兴地进行管理。在Entity Framework中,也有可能,他们只是希望您在建立数据库模型时遵循更好的标准,尤其是当他们必须推断应该执行的级联时:

您必须使用“ 标识关系 ” 正确定义父子关系

如果执行此操作,则Entity Framework知道子对象是由父对象标识的,因此它必须是“级联删除孤儿”情况。

除上述以外,您可能还需要(根据NHibernate的经验)

thisParent.ChildItems.Clear();
thisParent.ChildItems.AddRange(modifiedParent.ChildItems);

而不是完全替换列表。

更新

@Slauma的评论提醒我,独立实体是整个问题的另一部分。为了解决这个问题,您可以采用一种使用自定义模型绑定程序的方法,该方法通过尝试从上下文中加载来构造您的模型。这篇博客文章显示了我的意思的一个例子。


设置为识别关系在这里无济于事,因为问题中的场景必须处理分离的实体(“我的新列表来自MVC视图”)。您仍然必须从数据库中加载原始子项,然后基于分离的集合在该集合中找到已删除的项,然后从数据库中删除。唯一的区别是,有了标识关系,您可以呼叫parent.ChildItems.Remove而不是_dbContext.ChildItems.Remove。EF仍然没有(EF <= 6)内置支持来避免冗长的代码,就像其他答案中的代码一样。
斯劳马

我明白你的意思。但是,我相信使用自定义模型联编程序可以从上下文中加载实体或返回新实例,上述方法可以工作。我将更新我的答案以提出该解决方案。
Andre Luus

是的,您可以使用模型绑定器,但是现在必须从模型绑定器中的其他答案中进行操作。它将问题从回购/服务层转移到模型绑定器。至少,我没有看到真正的简化。
Slauma

简化是自动删除孤立实体。您在模型资料夹中所需要的就是与return context.Items.Find(id) ?? new Item()
Andre Luus

EF团队获得了良好的反馈,但是很遗憾,您提出的解决方案无法解决EF领域的任何问题。
克里斯·莫斯奇尼

9

如果您在同一类上使用AutoMapper和Entity Framework,则可能会遇到此问题。例如,如果您的课程是

class A
{
    public ClassB ClassB { get; set; }
    public int ClassBId { get; set; }
}

AutoMapper.Map<A, A>(input, destination);

这将尝试复制两个属性。在这种情况下,ClassBId是不可为空的。由于AutoMapper会复制,destination.ClassB = input.ClassB;因此会引起问题。

将您的AutoMapper设置为忽略ClassB属性。

 cfg.CreateMap<A, A>()
     .ForMember(m => m.ClassB, opt => opt.Ignore()); // We use the ClassBId

我遇到了与AutoMapper类似的问题,但这对我来说不起作用:(参见stackoverflow.com/q/41430679/613605
J86,2015年

4

我只是有同样的错误。我有两个具有父子关系的表,但是我在子表的表定义中的外键列上配置了“删除时级联”。因此,当我手动(通过SQL)删除数据库中的父行时,它将自动删除子行。

但是,这在EF中不起作用,显示了该线程中描述的错误。原因是,在我的实体数据模型(edmx文件)中,父表和子表之间的关联属性不正确。该End1 OnDelete选项配置为none(我的模型中的“ End1”是具有1的多重性的末端)。

我手动将End1 OnDelete选项更改为Cascade,然后生效了。当我从数据库中更新模型时(我有数据库优先模型),我不知道为什么EF无法收拾它。

为了完整起见,这就是我要删除的代码的样子:

   public void Delete(int id)
    {
        MyType myObject = _context.MyTypes.Find(id);

        _context.MyTypes.Remove(myObject);
        _context.SaveChanges(); 
   }    

如果没有定义级联删除,则必须在删除父行之前手动删除子行。


4

发生这种情况是因为子实体被标记为“已修改”而不是“已删除”。

而EF在parent.Remove(child)执行时对子实体所做的修改只是将对父实体的引用设置为null

执行SaveChanges()以下操作后,可以通过在发生异常时在Visual Studio的“即时窗口”中键入以下代码来检查孩子的EntityState :

_context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Modified).ElementAt(X).Entity

其中X应该由删除的实体替换。

如果您无权ObjectContext执行_context.ChildEntity.Remove(child),则可以通过使外键成为子表的主键的一部分来解决此问题。

Parent
 ________________
| PK    IdParent |
|       Name     |
|________________|

Child
 ________________
| PK    IdChild  |
| PK,FK IdParent |
|       Name     |
|________________|

这样,如果执行parent.Remove(child),EF将正确地将实体标记为“已删除”。


2

这种解决方案对我有用:

Parent original = db.Parent.SingleOrDefault<Parent>(t => t.ID == updated.ID);
db.Childs.RemoveRange(original.Childs);
updated.Childs.ToList().ForEach(c => original.Childs.Add(c));
db.Entry<Parent>(original).CurrentValues.SetValues(updated);

重要的是要说这将删除所有记录并再次插入它们。但是对于我的情况(少于10个)来说还可以。

希望对您有所帮助。


重新插入会发生在新的ID上还是将孩子的ID放在第一位吗?
Pepito Fernandez

2

我今天遇到了这个问题,想分享我的解决方案。就我而言,解决方案是在从数据库中获取父项之前删除子项。

以前我是按照下面的代码那样做的。然后,我将得到此问题中列出的相同错误。

var Parent = GetParent(parentId);
var children = Parent.Children;
foreach (var c in children )
{
     Context.Children.Remove(c);
}
Context.SaveChanges();

对我有用的是,首先使用parentId(外键)获取子项,然后删除这些项。然后,我可以从数据库中获取父项,此时,它不再具有任何子项,并且可以添加新的子项。

var children = GetChildren(parentId);
foreach (var c in children )
{
     Context.Children.Remove(c);
}
Context.SaveChanges();

var Parent = GetParent(parentId);
Parent.Children = //assign new entities/items here

2

您必须手动清除ChildItems集合并将新项目添加到其中:

thisParent.ChildItems.Clear();
thisParent.ChildItems.AddRange(modifiedParent.ChildItems);

之后,您可以调用DeleteOrphans扩展方法,该方法将处理孤立的实体(必须在DetectChanges和SaveChanges方法之间调用)。

public static class DbContextExtensions
{
    private static readonly ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>> s_navPropMappings = new ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>>();

    public static void DeleteOrphans( this DbContext source )
    {
        var context = ((IObjectContextAdapter)source).ObjectContext;
        foreach (var entry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
        {
            var entityType = entry.EntitySet.ElementType as EntityType;
            if (entityType == null)
                continue;

            var navPropMap = s_navPropMappings.GetOrAdd(entityType, CreateNavigationPropertyMap);
            var props = entry.GetModifiedProperties().ToArray();
            foreach (var prop in props)
            {
                NavigationProperty navProp;
                if (!navPropMap.TryGetValue(prop, out navProp))
                    continue;

                var related = entry.RelationshipManager.GetRelatedEnd(navProp.RelationshipType.FullName, navProp.ToEndMember.Name);
                var enumerator = related.GetEnumerator();
                if (enumerator.MoveNext() && enumerator.Current != null)
                    continue;

                entry.Delete();
                break;
            }
        }
    }

    private static ReadOnlyDictionary<string, NavigationProperty> CreateNavigationPropertyMap( EntityType type )
    {
        var result = type.NavigationProperties
            .Where(v => v.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
            .Where(v => v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One || (v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne && v.FromEndMember.GetEntityType() == v.ToEndMember.GetEntityType()))
            .Select(v => new { NavigationProperty = v, DependentProperties = v.GetDependentProperties().Take(2).ToArray() })
            .Where(v => v.DependentProperties.Length == 1)
            .ToDictionary(v => v.DependentProperties[0].Name, v => v.NavigationProperty);

        return new ReadOnlyDictionary<string, NavigationProperty>(result);
    }
}

这对我来说很好。我只需要添加context.DetectChanges();
安迪·爱丁堡

1

我已经尝试过这些解决方案以及许多其他解决方案,但是没有一个完全可行。由于这是google的第一个答案,因此我将在此处添加解决方案。

对我而言,最有效的方法是在提交过程中将关系从图片中删除,因此EF没有任何麻烦。我通过在DBContext中重新找到父对象并将其删除来做到这一点。由于重新找到的对象的导航属性全部为null,因此在提交期间将忽略子级的关系。

var toDelete = db.Parents.Find(parentObject.ID);
db.Parents.Remove(toDelete);
db.SaveChanges();

请注意,这假定外键是使用ON DELETE CASCADE设置的,因此当删除父行时,数据库将清除子级。


1

我使用了Mosh的解决方案,但是对于我而言,如何首先在代码中正确地实现组合键并不十分明显。

所以这是解决方案:

public class Holiday
{
    [Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int HolidayId { get; set; }
    [Key, Column(Order = 1), ForeignKey("Location")]
    public LocationEnum LocationId { get; set; }

    public virtual Location Location { get; set; }

    public DateTime Date { get; set; }
    public string Name { get; set; }
}

1

我遇到了同样的问题,但是我知道它在其他情况下也可以正常工作,因此我将问题简化为:

parent.OtherRelatedItems.Clear();  //this worked OK on SaveChanges() - items were being deleted from DB
parent.ProblematicItems.Clear();   // this was causing the mentioned exception on SaveChanges()
  • OtherRelatedItems具有复合主键(parentId +一些本地列),并且工作正常
  • ProblematicItems具有其自己的单列主键,并且parentId 仅为 FK。这在Clear()之后导致异常。

我要做的就是使ParentId成为复合PK的一部分,以指示没有父母就不能存在子代。我使用了数据库优先模型,添加了PK ,并将parentId列标记为EntityKey(因此,我不得不在数据库和EF中都对其进行更新-不确定仅使用EF是否足够)。

我使RequestId成为PK的一部分 然后更新EF模型,并将另一个属性设置为Entity Key的一部分

一旦您考虑了一下,EF便可以使用它来区分一个非常优雅的区别,即EF可以决定没有父母的孩子是否“有意义”(在这种情况下,Clear()不会删除它们并抛出异常,除非您将ParentId设置为其他/特殊的东西) ),或者-就像原始问题中一样-我们希望将这些项目从父项中删除后将被删除。


0

出现此问题的原因是我们尝试删除父表,但仍然存在子表数据。我们借助级联删除解决了该问题。

在模型中dbcontext类中的Create方法。

 modelBuilder.Entity<Job>()
                .HasMany<JobSportsMapping>(C => C.JobSportsMappings)
                .WithRequired(C => C.Job)
                .HasForeignKey(C => C.JobId).WillCascadeOnDelete(true);
            modelBuilder.Entity<Sport>()
                .HasMany<JobSportsMapping>(C => C.JobSportsMappings)
                  .WithRequired(C => C.Sport)
                  .HasForeignKey(C => C.SportId).WillCascadeOnDelete(true);

之后,在我们的API调用中

var JobList = Context.Job                       
          .Include(x => x.JobSportsMappings)                                     .ToList();
Context.Job.RemoveRange(JobList);
Context.SaveChanges();

级联删除选项使用此简单代码删除父表以及与父表相关的子表。以这种简单的方式尝试。

删除范围用于删除数据库中的记录列表谢谢


0

我还用Mosh的答案解决了我的问题,我认为PeterB的答案有点过头,因为它使用枚举作为外键。请记住,添加此代码后,您将需要添加新的迁移。

我还可以为其他解决方案推荐此博客文章:

http://www.kianryan.co.uk/2013/03/orphaned-child/

码:

public class Child
{
    [Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public string Heading { get; set; }
    //Add other properties here.

    [Key, Column(Order = 1)]
    public int ParentId { get; set; }

    public virtual Parent Parent { get; set; }
}

0

使用Slauma的解决方案,我创建了一些通用函数来帮助更新子对象和子对象的集合。

我所有的持久对象都实现了此接口

/// <summary>
/// Base interface for all persisted entries
/// </summary>
public interface IBase
{
    /// <summary>
    /// The Id
    /// </summary>
    int Id { get; set; }
}

这样,我在存储库中实现了这两个功能

    /// <summary>
    /// Check if orgEntry is set update it's values, otherwise add it
    /// </summary>
    /// <param name="set">The collection</param>
    /// <param name="entry">The entry</param>
    /// <param name="orgEntry">The original entry found in the database (can be <code>null</code> is this is a new entry)</param>
    /// <returns>The added or updated entry</returns>
    public T AddOrUpdateEntry<T>(DbSet<T> set, T entry, T orgEntry) where T : class, IBase
    {
        if (entry.Id == 0 || orgEntry == null)
        {
            entry.Id = 0;
            return set.Add(entry);
        }
        else
        {
            Context.Entry(orgEntry).CurrentValues.SetValues(entry);
            return orgEntry;
        }
    }

    /// <summary>
    /// check if each entry of the new list was in the orginal list, if found, update it, if not found add it
    /// all entries found in the orignal list that are not in the new list are removed
    /// </summary>
    /// <typeparam name="T">The type of entry</typeparam>
    /// <param name="set">The database set</param>
    /// <param name="newList">The new list</param>
    /// <param name="orgList">The original list</param>
    public void AddOrUpdateCollection<T>(DbSet<T> set, ICollection<T> newList, ICollection<T> orgList) where T : class, IBase
    {
        // attach or update all entries in the new list
        foreach (T entry in newList)
        {
            // Find out if we had the entry already in the list
            var orgEntry = orgList.SingleOrDefault(e => e.Id != 0 && e.Id == entry.Id);

            AddOrUpdateEntry(set, entry, orgEntry);
        }

        // Remove all entries from the original list that are no longer in the new list
        foreach (T orgEntry in orgList.Where(e => e.Id != 0).ToList())
        {
            if (!newList.Any(e => e.Id == orgEntry.Id))
            {
                set.Remove(orgEntry);
            }
        }
    }

要使用它,请执行以下操作:

var originalParent = _dbContext.ParentItems
    .Where(p => p.Id == parent.Id)
    .Include(p => p.ChildItems)
    .Include(p => p.ChildItems2)
    .SingleOrDefault();

// Add the parent (including collections) to the context or update it's values (except the collections)
originalParent = AddOrUpdateEntry(_dbContext.ParentItems, parent, originalParent);

// Update each collection
AddOrUpdateCollection(_dbContext.ChildItems, parent.ChildItems, orgiginalParent.ChildItems);
AddOrUpdateCollection(_dbContext.ChildItems2, parent.ChildItems2, orgiginalParent.ChildItems2);

希望这可以帮助


额外:您还可以创建一个单独的DbContextExtentions(或您自己的上下文接口)类:

public static void DbContextExtentions {
    /// <summary>
    /// Check if orgEntry is set update it's values, otherwise add it
    /// </summary>
    /// <param name="_dbContext">The context object</param>
    /// <param name="set">The collection</param>
    /// <param name="entry">The entry</param>
    /// <param name="orgEntry">The original entry found in the database (can be <code>null</code> is this is a new entry)</param>
    /// <returns>The added or updated entry</returns>
    public static T AddOrUpdateEntry<T>(this DbContext _dbContext, DbSet<T> set, T entry, T orgEntry) where T : class, IBase
    {
        if (entry.IsNew || orgEntry == null) // New or not found in context
        {
            entry.Id = 0;
            return set.Add(entry);
        }
        else
        {
            _dbContext.Entry(orgEntry).CurrentValues.SetValues(entry);
            return orgEntry;
        }
    }

    /// <summary>
    /// check if each entry of the new list was in the orginal list, if found, update it, if not found add it
    /// all entries found in the orignal list that are not in the new list are removed
    /// </summary>
    /// <typeparam name="T">The type of entry</typeparam>
    /// <param name="_dbContext">The context object</param>
    /// <param name="set">The database set</param>
    /// <param name="newList">The new list</param>
    /// <param name="orgList">The original list</param>
    public static void AddOrUpdateCollection<T>(this DbContext _dbContext, DbSet<T> set, ICollection<T> newList, ICollection<T> orgList) where T : class, IBase
    {
        // attach or update all entries in the new list
        foreach (T entry in newList)
        {
            // Find out if we had the entry already in the list
            var orgEntry = orgList.SingleOrDefault(e => e.Id != 0 && e.Id == entry.Id);

            AddOrUpdateEntry(_dbContext, set, entry, orgEntry);
        }

        // Remove all entries from the original list that are no longer in the new list
        foreach (T orgEntry in orgList.Where(e => e.Id != 0).ToList())
        {
            if (!newList.Any(e => e.Id == orgEntry.Id))
            {
                set.Remove(orgEntry);
            }
        }
    }
}

并像这样使用它:

var originalParent = _dbContext.ParentItems
    .Where(p => p.Id == parent.Id)
    .Include(p => p.ChildItems)
    .Include(p => p.ChildItems2)
    .SingleOrDefault();

// Add the parent (including collections) to the context or update it's values (except the collections)
originalParent = _dbContext.AddOrUpdateEntry(_dbContext.ParentItems, parent, originalParent);

// Update each collection
_dbContext.AddOrUpdateCollection(_dbContext.ChildItems, parent.ChildItems, orgiginalParent.ChildItems);
_dbContext.AddOrUpdateCollection(_dbContext.ChildItems2, parent.ChildItems2, orgiginalParent.ChildItems2);

您还可以使用以下功能为上下文创建扩展类:
Bluemoon74,19年

0

我要删除记录时遇到了同样的问题,因为发生了某些问题,因此此问题的解决方案是,当您要删除记录时,在删除标题/主记录之前您丢失了一些东西,您必须为请在标题/主文件之前删除其详细信息,希望您能解决。


-1

我在几个小时前就遇到了这个问题,并尝试了所有方法,但就我而言,解决方案与上面列出的有所不同。

如果您使用已经从数据库中检索到的实体并尝试对其进行修改,则将发生错误,但是如果您从数据库中获取了该实体的新副本,则应该不会有任何问题。不要使用此:

 public void CheckUsersCount(CompanyProduct companyProduct) 
 {
     companyProduct.Name = "Test";
 }

用这个:

 public void CheckUsersCount(Guid companyProductId)
 {
      CompanyProduct companyProduct = CompanyProductManager.Get(companyProductId);
      companyProduct.Name = "Test";
 }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.