Can't Update - Entity Framework - entity-framework

Error:
DbUpdateConcurrencyException: 'Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded.
Code:
public TEntity Update(TEntity obj)
{
var entry = Db.Entry(obj);
DbSet.Attach(obj);
entry.State = EntityState.Modified;
SaveChanges();
return obj;
}
Which are the possibilities problems for this error?
I already tried remove DbSet.Attach(obj) like some others SO's post say to do, but occurred the same error

Related

Update method throws an exception, because the entity is already tracked

I have a problem with EF Core 5 that is really getting me down.
FYI, LazyLoadingProxies are used (something else that just gives me a headache, but well, different topic).
Information for the code below:
Service: A service per entity, contains all CRUD operations into the database and other methods if needed.
Workflow: Uses multiple services at once to perform certain operations (e.g. create product -> create product folder -> save product).
Problem:
I have an entity "Product" which contains the following update method which is used to update the properties of the entity with those of another object:
public override void Update(Product source)
{
// Properties
AnnualPrice = source.AnnualPrice;
...
// Relations
var sourceRelatedProductIds = source.RelatedWithProductIds.Where(x => x != Id);
if (sourceRelatedProductIds.Count() != 0)
{
RelatedWithProducts.Clear();
foreach (var relatedWithProduct in ctx.Set<Product>().Where(x => source.RelatedWithProductIds.Contains(x.Id)).AsNoTracking())
{
RelatedWithProducts.Add(relatedWithProduct);
}
}
var oldShortDescriptions = ShortDescriptions.ToList(); <--- EXCEPTION
ShortDescriptions.Clear();
foreach (var shortDescription in source.ShortDescriptions)
{
shortDescription.Id = oldShortDescriptions.FirstOrDefault(x => x.Culture == shortDescription.Culture)?.Id ?? 0;
ShortDescriptions.Add(shortDescription);
}
...
}
In the line with the arrow and "Exception", I get the following exception:
System.InvalidOperationException: 'The instance of entity type 'Product' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.'
In itself, I understand what the exception is trying to tell me. My problem is that I can't find the reason for it anywhere. Because as far as I can tell, the product with ID 1 can't be tracked yet.
Of course, the problem is not in the update method, but before it, so here is the rest of the code.
ProductController.Update:
[HttpPut("update")]
public IActionResult Update(C.Product[] products)
{
if (!ModelState.IsValid)
{
return UnprocessableEntity(ModelState.Values.SelectMany(x => x.Errors));
}
var dbProducts = products.Select(ToDatabase).ToArray(); <--- Just converts the given client model into a Database model
var result = productWorkflow.Update(dbProducts); <--- Calls a workflow class, NOT the update method of the entity
return CoreToActionResultConverter.ToActionResult<Db.Product>(result);
}
ProductWorkflow.Update:
public ResultBase Update(params Product[] products)
{
var result = productService.AddOrUpdate(products); <--- This calls the Service CRUD AddOrUpdate method
if (result is not ServiceResult<Product>)
{
return result;
}
return new ServiceResult<Product>(ResultType.AddedOrUpdated);
}
ProductService.AddOrUpdate:
public virtual ResultBase AddOrUpdate(IEnumerable<TEntity> entities)
{
var currentEntities = new List<TEntity>();
foreach (var entity in entities)
{
var currentEntity = Get(entity.Id); <--- This line is the only one where I could imagine that it is already tracked here. The problem is only that it does not work ONLY with the workflow. If I call my AddOrUpdate method from the controller, which directly calls THIS method, it works (although this line is just executed the same way).
if (currentEntity == null)
{
currentEntity = Ctx.CreateProxy<TEntity>();
Ctx.Attach(currentEntity);
}
if (currentEntity != entity)
{
currentEntity.Update(entity);
}
currentEntities.Add(currentEntity);
}
Ctx.AddRange(currentEntities.Where(x => x.Id == 0));
Ctx.UpdateRange(currentEntities.Where(x => x.Id != 0));
try
{
Ctx.SaveChanges();
}
catch (DbUpdateException ex)
{
// Commented out the error handling to remove unnecessary things for the post
}
return new ServiceResult<TEntity>(ServiceResult.ResultType.AddedOrUpdated, currentEntities);
}
I found the problem and it was not on the line where the exception was thrown, but before.
In my Product.Update() method (the first code snippet), I get the Related Products by ID and add them to the list (Simply a Many to Many relationship, from Product <--> Product). When calling Update, I specified ID 1 in the RelatedProductIds, however the entity itself also has ID 1, so it references itself. I have now just fixed that by omitting the ID, if the same as the object itself.
This still doesn't explain why it works with a breakpoint, because it's still tracked in that case (or not tracked, since I'm using AsNoTracking(), but good).

EF Core 2.0: How to discover the exact object, in object graph, causing error in a insert operation?

I have a complex and big object graph that I want to insert in database by using a DbContext and SaveChanges method.
This object is a result of parsing a text file with 40k lines (around 3MB of data). Some collections inside this object have thousands of items.
I am able to parse the file correctly and add it to the context so that it can start tracking the object. But when I try to SaveChanges, it says:
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.SqlClient.SqlException: String or binary data would be truncated.
I would like to know if there is a smart and efficient way of discovering which object is causing the issue. It seems that a varchar field is too little to store the data. But it's a lot of tables and fields to check manually.
I would like to get a more specific error somehow. I already configured an ILoggerProvider and added the EnableSensitiveDataLogging option in my dbContext to be able to see which sql queries are being generated. I even added MiniProfiler to be able to see the parameter values, because they are not present in the log generated by the dbContext.
Reading somewhere in the web, I found out that in EF6 there is some validation that happens before the sql is passed to the database to be executed. But it seems that in EF Core this is not available anymore. So how can I solve this?
After some research, the only approach I've found to solve this, is implementing some validation by overriding dbContext's SaveChanges method. I've made a merge of these two approaches to build mine:
Implementing Missing Features in Entity Framework Core - Part 3
Validation in EF Core
The result is...
ApplicationDbContext.cs
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
ValidateEntities();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken())
{
ValidateEntities();
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private void ValidateEntities()
{
var serviceProvider = this.GetService<IServiceProvider>();
var items = new Dictionary<object, object>();
var entities = from entry in ChangeTracker.Entries()
where entry.State == EntityState.Added || entry.State == EntityState.Modified
select entry.Entity;
foreach (var entity in entities)
{
var context = new ValidationContext(entity, serviceProvider, items);
var results = new List<ValidationResult>();
if (Validator.TryValidateObject(entity, context, results, true)) continue;
foreach (var result in results)
{
if (result == ValidationResult.Success) continue;
var errorMessage = $"{entity.GetType().Name}: {result.ErrorMessage}";
throw new ValidationException(errorMessage);
}
}
}
Note that it's not necessary to override the other SaveChanges overloads, because they call these two.
The Error tells you that youre writing more characters to a field than it can hold.
This error for example would be thrown when you create a given field as NVARCHAR(4) or CHAR(4) and write 'hello' to it.
So you could simply check the length of the values you read in to find the one which is causing your problem. There is at least on which is too long for a field.

Entity Framework 6 and ObjectCache: saving issues

I'm using Entity Framework 6 and ObjectCache to cache some entities that don't change often. But I faced an error when trying to save entities that are cached because they were retrieved from a different context. Before saving, I verified and the state of the object was detached but couldn't get rid of that error until I did this:
public void Save(Product obj)
{
var objInDb = dbContext.Products.Find(obj.Id);
if (objInDb == null)
{
dbContext.Products.Add(obj);
dbContext.SaveChanges();
}
else
{
dbContext.Entry(objInDb).CurrentValues.SetValues(obj);
dbContext.Entry(objInDb).State = EntityState.Modified;
dbContext.SaveChanges();
}
}
Original error that was fixed after implemented the described solution:
Attaching an entity of type 'C' 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 original code was:
if (obj.Id == 0)
{
context.Products.Add(obj);
}
else
{
context.Entry(obj).State = EntityState.Modified;
}
context.SaveChanges();
Is there a better way to handle this? I really don't like to bypass my cache and hit the DB to get the object because it looks unnecessary to me.
I think the only way of not "hitting" de db would be something like the following. Please keep in mind that, based on your first exception, you are already "hitting" the db for this entity somewhere else.
if (obj.Id == 0)
{
context.Products.Add(obj);
}
else
{
var localProduct = context.Products.Local.FirstOrDefault(x => x.Id == obj.Id);
if (localProduct == null)
{
context.Entry(obj).State = EntityState.Modified;
}
else
{
context.Entry(localProduct).CurrentValues.SetValues(obj);
}
}
context.SaveChanges();

EF 4 CTP 5: Trouble trying to remove an entity

I have created a model POCO class called Recipe; a corresponding RecipeRepository persists these objects. I am using Code First on top of an existing database.
Every Recipe contains an ICollection<RecipeCategory> of categories that link the Recipes and the Categories table in a many-to-many relationship. RecipeCategory contains the corresponding two foreign keys.
A simplified version of my controller and repository logic looks like this (I have commented out all checks for authorization, null objects etc. for simplicity):
public ActionResult Delete(int id)
{
_recipeRepository.Remove(id);
return View("Deleted");
}
The repository's Remove method does nothing but the following:
public void Remove(int id)
{
Recipe recipe = _context.Recipes.Find(id);
_context.Recipes.Remove(recipe);
_context.SaveChanges();
}
Howevery, the code above does not work since I receive a System.InvalidOperationException every time I run it: Adding a relationship with an entity which is in the Deleted state is not allowed.
What does the error message stand for and how can I solve the problem? The only thing I try to achieve is deleting an entity.
#Ladislav: I have replaced ICollection<RecipeCategory> by ICollection<Category>. Accidentially, ReSharper refactored away the virtual keyword.
However, the problem remains — I cannot delete a Category from a Recipe entity. The following code does not persist the deletion of the categories to the database:
private void RemoveAllCategoriesAssignedToRecipe()
{
foreach (Category category in _recipe.Categories.ToArray())
{
_recipe.Categories.Remove(category);
category.Recipes.Remove(_recipe);
}
_context.SaveChanges();
}
I have debugged the code and can confirm that the collections are modified correctly — that is, they contain no elements after the loop (I have also used the Clear() method). After calling SaveChanges(), they are populated again.
What am I doing wrong?
(Maybe it is important: I am using the Singleton pattern to only have one instance of the context.)
I was able to solve the problem the following way:
private void RemoveAllCategoriesAssignedToRecipe()
{
foreach (Category category in _recipe.Categories.ToArray())
{
Category categoryEntity = _categoryRepository.Retrieve(category.CategoryID);
var recipesAssignedToCategory = categoryEntity.Recipes.ToArray();
categoryEntity.Recipes.Clear();
foreach (Recipe assignedRecipe in recipesAssignedToCategory)
{
if (assignedRecipe.RecipeID == _recipe.RecipeID)
{
continue;
}
categoryEntity.Recipes.Add(assignedRecipe);
}
_context.Entry(categoryEntity).State = EntityState.Modified;
}
_recipe.Categories.Clear();
_context.SaveChanges();
}

How to get rid off "An entity object cannot be referenced by multiple instances of IEntityChangeTracker"?

I have a model in Ado.Net EF.
I have a one to many relation and when I want to Add the entities I get the error
"An entity object cannot be referenceed by multiple instances of IEntityChangeTracker"
Any clue?
Something similar to
Template template = new Template();
...
...
while (from < to)
{
Course course = new Course();
.....
template.Course.Add(course);
.....
}
courseEntities.AddToTemplate(template); // Problem line
courseEntities.SaveChanges();
I was getting this message until i started to store the data context in the HttpContext.Items Property. This means you can use the same data context for the current web request. That way you don't end up with 2 data contexts referencing the same entities.
Here is a good post on DataContext Life Management.
I hope it helps.
Dave
"template", or something that it references, has already been added to courseEntities or another context. I don't see anything in the code you show it would do that, but it is certainly happening. Perhaps it's happening in some of the code that you've trimmed. Look at the EntityState property of "template" in the debugger, and look at the EntityState of the properties of "template" as well. This should help you find out which entity instance is already in a context.
I already realize the problem. I have another relation and I get the other entity from another context.
Let me relate my experience with this nasty error and point out the terrain chasing it will take you over leading to a tremendously simple solution.
CompanyGroup is pretty simple. It has a name and it has a Company object.
I started with this:
1 public static void Add(CompanyGroup item)
2 {
3 try
4 {
5 using (Entities scope = new Entities())
6 {
7 scope.AddToCompanyGroup(item);
8 scope.SaveChanges();
9 }
10 }
11 catch (Exception ex)
12 {
13 LogException(ex, item);
14 throw;
15 }
16 }
And got this error:
{"An entity object cannot be
referenced by multiple instances of
IEntityChangeTracker."}
So, I added this between lines 6 and 7:
(IEntityWithChangeTracker)item).SetChangeTracker(null);
That rewarded me with:
{"The object cannot be added to the
ObjectStateManager because it already
has an EntityKey. Use
ObjectContext.Attach to attach an
object that has an existing key."}
So I changed
scope.AddToCompanyGroup(item);
to
scope.Attach(item);
Now it complained about:
{"An object with a temporary EntityKey
value cannot be attached to an object
context."}
(beginning to sound like some of the girls I dated in my youth -- never content -- but I digress)
So I made the entity key null (didn't work) and used the method to create new (didn't work, either)
Along the way, I got this error, too:
{"The source query for this
EntityCollection or EntityReference
cannot be returned when the related
object is in either an added state or
a detached state and was not
originally retrieved using the
NoTracking merge option."}
The Solution?
Replace the core, lines 7 and 8, with:
CompanyGroup newcg = new CompanyGroup();
newcg.GroupName = item.GroupName;
newcg.Company = scope.Company.Where(c => c.CompanyID == item.Company.CompanyID).First();
scope.AddToCompanyGroup(newcg);
scope.SaveChanges();
Essentially, I took the data passed via 'item', and moved it to newly created object of the same type that introduces the same
scope as the one used in the Add.
I hope this is the simplest and correct solution. You need one db context per httprequest.
EF4 Code First template Global.asax.cs
http://gist.github.com/574505
void MvcApplication_BeginRequest(object sender, EventArgs e)
{
HttpContext.Current.Items[SessionKey] = new Db();
}
void MvcApplication_EndRequest(object sender, EventArgs e)
{
var disposable = HttpContext.Current.Items[SessionKey] as IDisposable;
if (disposable != null)
disposable.Dispose();
}
Please initialize your Entities only one time.
Like as
If You more than one time initialize your Entities.
You will get the error:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
ex:
public class Test
{
private Entities db=new Entities();
public static void Add(CompanyGroup item)
{
try
{
db.CompanyGroup.Add(item);
db.SaveChanges();
}
catch (Exception ex)
{
}
}
}
This problem I was solved by removing from the object that I update, extra relationships with other entities (Virtual). Left only their ID.
This is wrong code
dataFileEntity.IterParameterValue = parameterValueEntity.ParameterValue;
dataFileEntity.IterParameterValueId = parameterValueEntity.ParameterValue.Id;
dataFileEntity.ResultParameter = parameterValueEntity.ResultParameter;
dataFileEntity.ResultParameterId = parameterValueEntity.ResultParameter.Id;
dataFileEntity.RawDataResult = result.Value;
This is right
dataFileEntity.IterParameterValueId = parameterValueEntity.ParameterValue.Id;
dataFileEntity.ResultParameterId = parameterValueEntity.ResultParameter.Id;
dataFileEntity.RawDataResult = result.Value;
RequestTestRawDataFileRepository.AddOrUpdate(dataFileEntity);
Я эту проблему решила, убрав из объекта, который я апдейтила лишние связи с другими сущностями (Virtual). Оставила только их id.