Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace entity in context with a different instance of the same entity

I have an entity which is not connected to my dbcontext. I want to change that. However there is already another instance of the same entity attached to dbcontext. If I just add my new entity, I get an error, that an entity with the same primary key is already attached.
I tried multiple different variants of removing the old entity from dbcontext without any success. How can I replace the old instance with the new one?
Note: I don't want to copy the values, I want to attach this very instance of my new entity to dbcontext.

var entity = new MyEntity { Id = 1 };
var logicalDuplicate = dbcontext.Set<MyEntity >().Local
    .FirstOrDefault(e => e.Equals(entity));
if (logicalDuplicate != null)
{
    // remove logicalDuplicate from dbcontext
}
dbcontext.MyEntity.Attach(entity);

For clarification: I have overridden Equals to check for Id instead of reference.

like image 486
Tim Pohlmann Avatar asked Jun 02 '16 08:06

Tim Pohlmann


People also ask

How does DbContext change state of entity?

This can be achieved in several ways: setting the EntityState for the entity explicitly; using the DbContext. Update method (which is new in EF Core); using the DbContext. Attach method and then "walking the object graph" to set the state of individual properties within the graph explicitly.

Can we have multiple DbContext in Entity Framework?

Multiple DbContext was first introduced in Entity Framework 6.0. Multiple context classes may belong to a single database or two different databases.

How do you attach entity to context?

Add() method attaches the entire entity graph to a context and automatically applies the Added state to all entities. The DbSet. Add() method attaches the entire entity graph to a context with the Added state to each entity. Calling context.


1 Answers

Try this:

if (logicalDuplicate != null)
{
    dbcontext.Entry(logicalDuplicate).State = EntityState.Detached;
    dbcontext.MyEntity.Attach(entity);
    dbcontext.Entry(entity).State = EntityState.Modified;
}
else
{
    dbcontext.MyEntity.Add(entity);
}

How to get related entries

I investigated that and want to share with my results. I used reflection as short way to get entity properties names. But it's possible to get it without reflection as mentioned @Florian Haider. You can use answer and this.

// Found loaded related entries that can be detached later.
private HashSet<DbEntityEntry> relatedEntries;

private DbContext context;

private List<string> GetPropertiesNames(object classObject)
{
    // TODO Use cache for that.
    // From question https://stackoverflow.com/questions/5851274/how-to-get-all-names-of-properties-in-an-entity
    var properties = classObject.GetType().GetProperties(BindingFlags.DeclaredOnly |
                                                              BindingFlags.Public |
                                                              BindingFlags.Instance);
    return properties.Select(t => t.Name).ToList();
}

private void GetRelatedEntriesStart(DbEntityEntry startEntry)
{
    relatedEntries = new HashSet<DbEntityEntry>();

    // To not process start entry twice.
    relatedEntries.Add(startEntry);
    GetRelatedEntries(startEntry);
}

private void GetRelatedEntries(DbEntityEntry entry)
{
    IEnumerable<string> propertyNames = GetPropertiesNames(entry.Entity);
    foreach (string propertyName in propertyNames)
    {
        DbMemberEntry dbMemberEntry = entry.Member(propertyName);
        DbReferenceEntry dbReferenceEntry = dbMemberEntry as DbReferenceEntry;
        if (dbReferenceEntry != null)
        {
            if (!dbReferenceEntry.IsLoaded)
            {
                continue;
            }

            DbEntityEntry refEntry = context.Entry(dbReferenceEntry.CurrentValue);
            CheckReferenceEntry(refEntry);
        }
        else
        {
            DbCollectionEntry dbCollectionEntry = dbMemberEntry as DbCollectionEntry;
            if (dbCollectionEntry != null && dbCollectionEntry.IsLoaded)
            {
                foreach (object entity in (ICollection)dbCollectionEntry.CurrentValue)
                {
                    DbEntityEntry refEntry = context.Entry(entity);
                    CheckReferenceEntry(refEntry);
                }
            }
        }
    }
}

private void CheckReferenceEntry(DbEntityEntry refEntry)
{
    // Add refEntry.State check here for your need.
    if (!relatedEntries.Contains(refEntry))
    {
        relatedEntries.Add(refEntry);
        GetRelatedEntries(refEntry);
    }
}
like image 132
Vitaliy Smolyakov Avatar answered Oct 03 '22 17:10

Vitaliy Smolyakov