Chazt3n
Chazt3n

Reputation: 1651

Set collection to modified Entity Framework

How can I set a collection to modified in the same way that I would do

_context.Entry(thing).Property(x => x.MyProperty).isModified = true;

like:

_context.Entry(thing).Collection(x => x.MyCollection).isModified = true;

EDIT: The purpose of this, is that my collection is a list of objects stored in a lookup table. I will only have a list of stubs with their id's in this collection and I would like to update the relationships without messing with the audit values and whatever else is contains within the lookup objects. For instance, a contact will have multiple contact types, which for whatever reason are complex objects in this scenario. I want to be able to add and remove types using only the FKs and let EF handle the relationship fixups.

public class Contact
{
   public int Id {get;set;}
   public list<ContactTypes> ContactTypes {get;set;}
   //audit values/other properties
}

public class ContactType
{
   public int Id {get;set;}
   public string Value {get;set;}
}

Upvotes: 5

Views: 3501

Answers (3)

John Dubois
John Dubois

Reputation: 11

You have to synchronize collection properties from detached entities. I wrote the following which I found useful:

namespace DbContextExtensions;

public static class ContextExtensions
{
public static void SyncCollections<T>(this DbContext Context, IEnumerable<T>? Existing, IEnumerable<T>? Detached) where T : class
    {
    PropertyInfo? key = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(pi => pi.GetCustomAttribute(typeof(KeyAttribute)) != null).FirstOrDefault();
    if(key != null)
        { // we have a key property:
        if(Detached != null)
            {
            if(Existing != null)
                {
                var deleted = Existing.Where(ex => !Detached.Select(d => key.GetValue(d)).Contains(key.GetValue(ex)));
                Context.RemoveRange(deleted); // remove any items that don't exist on the Detached collection.
                }
            Detached.Where(d => ((int)key.GetValue(d)) < 0).All(d => { key.SetValue(d, 0); Context.Set<T>().Add(d); return true; });
            }
        else
            if(Existing != null)
            Context.RemoveRange(Existing);  // kill them all!
        }
    }
}

You can use it like this:

var existing = context.MyEntities.Where(mi => mi.Id == TargetId).Include(....
context.SyncCollections(existing.CollectionProp, detached.CollectionProp);
context.MyEntities.Attach(detached);
context.ChangeTracker.Entries().Where(e => e.State == EntityState.Unchanged).All(e => { e.State=EntityState.Modified; return true; });

In this scenario:

  1. Only works for integer keys.
  2. I set unique negative keys for new collection items on my detached entity so they can be manipulated by a UI somewhere. These have to be set to 0 on insert to EF.
  3. This only works for collection properties, but could be easily adjusted for collection fields.

I have a lot of collection properties on my entities and this really simplifies the code.

Upvotes: 1

Chazt3n
Chazt3n

Reputation: 1651

If you have a list of ForeignKey objects, you probably know how frustrating it is to force EF's Relationship Fixup on them. Here's slick way to do that.

public void SetContactTypesToUnchanged(Contact contact)
  {
    contact.ContactTypes.Each(type => _context.Entry(type).State = EntityState.Unchanged);
    _context.SaveChanges();
  }

Upvotes: 1

Gert Arnold
Gert Arnold

Reputation: 109079

context.Entry represents a single entity, never a collection. So you have to loop through the collection and mark each entity as modified.

Upvotes: 3

Related Questions