如何阻止entity framework尝试保存/插入子对象?

当我用entity framework保存一个实体时,我自然认为它只会试图保存指定的实体。 但是,它也试图挽救该实体的子实体。 这导致了各种各样的完整性问题。 如何强制EF只保存我想要保存的实体,因此忽略所有子对象?

如果手动将属性设置为null,则会出现错误“操作失败:由于一个或多个外键属性不可空,因此无法更改关系。 这是非常适得其反的,因为我将子对象设置为null,所以EF会使它独立。

为什么我不想保存/插入子对象?

由于这是在评论中来回讨论的,所以我会给出一些理由,说明为什么我希望我的子对象留下。

在我构build的应用程序中,EF对象模型不是从数据库加载的,而是用作我在分析平面文件时填充的数据对象。 在子对象的情况下,这些对象中的许多指向定义父表的各种属性的查找表。 例如,主要实体的地理位置。

由于我自己填充了这些对象,因此EF假定这些对象是新对象,需要与父对象一起插入。 但是,这些定义已经存在,我不想在数据库中创build重复项。 我只使用EF对象进行查找,并在我的主表实体中填充外键。

即使使用真实数据的子对象,我也需要先保存父对象,然后获得主键或EF,但似乎只是把事情弄得一团糟。 希望这给一些解释。

据我所知,你有两个select。

选项1)

将所有子对象都清空,这将确保EF不添加任何内容。 它也不会从你的数据库中删除任何东西。

选项2)

使用以下代码将子对象设置为与上下文分离

context.Entry(yourObject).State = EntityState.Detached 

请注意,您不能分离List / Collection 。 你将不得不循环你的列表,并像这样分离列表中的每个项目

 foreach (var item in properties) { db.Entry(item).State = EntityState.Detached; } 

build议的解决scheme之一是从相同的数据库上下文中分配导航属性。 在此解决scheme中,从数据库上下文外部分配的导航属性将被replace。 请看下面的例子来说明。

 class Company{ public int Id{get;set;} public Virtual Department department{get; set;} } class Department{ public int Id{get; set;} public String Name{get; set;} } 

保存到数据库:

  Company company = new Company(); company.department = new Department(){Id = 45}; //an Department object with Id = 45 exists in database. using(CompanyContext db = new CompanyContext()){ Department department = db.Departments.Find(company.department.Id); company.department = department; db.Companies.Add(company); db.SaveChanges(); } 

微软将其列为一个function,但我觉得这很烦人。 如果与公司对象关联的部门对象具有已经存在于数据库中的Id,那么EF为什么不将公司对象与数据库对象关联? 我们为什么要自己照顾这个协会? 在添加新对象期间照顾导航属性就像将数据库操作从SQL移动到C#,对开发人员来说很麻烦。

长话短说:使用外键,它将节省你的一天。

假设你有一个学校实体和一个城市实体,这是一个多对一的关系,一个城市有很多学校和一个学校属于一个城市。 假设城市已经存在于查找表中,所以在插入新学校时不要再插入城市。

最初你可以定义你这样的实体:

 public class City { public int Id { get; set; } public string Name { get; set; } } public class School { public int Id { get; set; } public string Name { get; set; } [Required] public City City { get; set; } } 

你可能会这样插入学校 (假设你已经将City属性分配给newItem ):

 public School Insert(School newItem) { using (var context = new DatabaseContext()) { context.Set<School>().Add(newItem); // use the following statement so that City won't be inserted context.Entry(newItem.City).State = EntityState.Unchanged; context.SaveChanges(); return newItem; } } 

上述方法在这种情况下可能完美,但是我更喜欢外键方法,这对我来说更加清晰和灵活。 请参阅下面的更新解决scheme

 public class City { public int Id { get; set; } public string Name { get; set; } } public class School { public int Id { get; set; } public string Name { get; set; } [ForeignKey("City_Id")] public City City { get; set; } [Required] public int City_Id { get; set; } } 

这样,你明确地定义学校有一个外键City_Id ,它是指城市实体。 所以当涉及到学校的插入,你可以做:

  public School Insert(School newItem, int cityId) { if(cityId <= 0) { throw new Exception("City ID no provided"); } newItem.City = null; newItem.City_Id = cityId; using (var context = new DatabaseContext()) { context.Set<School>().Add(newItem); context.SaveChanges(); return newItem; } } 

在这种情况下,您可以明确指定新logging的City_Id ,并从图中删除City ,这样EF就不会费心将其与School一起添加到上下文中。

尽pipe在第一印象中,外键方法似乎更复杂,但相信我这种心态将为您节省大量的时间,当插入一个多对多的关系(成像你有一个学校和学生的关系,学生有一个城市财产)等等。

希望这对你有帮助。

首先,您需要知道在EF中更新实体有两种方法。

  • 附加的对象

    当通过使用上述方法之一更改附加到对象上下文的对象的关系时,entity framework需要保持外键,引用和集合同步。

  • 断开连接的对象

    如果您正在使用断开连接的对象,则必须手动pipe理同步。

在构build的应用程序中,EF对象模型不是从数据库加载的,而是用作parsing平面文件时填充的数据对象。

这意味着你正在使用断开连接的对象,但不清楚你是使用独立的关联还是外键关联 。

  • 当添加具有现有子对象(数据库中存在的对象)的新实体时,如果未通过EF跟踪子对象,则将重新插入子对象。 除非您先手动附加子对象。

     db.Entity(entity.ChildObject).State = EntityState.Modified; db.Entity(entity).State = EntityState.Added; 
  • 更新

    您可以将实体标记为已修改,然后将更新所有标量属性,导航属性将被忽略。

     db.Entity(entity).State = EntityState.Modified; 

graphics差异

如果想在使用断开连接的对象时简化代码,可以试试graphics比较库 。

以下是介绍, 介绍GraphDiffentity framework代码优先 – 允许自动更新分离实体的graphics 。

示例代码

  • 插入实体如果不存在,则更新。

     db.UpdateGraph(entity); 
  • 插入实体,如果它不存在,否则更新插入子对象,如果它不存在,否则更新。

     db.UpdateGraph(entity, map => map.OwnedEntity(x => x.ChildObject)); 

如果您只想将更改存储到对象,并避免将更改存储到其任何子对象,那么为什么不只是执行以下操作:

 using (var ctx = new MyContext()) { ctx.Parents.Attach(parent); ctx.Entry(parent).State = EntityState.Added; // or EntityState.Modified ctx.SaveChanges(); } 

第一行将父对象和其依赖子对象的整个图连接到处于“未Unchanged状态的上下文。

第二行只改变父对象的状态,使其子节点处于Unchanged状态。

请注意,我使用新创build的上下文,所以这可以避免保存对数据库的任何其他更改。

最好的方法是通过覆盖datacontext中的SaveChanges函数。

  public override int SaveChanges() { var added = this.ChangeTracker.Entries().Where(e => e.State == System.Data.EntityState.Added); // Do your thing, like changing the state to detached return base.SaveChanges(); } 

我们所做的是在将父项添加到dbset之前,从父项断开子项集合,确保将现有集合推送到其他variables以允许稍后处理它们,然后用新的空集合replace当前子集合。 设置孩子收集为空/没有任何东西似乎失败了我们。 这样做之后,将父项添加到数据库。 这样,孩子们不会被添加,直到你想要他们。

这对我工作:

 // temporarily 'detach' the child entity/collection to have EF not attempting to handle them var temp = entity.ChildCollection; entity.ChildCollection = new HashSet<collectionType>(); .... do other stuff context.SaveChanges(); entity.ChildCollection = temp; 

我知道这是旧post,但是如果您使用的是代码优先的方法,您可以通过在映射文件中使用以下代码来达到预期的效果。

 Ignore(parentObject => parentObject.ChildObjectOrCollection); 

这将基本上告诉EF从模型中排除“ChildObjectOrCollection”属性,以便它不映射到数据库。

当我尝试保存configuration文件时,我遇到了同样的问题,我已经表示称赞,并且新build了configuration文件。 当我插入configuration文件时,也插入到称呼。 所以我在savechanges()之前就这样尝试了。

db.Entry(Profile.Salutation).State = EntityState.Unchanged;