ChatGPT解决这个技术问题 Extra ChatGPT

How do I stop Entity Framework from trying to save/insert child objects?

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.

As far as I know you will have to null the child objects.
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.
Euphoric, that is completely unhelpful.
@Euphoric Even when not changing child objects, EF still attempts to insert them by default and not ignore them or update them.
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?

J
Johan

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

Hi Johan, I tried detatching one of the collections and it threw the following error: The entity type HashSet`1 is not part of the model for the current context.
@MarkyMark Don't detach the collection. You will have to loop over the collection and detach it object for object (I'll update my answer now).
Unfortunately, even with a list of loops to detatch everything, EF seems to be still trying to insert into some of the related tables. At this point, I'm ready to rip EF out and switch back to SQL which at least behaves sensibly. What a pain.
Can I use context.Entry(yourObject).State before even adding it?
@mirind4 I did not use state. If I insert an object, I make sure all childs are null. On update I get the object first without the childs.
l
lenglei

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.


Great answer, helped me a lot! But would it not be better to state the minimum value of 1 for City_Id using [Range(1, int.MaxValue)] attribute?
Like a dream!! Thanks a Million!
This would remove the value from the School object in the caller. Does SaveChanges reload the value of null navigation properties? Otherwise the caller should reload the City object after calling the Insert() method if it needs that info. This is the pattern I have used often, but I am still open to better patterns if anyone has a good one.
This was really helpful to me. EntityState.Unchaged is exactly what I needed to assign an object that represents a lookup table foreign key in a large object graph that I was saving as a single transaction. EF Core throws a less than intuitive error message IMO. I cached my lookup tables that rarely change for performance reasons. You're assumption is that, because the PK of the lookup table object is the same as what's in the database already that it knows its unchanged and to just assign the FK to the existing item. Instead of tries to insert the lookup table object as new.
@ienglei... 4 years has passes by and your suggestion works yet... I Prefer the first solution... i'm using Entity Framework Core 6... thanks for this great advice!
k
keykey76

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.


This answer has the advantage that if someone comes along later and adds a child object, it won't break existing code. This is an "opt in" solution where the others require you to explicitly exclude child objects.
This no longer works in core. "Attach: Attaches every reachable entity, except where a reachable entity has a store-generated key and no key value is assigned; these will be marked as added." If children are new as well they will be Added.
easy and clean. no headache! Thanks.
Fantastic! Does what I need when dealing with existing employee entities that are loaded from a different instance of my db context. No fuss saving the primary keys to my main table.
Thank you. Worked, It saved lot of time. Simple solution.
B
Bikash Bishwokarma

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.


I agree 100% it is ridiculous that EF attempts to create a new record when the ID is provided. If there was a way to fill select list options with the actual object we'd be in business.
P
Peter Morris

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


W
Wouter Schut

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

K
Klaus

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;

Z
ZweHtatNaing

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;


S
Shaggie

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.


s
slfan

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)...

E
Elyel Rubens da Rosa

If you want to set specific object without their relations do track. Use this option.

public void Insert(School newItem)
{

    using (var context = new DatabaseContext())
    {
        context.Entry(newItem).State = EntityState.Added;
        context.SaveChanges();
    }
}

Read more for a better Knolage at EF Core Documentation about Tracking


S
Syed Danish

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.


What context is "Ignore" being used in? It doesn't appear to exist in the presumed context.
K
KushalSeth

This is the working code of Entity Framework Core .net 5 that saves just in parent table with no errors. I am saving the list, you can just save the object as well.

Changing to this, starts giving me another error: The entity type <typename> was not found. Ensure that the entity type has been added to the model

note: YouObject is the entity

error code: 
_context.yourObject.AttachRange(yourObjectList);
_context.Entry(_context.yourObject).State = EntityState.Added;
await _context.SaveChangesAsync().ConfigureAwait(false);

So, this is what I did: (Working Code below)

Step 1: I made my child components as null.

yourObjectList.ForEach(req => { req.ChildObject = null; });

Step 2: Changed the above code to this:

working code:

_context.yourObject.UpdateRange(yourObjectList);
await _context.SaveChangesAsync().ConfigureAwait(false); 

This is a totally different situation than in the question. And your "fix" would be a very bad idea in that situation.
what is the problem with this code? @GertArnold
You sever a relationship and then save the entities. Think of what woud happen if the child objects were known entities (as in the question). Apart from that, it's not clear why you have unknown entities in your model and how the objects get there.
In short, your answer only tells how you solved your own, totally different, problem.