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.
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.
Multiple DbContext was first introduced in Entity Framework 6.0. Multiple context classes may belong to a single database or two different databases.
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.
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);
}
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With