120
votes

When I save an entity with entity framework, I naturally assumed it would only try to save the specified entity. However, it is also trying to save that entity's child entities. This is causing all sorts of integrity problems. How do I force EF to only save the entity I want to save and therefore ignore all child objects?

If I manually set the properties to null, I get an error "The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable." This is extremely counter-productive since I set the child object to null specifically so EF would leave it alone.

Why don't I want to save/insert the child objects?

Since this is being discussed back and forth in the comments, I'll give some justification of why I want my child objects left alone.

In the application I'm building, the EF object model is not being loaded from the database but used as data objects which I'm populating while parsing a flat file. In the case of the child objects, many of these refer to lookup tables defining various properties of the parent table. For example, the geographic location of the primary entity.

Since I've populated these objects myself, EF assumes these are new objects and need to be inserted along with the parent object. However, these definitions already exist and I don't want to create duplicates in the database. I only use the EF object to do a lookup and populate the foreign key in my main table entity.

Even with the child objects that are real data, I needs to save the parent first and get a primary key or EF just seems to make a mess of things. Hope this gives some explanation.

11
As far as I know you will have to null the child objects.Johan
Hi Johan. Doesn't work. It throws errors if I null the collection. Depending on how I do it, it complains about keys being null or that I collection has been modified. Obviously, those things are true, but I did that on purpose so it would leave alone the objects it's not supposed to touch.Mark Micallef
Euphoric, that is completely unhelpful.Mark Micallef
@Euphoric Even when not changing child objects, EF still attempts to insert them by default and not ignore them or update them.Johan
What really annoys me is that if I go out of my way to actually null those objects, it then complains rather than realising that I want it to just leave them alone. Since those child objects are all optional (nullable in the database), is there some way to force EF to forget that I had those objects? i.e. purge its context or cache somehow?Mark Micallef

11 Answers

66
votes

As far as I know, you have two options.

Option 1)

Null all the child objects, this will ensure EF not to add anything. It will also not delete anything from your database.

Option 2)

Set the child objects as detached from the context using the following code

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

Note that you can not detach a List/Collection. You will have to loop over your list and detach each item in your list like so

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

Long story short: Use Foreign key and it will save your day.

Assume you have a School entity and a City entity, and this is a many-to-one relationship where a City has many Schools and a School belong to a City. And assume the Cities are already existing in the lookup table so you do NOT want them to be inserted again when inserting a new school.

Initially you might define you entities like this:

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; }
}

And you might do the School insertion like this (assume you already have City property assigned to the 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;
    }
}

The above approach may work perfectly in this case, however, I do prefer the Foreign Key approach which to me is more clear and flexible. See the updated solution below:

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; }
}

In this way, you explicitly define that the School has a foreign key City_Id and it refers to the City entity. So when it comes to the insertion of School, you can do:

    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;
        }
    }

In this case, you explicitly specify the City_Id of the new record and remove the City from the graph so that EF won't bother to add it to the context along with School.

Though at the first impression the Foreign key approach seems more complicated, but trust me this mentality will save you a lot of time when it comes to inserting a many-to-many relationship (imaging you have a School and Student relationship, and the Student has a City property) and so on.

Hope this is helpful to you.

29
votes

If you just want to store changes to a parent object and avoid storing changes to any of its child objects, then why not just do the following:

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

The first line attaches the parent object and the whole graph of its dependent child objects to the context in Unchanged state.

The second line changes the state for the parent object only, leaving its children in the Unchanged state.

Note that I use a newly created context, so this avoids saving any other changes to the database.

14
votes

One of the suggested solutions is to assign the navigation property from the same database context. In this solution, the navigation property assigned from outside the database context would be replaced. Please, see following example for illustration.

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;}
}

Saving to database:

 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();
  }

Microsoft enlists this as a feature, however I find this annoying. If the department object associated with company object has Id that already exists in database, then why doesn't EF just associates company object with database object? Why should we need to take care of the association by ourselves? Taking care of the navigation property during adding new object is something like moving the database operations from SQL to C#, cumbersome to the developers.

11
votes

First you need to know that there are two ways for updating entity in EF.

  • Attached objects

When you change the relationship of the objects attached to the object context by using one of the methods described above, the Entity Framework needs to keep foreign keys, references, and collections in sync.

  • Disconnected objects

If you are working with disconnected objects you must manually manage the synchronization.

In the application I'm building, the EF object model is not being loaded from the database but used as data objects which I'm populating while parsing a flat file.

That means you are working with disconnected object, but it's unclear whether you are using independent association or foreign key association.

  • Add

    When adding new entity with existing child object (object that exists in the database), if the child object is not tracked by EF, the child object will be re-inserted. Unless you manually attach the child object first.

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

    You can just mark the entity as modified, then all scalar properties will be updated and the navigation properties will simply be ignored.

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

Graph Diff

If you want to simplify the code when working with disconnected object, you can give a try to graph diff library.

Here is the introduction, Introducing GraphDiff for Entity Framework Code First - Allowing automated updates of a graph of detached entities.

Sample Code

  • Insert entity if it doesn't exist, otherwise update.

      db.UpdateGraph(entity);
    
  • Insert entity if it doesn't exist, otherwise update AND insert child object if it doesn't exist, otherwise update.

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

Best way to do this is in by overriding the SaveChanges function in your datacontext.

    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();
    }
2
votes

This worked for me:

// 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;
2
votes

I've got the same problem when I trying to save profile, I already table salutation and new to create profile. When I insert profile it also insert into salutation. So I tried like this before savechanges().

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

0
votes

What we have done is before adding the parent to the dbset, disconnect the child collections from the parent, making sure to push the existing collections to other variables to allow working with them later, and then replacing the current child collections with new empty collections. Setting the child collections to null/nothing seemed to fail for us. After doing that then add the parent to the dbset. This way the children are not added until you want them to.

0
votes

I know it's old post however if you are using code-first approach you may achieve the desired result by using following code in your mapping file.

Ignore(parentObject => parentObject.ChildObjectOrCollection);

This will basically tell EF to exclude "ChildObjectOrCollection" property from model so that it is not mapped to database.

0
votes

I had a similar challenge using Entity Framework Core 3.1.0, my repo logic is quite generic.

This worked for me:

builder.Entity<ChildEntity>().HasOne(c => c.ParentEntity).WithMany(l =>
       l.ChildEntity).HasForeignKey("ParentEntityId");

Please note "ParentEntityId" is the foreign key column name on the child entity. I added the above mentioned line of code on this method:

protected override void OnModelCreating(ModelBuilder builder)...