Attaching Entity to context fails because it already exist - entity-framework

I use the Unity of Work and Generic Repository of CodeCamper.
to update an entity, the generic repo has:
public virtual void Update(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State == EntityState.Detached)
{
DbSet.Attach(entity);
}
dbEntityEntry.State = EntityState.Modified;
}
the web api method:
public HttpResponseMessage Put(MyEditModel editModel)
{
var model = editModel.MapToMyEntity();
_myManager.Update(model);
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
The Update method:
public void Update(MyEntity model)
{
Uow.MyEntities.Update(model);
Uow.Commit();
}
In the Unityof Work:
IRepository<MyEntity> MyEntities { get; }
When updating an entity I get the following error:
Additional information: Attaching an entity of type 'X' failed because another entity of the same type already has the same primary key value.
This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values.
This may be because some entities are new and have not yet received database-generated key values.
In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
The update works fine, when it is the first method you call of the repository.
(I created an entity with an id already in the DB and called the Update.)
The update doesn't work when you do a get of the entity before you update it.
(For example, I get an entity X, convert it to a DTO, then change some values in the UI,
then call a web api that creates an entity X with the new values and
call the Update of the repository.)
Any ideas to avoid this?
When you have a CRUD app, you always call the get before the update.

I'm using my own attach method:
public void Attach<E>(ref E entity)
{
if (entity == null)
{
return;
}
try
{
ObjectStateEntry entry;
bool attach = false;
if (ObjectStateManager.TryGetObjectStateEntry(CreateEntityKey(entitySetName, entity), out entry))
{
attach = entry.State == EntityState.Detached;
E existingEntityInCache = (E)entry.Entity;
if (!existingEntityInCache.Equals(entity))
{
existingEntityInCache.SetAllPropertiesFromEntity(entity);
}
entity = existingEntityInCache;
}
else
{
attach = true;
}
if (attach)
objectContext.AttachTo(entitySetName, entity);
}
catch (Exception ex)
{
throw new Exception("...");
}
}

I had the same issue. The problem was in mixed contexts. When you read entity from DB in context1. Then if you can update this entity with contex2 (other instance of the same context with own entity cache). This may throw an exception.
Pls check for references too:
by context1:
read entity1 with referenced entity2 from DB
by context2:
read entity2 from DB. Then update entity1 (with referenced entity2 from context1).
When you try attach entity1 with referenced entity2 to context2, this throw exception because entity2 already exists in context2.
The solution is use only one context for this operation.

Related

Entity Framework - Replace child collection using a detached model

I have a many-to-many relationship between Entity A and Entity B. Entity Framework has automatically created a junction table in SQL Server after running the migration. (I don't have this junction table defined anywhere in the code.) For example:
class EntityA
{
// ...
public ICollection<EntityB> Foo { get; set; }
}
class EntityB
{
// ...
public ICollection<EntityA> Bar { get; set; }
}
I need to replace the Foo collection on EntityA using a (detached) list coming in from a client application. I've spent the better part of a day trying to figure this out. Here is what I've tried:
[HttpPut]
public async Task<IActionResult> Update(EntityA someEntity)
{
var entry = context.EntityA.Attach(someEntity);
entry.State = EntityState.Modified;
var collection = entry.Collection(x => x.Foo);
collection.IsModified = true;
await context.SaveChangesAsync();
}
I've also tried changing the CurrentValue property of collection, and obviously I've also tried replacing Foo directly, but nothing seems to work -- the junction table remains empty. How can this child list be entirely replaced without having to Include() / load the entire list into memory for manual tracking / removal?
Ivan (in the comments above) is right. After some trial and error, I ended up writing an extension method that works for my case. Before I get to that, I want to credit this answer for pointing me in the right direction, which I ended up modifying to get it working with auto-generated EF junction tables. First, the extension method:
// assuming your models inherit from a base class or implement an interface
public interface IEntity
{
Guid Id { get; set; } // or int or whatever your ID field is
}
public static class DbExtensions
{
// Updates the many-to-many child collections of an entity (for an auto-generated EF junction table)
public static async Task UpdateJunctionTableAsync<T, Y>(this DbContext baseContext, T entity, Expression<Func<T, IEnumerable<Y>>> property)
where T : class, IEntity
where Y : class, IEntity
{
// scope these calls to a new context -- working off the base context
// tends to cause issues down the line with the change tracking
using var context = new DbContext();
// EF internally compares with DB entities, so we'll do the same
var dbEntity = await context.FindAsync<T>(entity.Id);
var dbEntry = context.Entry(dbEntity);
// access the collection entry that resulted in a junction table
var dbItemsEntry = dbEntry.Collection(property);
// get its associated CLR collection accessor
var accessor = dbItemsEntry.Metadata.GetCollectionAccessor();
// load the entry's items
await dbItemsEntry.LoadAsync();
// build a dictionary to track what needs to be added vs removed
var dbItemsMap = dbItemsEntry.CurrentValue.ToDictionary(e => e.Id);
// get the current items in the entity (not DB)
var items = (IEnumerable<Y>)accessor.GetOrCreate(entity, false);
// add them to the DB as needed
foreach (var item in items)
{
// if this already exists, no need to process it.
if (dbItemsMap.ContainsKey(item.Id))
dbItemsMap.Remove(item.Id);
else
{
// otherwise, add a tracked version of it.
context.Set<Y>().Attach(item);
accessor.Add(dbEntity, item, false);
}
}
// anything still left here has been deleted from the entity
foreach (var oldItem in dbItemsMap.Values)
accessor.Remove(dbEntity, oldItem);
// we have to clear the junction table from the incoming model's collection,
// otherwise EF will try to attach to it again, which will cause errors
// further down the line
var memberSelectorExpression = property.Body as MemberExpression;
if (memberSelectorExpression != null)
{
var propertyInfo = memberSelectorExpression.Member as PropertyInfo;
if (propertyInfo != null)
propertyInfo.SetValue(entity, null, null);
}
await context.SaveChangesAsync();
}
}
Using this is simple:
[HttpPut]
public async Task<IActionResult> UpdateFoo(EntityA model)
{
// update the junction table first
await context.UpdateJunctionTableAsync(model, x => x.Foo);
// then update whatever else you want
// e.g., if we were updating the whole row:
// context.EntityA.Attach(model).State = EntityState.Modified;
// save
await context.SaveChangesAsync();
return Ok();
}

Entity Framework Update DbContext

I have a Repository project like this.
https://github.com/tugberkugurlu/GenericRepository/tree/master/src
I have a method.
public void Edit(TEntity entity)
{
_dbContext.SetAsModified(entity);
}
public void SetAsAdded<TEntity>(TEntity entity) where TEntity : class
{
DbEntityEntry dbEntityEntry = GetDbEntityEntrySafely(entity);
dbEntityEntry.State = EntityState.Added;
}
But I am getting while update record. I am getting sometimes this error.
Attaching an entity of type 'TP.Model' failed because
another entity of the same type already has the same primary key
value. This can happen when using the 'Attach' method or setting the
state of an entity to 'Unchanged' or 'Modified' if any entities in the
graph have conflicting key values. This may be because some entities
are new and have not yet received database-generated key values. In
this case use the 'Add' method or the 'Added' entity state to track
the graph and then set the state of non-new entities to 'Unchanged' or
'Modified' as appropriate.
I solved the problem like this. I checked the first columns. Afterwards, I did something like this.
_dbSet = dbContext.DbSet<TEntity>();
The rest is cake.
_dbSet.Attach(entity);
DbEntityEntry entry = _dbContext.Entry(entity);
foreach (var proprty in entry.OriginalValues.PropertyNames)
{
var Current = entry.CurrentValues.GetValue<object>(proprty);
var New = entry.GetDatabaseValues().GetValue<object>(proprty);
if (Current != null)
{
if (!object.Equals(New, Current))
{
entry.Property(proprty).IsModified = true;
}
}
}

How to add entity with existing attached to it

I want to add entity payment object, containing EXISTING Currency object to EF database:
public Payment()
{
int Id {get;set;}
public int Value {get;set;}
public Currency SelectedCurrency{get;set;}
}
public Currency()
{
int Id {get;set;}
string Name;
}
Suppose that I have existing Currency attached to new entity Payment(). When I add such entity Payment(), the error appears
Violation of PRIMARY KEY constraint 'PK_dbo.Currency'. Cannot insert duplicate key in object 'dbo.MwbeCurrency'. The duplicate key value is (GBP).\r\nThe statement has been terminated."}
How to add higher-level entity with attached existing lower-level entity?
My code for adding entity is:
public virtual TEntity Add(TEntity entity)
{
return DbSet.Add(entity);
}
public void SaveChanges()
{
Context.SaveChanges();
}
I suspect you retrieved Currency with a different instance than the one that retrieved Payment and did something like this :
payment.Currency = retrievedCurrency;
Therefore, the Payment context things that Currency is a new object and tries to persist it. Since it already exists, you are getting a PRIMARY KEY violation.
If you want to persist Payment correctly, add the following lines:
if (payment.Currency != null && payment.Currency.Id != 0)
{
context.Entry(payment.Currency).State = EntityState.Unchanged;
}
although it would probably be cleaner if you retrieved Payment and Currency with the same context, so you can persist them appropriately.
Calling DbSet.Add(entity) adds the entire graph for persistence, which means it will go through all the navigation properties of entity and set each one's state to EntityState.Added.
While the other answer might work, a better approach is to change the way you are adding the objects, and be explicit about what entities you are adding / updating / etc.
To do this, change:
public virtual void Add(TEntity entity)
{
DbSet.Add(entity);
}
To:
public virtual void Add(TEntity entity)
{
context.Entry(entity).State = EntityState.Added;
}
This will add only the supplied entity. If one of your navigation properties objects is also new, you call .Add(entity) on it as well.
If you do need to add the entire graph in other situations, you can add an additional method that works the way your original one does, but has a better name to indicate it's function:
public virtual void AddGraph(TEntity entity)
{
DbSet.Add(entity);
}
Good Luck
Update
Additionally, since it looks like you are using a repository, I prefer to disable auto detect changes by setting context.Configuration.AutoDetectChangesEnabled = false; If you modify a property on an entity that you want persisted, you would need to set the state of the entity to modified like so:
public virtual void Update(TEntity entity)
{
context.Entry(entity).State = EntityState.Modified;
}

Generic repository to update an entire aggregate

I am using the repository pattern to provide access to and saving of my aggregates.
The problem is the updating of aggregates which consist of a relationship of entities.
For example, take the Order and OrderItem relationship. The aggregate root is Order which manages its own OrderItem collection. An OrderRepository would thus be responsible for updating the whole aggregate (there would be no OrderItemRepository).
Data persistence is handled using Entity Framework 6.
Update repository method (DbContext.SaveChanges() occurs elsewhere):
public void Update(TDataEntity item)
{
var entry = context.Entry<TDataEntity>(item);
if (entry.State == EntityState.Detached)
{
var set = context.Set<TDataEntity>();
TDataEntity attachedEntity = set.Local.SingleOrDefault(e => e.Id.Equals(item.Id));
if (attachedEntity != null)
{
// If the identity is already attached, rather set the state values
var attachedEntry = context.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(item);
}
else
{
entry.State = EntityState.Modified;
}
}
}
In my above example, only the Order entity will be updated, not its associated OrderItem collection.
Would I have to attach all the OrderItem entities? How could I do this generically?
Julie Lerman gives a nice way to deal with how to update an entire aggregate in her book Programming Entity Framework: DbContext.
As she writes:
When a disconnected entity graph arrives on the server side, the
server will not know the state of the entities. You need to provide a
way for the state to be discovered so that the context can be made
aware of each entity’s state.
This technique is called painting the state.
There are mainly two ways to do that:
Iterate through the graph using your knowledge of the model and set the state for each entity
Build a generic approach to track state
The second option is really nice and consists in creating an interface that every entity in your model will implement. Julie uses an IObjectWithState interface that tells the current state of the entity:
public interface IObjectWithState
{
State State { get; set; }
}
public enum State
{
Added,
Unchanged,
Modified,
Deleted
}
First thing you have to do is to automatically set the state to Unchanged for every entity retrieved from the DB, by adding a constructor in your Context class that hooks up an event:
public YourContext()
{
((IObjectContextAdapter)this).ObjectContext
.ObjectMaterialized += (sender, args) =>
{
var entity = args.Entity as IObjectWithState;
if (entity != null)
{
entity.State = State.Unchanged;
}
};
}
Then change your Order and OrderItem classes to implement the IObjectWithState interface and call this ApplyChanges method accepting the root entity as parameter:
private static void ApplyChanges<TEntity>(TEntity root)
where TEntity : class, IObjectWithState
{
using (var context = new YourContext())
{
context.Set<TEntity>().Add(root);
CheckForEntitiesWithoutStateInterface(context);
foreach (var entry in context.ChangeTracker
.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
entry.State = ConvertState(stateInfo.State);
}
context.SaveChanges();
}
}
private static void CheckForEntitiesWithoutStateInterface(YourContext context)
{
var entitiesWithoutState =
from e in context.ChangeTracker.Entries()
where !(e.Entity is IObjectWithState)
select e;
if (entitiesWithoutState.Any())
{
throw new NotSupportedException("All entities must implement IObjectWithState");
}
}
Last but not least, do not forget to set the right state of your graph entities before calling ApplyChanges ;-) (You could even mix Modified and Deleted states within the same graph.)
Julie proposes to go even further in her book:
you may find yourself wanting to be more granular with the way
modified properties are tracked. Rather than marking the entire entity
as modified, you might want only the properties that have actually
changed to be marked as modified.
In addition to marking an entity as modified, the client is also
responsible for recording which properties have been modified. One way
to do this would be to add a list of modified property names to the
state tracking interface.
But as my answer is already too long, go read her book if you want to know more ;-)
My opinionated (DDD specific) answer would be:
Cut off the EF entities at the data layer.
Ensure your data layer only returns domain entities (not EF entities).
Forget about the lazy-loading and IQueryable() goodness (read: nightmare) of EF.
Consider using a document database.
Don't use generic repositories.
The only way I've found to do what you ask in EF is to first delete or deactivate all order items in the database that are a child of the order, then add or reactivate all order items in the database that are now part of your newly updated order.
So you have done well on update method for your aggregate root, look at this domain model:
public class ProductCategory : EntityBase<Guid>
{
public virtual string Name { get; set; }
}
public class Product : EntityBase<Guid>, IAggregateRoot
{
private readonly IList<ProductCategory> _productCategories = new List<ProductCategory>();
public void AddProductCategory(ProductCategory productCategory)
{
_productCategories.Add(productCategory);
}
}
it was just a product which has a product category, I've just created the ProductRepository as my aggregateroot is product(not product category) but I want to add the product category when I create or update the product in service layer:
public CreateProductResponse CreateProduct(CreateProductRequest request)
{
var response = new CreateProductResponse();
try
{
var productModel = request.ProductViewModel.ConvertToProductModel();
Product product=new Product();
product.AddProductCategory(productModel.ProductCategory);
_productRepository.Add(productModel);
_unitOfWork.Commit();
}
catch (Exception exception)
{
response.Success = false;
}
return response;
}
I just wanted to show you how to create domain methods for entities in domain and use it in service or application layer. as you can see the code below adds the ProductCategory category via productRepository in database:
product.AddProductCategory(productModel.ProductCategory);
now for updating the same entity you can ask for ProductRepository and fetch the entity and make changes on it.
note that for retrieving entity and value object of and aggregate separately you can write query service or readOnlyRepository:
public class BlogTagReadOnlyRepository : ReadOnlyRepository<BlogTag, string>, IBlogTagReadOnlyRepository
{
public IEnumerable<BlogTag> GetAllBlogTagsQuery(string tagName)
{
throw new NotImplementedException();
}
}
hope it helps

EF Entity not updated in database

This is the code that I have in my controller:
[HttpPost]
public ActionResult UpdateArticle(Article article)
{
if (ModelState.IsValid)
{
article.DateAdded =
this.articleRepository.GetSingle(article.ID).DateAdded;
article.DateModified = DateTime.Now;
this.articleRepository.Update(article);
return this.Redirect("~/Admin");
}
else
{
return this.Redirect("~/Admin/UnsuccessfulOperation");
}
}
From the view the data comes updated. I have a generic repository which handles the saving.
Update looks like this:
public virtual void Update(T entity)
{
//this.context.Entry(entity).State = EntityState.Modified;
this.context.SaveChanges();
}
If I uncomment the first line
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple
objects with the same key.
exception is thrown. When commented nothing is saved.
Any help is appreciated.
UPDATE
Ok the problem seems to be that the updated article is not "part" of the context so when I pass it to the update nothing happens. If I get the entity from the repository itself and pass the new values and after that pass this entity everything works as expected. This piece of code actually updates the date in the repository:
var art = this.articleRepository.GetSingle(article.ID);
art.Text = article.Text;
this.articleRepository.Update(art);
What I don't get is that this works too:
var art = this.articleRepository.GetSingle(article.ID);
art.Text = article.Text;
this.articleRepository.Update(article);
UPDATE 2
Thanks to Vitaliy I now know that attaching is the key, but when I try to attach the new entity I get the same ugly exception
An object with the same key already...
UPDATE 3
As I am not allowed to answer my own question in less than 8h I suppose I have to make another update.
Ok, so this is what I did in order to successfully detach the old and attach the new entity:
public virtual void Update(T entity, object id)
{
this.context.Entry(this.GetSingle(id)).State = EntityState.Detached;
this.context.Entry(entity).State = EntityState.Added;
this.context.Entry(entity).State = EntityState.Modified;
this.context.SaveChanges();
}
I will think of a better way to pass the ID, as it is already part of the entity, perhaps with and interface "myInterface" that has the ID property in it and T will be of type "myInterface".
Thanks a lot to Vitaliy.
You are updating article, which is not attached to Context, thus nothing will be saved.
Probably you intention was to change DateModified then you should do it like this:
public ActionResult UpdateArticle(Guid articleID)
{
if (ModelState.IsValid)
{
var article =
this.articleRepository.GetSingle(articleID);
article.DateModified = DateTime.Now;
this.articleRepository.Update(article);
return this.Redirect("~/Admin");
}
else
{
return this.Redirect("~/Admin/UnsuccessfulOperation");
}
}