14

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.

Tim Pohlmann
  • 4,140
  • 3
  • 32
  • 61
  • Have you tried using the refresh method? `dbcontext.Refresh(RefreshMode.ClientWins, entity)` – Florian Haider Jun 02 '16 at 09:54
  • @FlorianHaider dbcontext doesn't have Refresh. But even if, I don't think this would solve the issue. – Tim Pohlmann Jun 02 '16 at 10:02
  • My mistake, you have to cast it to `((IObjectContextAdapter)dbcontext).ObjectContext`. – Florian Haider Jun 02 '16 at 10:13
  • @FlorianHaider I clarified my question: I don't want to update the properties. I want to attach this very instance of my entity to dbcontext. Refresh is not actually helping me with this. – Tim Pohlmann Jun 02 '16 at 11:22
  • Ok, well in this case just call `((IObjectContextAdapter)dbcontext).ObjectContext.Detach(logicalDuplicate)`. Then attaching should work in any case. – Florian Haider Jun 02 '16 at 13:27
  • @FlorianHaider That's what I thought...but it doesn't. – Tim Pohlmann Jun 02 '16 at 13:29
  • Are you sure this is still the same issue? I just tried this code in a unit test and it works fine https://www.codepile.net/pile/vJgxmqeE – Florian Haider Jun 02 '16 at 13:58
  • @FlorianHaider I just figured it out: The problem occurs because my entity is connected to other entities which are still attached to the context. Do you know of a simple way to recursively detach all connected entities as well? – Tim Pohlmann Jun 06 '16 at 10:08
  • 2
    As far as I know there is no easy way. You can either dig into Reflections to get a list of navigation properties and detach them, or look into `ObjectContext` -> `ObjectStateManager` -> `GetRelationshipManager()` and go through the related ends, not sure about that as I have not tried it myself. – Florian Haider Jun 06 '16 at 11:55
  • @FlorianHaider Ok thanks for your help – Tim Pohlmann Jun 06 '16 at 12:05

3 Answers3

13

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);
    }
}
Community
  • 1
  • 1
  • Strange, worked for me - all properties were update on save. Other way is to copy all properties to loaded entity. You may use AutoMapper for that. – Vitaliy Smolyakov Jun 02 '16 at 10:10
  • I'm not talking about updating properties but attaching this very instance to dbcontext. Should have been more clear, will edit the question later. – Tim Pohlmann Jun 02 '16 at 11:14
  • Ok I have to correct myself. Your approach seems to work. My problem is, that my entity is connected to other entities which are still attached to the context. Do you know of a simple way to recursively detach all connected entities as well? – Tim Pohlmann Jun 06 '16 at 10:10
  • 1
    @tim-pohlmann I added to answer code sample of how to get related entries. – Vitaliy Smolyakov Jun 08 '16 at 04:32
1

Edit This finds the original product, removes it, and adds the new one:

    static void UpdateDatabase() 
    {
        Context context = new Context();
        Product product1 = context.Products.Find(1);
        context.Products.Remove(product1);
        Product product2 = new Product(){ProductId = 1, Name = "Product2"};
        context.Products.Add(product2);
        context.SaveChanges();
    }
poppertech
  • 1,286
  • 2
  • 9
  • 17
-2

Best way to salve this problem is


db is my database Object
updateprice is my database entity object
ep is my old same database entity object

db.Entry(updateprice).CurrentValues.SetValues(ep);

  • 1
    I quote from the question: "I don't want to copy the values, I want to attach this very instance of my new entity to dbcontext." – Tim Pohlmann May 04 '17 at 06:39