Entity Framework 6 and ObjectCache: saving issues - entity-framework

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();

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).

When does the entity get detached in Entity Framework?

I have seen in many posts that if the tracking is not enabled, the entity is detached.
What I would like to know is: how can there be few objects which are not tracked and a few which are tracked?
Can someone share the code snippet which shows that this entity is not tracked by a context.
According to MSDN:
Detached: the entity is not being tracked by the context
https://msdn.microsoft.com/en-us/library/jj592676(v=vs.113).aspx
According to following post which I read:
http://blog.maskalik.com/entity-framework/2013/12/23/entity-framework-updating-database-from-detached-objects/
var entry = _context.Entry<T>(entity);
if (entry.State == EntityState.Detached)
{
_context.Set<T>().Attach(entity);
entry.State = EntityState.Modified;
}
Detached objects, or objects that are created outside of Entity Framework (EF), don’t have automatic tracking enabled.
And creating a POCO class in code-first approach is one such example of a detached entity.
Is this the only scenario?
There are more scenarios for the Detached Object.
1.You dont want to track an entity.
var entity= context.MyEntities.AsNoTracking().Where(...).FirsOrDefault();
In this query entities retrieved are not tracked hence any changes on the entities will not be recorded to database.
Consider this.
entity.Name = "1";
context.SaveChanges();
As this entities are not tracked the changes will not be saved
unless you attach this.
var entry = _context.Entry<T>(entity);
if (entry.State == EntityState.Detached)
{
_context.Set<T>().Attach(entity);
entry.State = EntityState.Modified;
}
2.Consider your working on disconnected architecture (API,Web). Consider an employee API which have PUT endpoint.
This would attach the employee to the context and update the entity as context is not aware of this entity.
Advantage : No need to fetch the employee entity from the database.
Disadvantage : Someother user changes the entity between the transaction might be losed (you can still update property that are only changed)
public void UpdateEmployee(Employee entity)
{
var entry = _context.Entry<Employee>(entity);
if (entry.State == EntityState.Detached)
{
_context.Attach(entity);
entry.State = EntityState.Modified;
}
Context.SaveChanges()
}
Second Version
public void UpdateEmployee(Employee entity)
{
var dbItem = context.EmployeeEnities.FirstOrDefault(g=>g.Id==entity.Id);
//Context is already have track of this entity, you can just update properties you have changed.
dbItem.Name = entity.Name;
Context.SaveChanges()
}

DbContext and RejectChanges

I was working with RIA services where ObjectContext has RejectChanges() method. However, I am now working with EF 4.4 in a desktop application and I cannot find that method. So, my question is: in a scenrario where I allow user to do batch CrUD operations on collection, how would I revert all the changes? I could go with recreating the Context and fetching the data again, but that sound highly unnecessary if I need to revert changes back to 1-2 entities.
So, what is the best way to reject changes? And also, how do we know if the context is doing something (IsBusy)?
EF doesn't have any direct "reject changes" operation. You can go through entity entries in ChangeTracker / ObjectStateManager and overwrite current values with original values for modified entities. You can also detach added entities and change deleted entities back to unchanged but that all will mostly work only if you (or EF internally) didn't change the state of any independent association (relation). If you work with relations as well the whole thing can become much more complicated because you must revert relations as well - in such case it is simpler to reload data.
For reverting changes in DbContext API you can try this:
foreach (var entry in context.ChangeTracker
.Entries<YourEntityType>()
.Where(e => e.State == EntityState.Modified))
{
entry.CurrentValues.SetValues(entry.OriginalValues);
}
In this case I think the main problem is the way how you work with entities - you allow changes on live data and EF does its logic on behind to keep data consistent when changes are performed but later on you decide that those changes will not be saved. In this case you should do one of following:
Discard changed data and reload whole data set (by recreating the context)
Separate this logic to not work on live data and push data modification to EF context only when the modification is really confirmed
Context is doing something if you say it to do something. It never becomes busy itself.
public void RejectChanges()
{
foreach (var entry in ChangeTracker.Entries())
{
switch (entry.State)
{
case EntityState.Modified:
{
entry.CurrentValues.SetValues(entry.OriginalValues);
entry.State = EntityState.Unchanged;
break;
}
case EntityState.Deleted:
{
entry.State = EntityState.Unchanged;
break;
}
case EntityState.Added:
{
entry.State = EntityState.Detached;
break;
}
}
}
}
I know this is an old question. However, none of the answers fit my situation. I needed to reject the changes on only 1 entity in a collection. This is what worked for me:
var objectContext = (myDbContext as IObjectContextAdapter).ObjectContext;
objectContext.Refresh(RefreshMode.StoreWins, partMaster);
This works for me:
public void RejectChanges() {
var context = ((IObjectContextAdapter)this).ObjectContext;
foreach (var change in this.ChangeTracker.Entries()) {
if (change.State == EntityState.Modified) {
context.Refresh(RefreshMode.StoreWins, change.Entity);
}
if (change.State == EntityState.Added) {
context.Detach(change.Entity);
}
}
}
this=DbContext in this case
This may be an old answer but useful to any new visitors....
The Reload function will reload the object from the data source and overwrite any existing changes and the newly loaded entity will have a unchanged status.
public static void UndoEntityChanges(object Entity)
{
<EFModelContainer>.Entry(Entity).Reload();
}

Updating a list of foreign keys in EF4, using MVC 2 Repository Viewmodel Pattern

Okay, I'm really struggling with how to update a list of foreign keys in MVC2/EF4.
I have a one to many relationship between a Template object which can have many or no TemplateScenario objects.
Essentially, I have an edit method in a controller that is trying to do this:
// POST: /Modes/Edit/1
[HttpPost]
public ActionResult Edit(int id, FormCollection formValues)
{
Template template = _templateRepository.GetTemplate(id);
TemplateCreateViewModel viewModel = new TemplateCreateViewModel();
viewModel.Template = template;
viewModel.TemplateScenarioList = template.TemplateScenarios.ToList();
//Update the model
UpdateModel(viewModel);
UpdateModel(viewModel.Template.TemplateScenarios, "TemplateScenarioList", new[] { "ScenarioID", "TemplateID" });
_templateRepository.Save();
return RedirectToAction("Edit", new { id = template.TemplateID });
}
This code successfully updates the 'template' object. It also adds the 'templatescenario' child objects BUT only if it is the first time I have added 'templatescenarios' to this particular template. If any templatescenario objects already exist for a given template, and I try to update them based on the new list, I get this error:
"The operation failed: The relationship could not be changed because one or more
of the foreign-key properties is non-nullable. When a change is made to a
relationship, the related foreign-key property is set to a null value. If the
foreign-key does not support null values, a new relationship must be defined,
the foreign-key property must be assigned another non-null value, or the
unrelated object must be deleted."
The _templateRepository.Save(); is just calling the entities.SaveChanges() EF4 method.
I can solve this in a dirty way by passing down a list of templatescenario ids to my repository class in a custom 'update' method that looks like this:
public void Update(Template template, IList<int> templateScenarios)
{
//Delete Old Entries
foreach (TemplateScenario ts in entities.TemplateScenarios)
{
if (ts.TemplateID == template.TemplateID)
{
if (templateScenarios == null)
entities.TemplateScenarios.DeleteObject(ts);
else if (!templateScenarios.Where(tsl => tsl == ts.ScenarioID).Any())
entities.TemplateScenarios.DeleteObject(ts);
}
}
//Don't need to add anything if they are null.
if (templateScenarios == null)
return;
//Add New Entries
foreach (int ts in templateScenarios)
{
if (!entities.TemplateScenarios.Where(tsc => tsc.ScenarioID == ts && tsc.TemplateID == template.TemplateID).Any())
{
TemplateScenario tempScenToAdd = new TemplateScenario();
tempScenToAdd.ScenarioID = ts;
tempScenToAdd.TemplateID = template.TemplateID;
entities.TemplateScenarios.AddObject(tempScenToAdd);
}
}
}
But that just feels dirty and I think I'm so close with the first, more automatic method. I've scoured the internet and found some similar posts on stackoverflow but am finding it difficult to reach that 'aha' moment.
Thanks,
Tom.
Incidently, I sorted out my problem.
The problem was my joining table was incorrectly using it's own primary key instead of using a composite key based on two foreign keys. This is obviously wrong /bad practice and EF4 and UpdateModel() don't play nice.
I had inherited the DB design from an ex-collegue and thus had taken the db design as correct without thinking too much about it. Very stupid of me, I know.

How to delete an object by id with entity framework

It seems to me that I have to retrieve an object before I delete it with entity framework like below
var customer = context.Customers.First(c => c.Id == 1);
context.DeleteObject(customer);
context.Savechanges();
So I need to hit database twice. Is there a easier way?
In Entity Framework 6 the delete action is Remove. Here is an example
Customer customer = new Customer () { Id = id };
context.Customers.Attach(customer);
context.Customers.Remove(customer);
context.SaveChanges();
The same as #Nix with a small change to be strongly typed:
If you don't want to query for it just create an entity, and then delete it.
Customer customer = new Customer () { Id = id };
context.Customers.Attach(customer);
context.Customers.DeleteObject(customer);
context.SaveChanges();
Similar question here.
With Entity Framework there is EntityFramework-Plus (extensions library).
Available on NuGet. Then you can write something like:
// DELETE all users which has been inactive for 2 years
ctx.Users.Where(x => x.LastLoginDate < DateTime.Now.AddYears(-2))
.Delete();
It is also useful for bulk deletes.
If you dont want to query for it just create an entity, and then delete it.
Customer customer = new Customer() { Id = 1 } ;
context.AttachTo("Customers", customer);
context.DeleteObject(customer);
context.Savechanges();
I am using the following code in one of my projects:
using (var _context = new DBContext(new DbContextOptions<DBContext>()))
{
try
{
_context.MyItems.Remove(new MyItem() { MyItemId = id });
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
if (!_context.MyItems.Any(i => i.MyItemId == id))
{
return NotFound();
}
else
{
throw ex;
}
}
}
This way, it will query the database twice only if an exception occurs when trying to remove the item with the specified ID. Then if the item is not found, it returns a meaningful message; otherwise, it just throws the exception back (you can handle this in a way more fit to your case using different catch blocks for different exception types, add more custom checks using if blocks etc.).
[I am using this code in a MVC .Net Core/.Net Core project with Entity Framework Core.]
This answer is actually taken from Scott Allen's course titled ASP.NET MVC 5 Fundamentals. I thought I'd share because I think it is slightly simpler and more intuitive than any of the answers here already. Also note according to Scott Allen and other trainings I've done, find method is an optimized way to retrieve a resource from database that can use caching if it already has been retrieved. In this code, collection refers to a DBSet of objects. Object can be any generic object type.
var object = context.collection.Find(id);
context.collection.Remove(object);
context.SaveChanges();
dwkd's answer mostly worked for me in Entity Framework core, except when I saw this exception:
InvalidOperationException: The instance of entity type 'Customer' cannot
be tracked because another instance with the same key value for {'Id'}
is already being tracked. When attaching existing entities, ensure
that only one entity instance with a given key value is attached.
Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to
see the conflicting key values.
To avoid the exception, I updated the code:
Customer customer = context.Customers.Local.First(c => c.Id == id);
if (customer == null) {
customer = new Customer () { Id = id };
context.Customers.Attach(customer);
}
context.Customers.Remove(customer);
context.SaveChanges();
A smaller version (when compared to previous ones):
var customer = context.Find(id);
context.Delete(customer);
context.SaveChanges();
In EF Core, if you don't care if the object exists or not, and you just care that it will not be in the DB, the simplest would be:
context.Remove(new Customer(Id: id)); // adds the object in "Deleted" state
context.SaveChanges(); // commits the removal
You don't really need Attach() - it adds the object to the change tracker in the Unchanged state and Remove() adds the object to the tracker in the Deleted state. The most important thing, however, is that you do only one roundtrip to the backend.
Raw sql query is fastest way I suppose
public void DeleteCustomer(int id)
{
using (var context = new Context())
{
const string query = "DELETE FROM [dbo].[Customers] WHERE [id]={0}";
var rows = context.Database.ExecuteSqlCommand(query,id);
// rows >= 1 - count of deleted rows,
// rows = 0 - nothing to delete.
}
}
From official documentation (and the most efficient one I have found so far):
Student studentToDelete = new Student() { ID = id };
_context.Entry(studentToDelete).State = EntityState.Deleted;
await _context.SaveChangesAsync();
Easier and more understandable version.
var customer = context.Find<Customer>(id);
context.Remove(customer);
context.SaveChanges();
Since Entity Framework Core 7 you can use this:
await context.Customers.Where(c => c.Id == 1).ExecuteDeleteAsync();