I have 6 related tables. I am using a view model to show the properties from all 6 tables in my view. I am able to add data in single transaction using the above structure. But while editing the data I get - "Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded." error message.
From the message it seems that the 0 rows were affected in some table. In my view it might be possible that not every value will be edited. Only some values belonging to some table will be edited and some will be stored as it is. So if the values from one table are not at all edited and if I try to do the following, then the above error pops up-
db.Entry(tbl).State = EntityState.Modified;
db.SaveChanges();
Is there a way to Modify the entity state of only those tables whose values are edited in the Edit View? Or is there any other better approach for this?
Please help. Thanks.
For a project here we did the following:
Perhaps an important one, is the Context.People.Attach() method.
Context.People.Attach(person);
// Disable validation otherwise you can't do a partial update
Context.Configuration.ValidateOnSaveEnabled = false;
Context.Entry(person).Property(x => x.AddressId).IsModified = true;
Context.Entry(person).Property(x => x.Birthday).IsModified = true;
Context.Entry(person).Property(x => x.Name).IsModified = true;
...
await Context.SaveChangesAsync();
Perhaps this is something you can work with? Not sure if the same approach can help for your case.
Example of editing an entity:
//Get the database entry
var entity = db.Person.First(c=>c.ID == 1);
//OR
//Attach a object to the context, see Nsevens answer.
db.Person.Attach(entity);
//Change a property
entity.Job = "Accountant";
//State it's a modified entry
db.Entry(entity).State = EntityState.Modified;
//Save
db.SaveChanges();
If you are editing multiple entries you will need to set every one to EntityState.Modifed, for example in a foreach loop.
Related
In EF Core you can "reload" an Entity from the data store to pickup any changes.
An example of why you might need to do that is if you need to resolve a DB concurrency exception on SaveChanges.
This does work ok for one record...
EntityEntry<T> entityEntry = GetEntity(123);
entityEntry.Reload();
The only problem is, the Reload() executes a SQL statement per entityEntry.
So, if you want to refresh a set of entityEntry, you get a SQL statement per entry.
Whereas, the normal context.Set<T>().Load(); executes one SQL statement that retrieves all of the rows.
With a small number of entityEntry, the performance hit is negligable; but anytime I see a RBAR design approach, it raises a red flag for me.
Question: other than a RBAR loop
foreach (var e in context.ChangeTracker.Entries<T>)
{
e.Reload();
}
is there a way to re-execute the context.Set<T>().Load(); to reload the entries as a set?
Note: I am using the
context.Entry(e).State = EntityState.Detached
approach now; checking to see if there is something better that I have missed.
UPDATE 1
How I am testing
Read data from database
Add record to database
Delete record from database
Change record in database
"reload" data
the result I am looking to get is
entity for changed record is updated
entity for deleted record is removed
"optionally" entity for new record is added
the Add is optional because that is not the designed behaviour of "reload", which is to update existing entity.
I started working on a project in my work that doesn't have any documentation, and the person who developed the project in the first place isn't avalaible anymore.
There is this piece of code for doing and update to the database
_report = db.Report.Where(x => x.IdReport == ReportId).FirstOrDefault();
db.Report.Attach(_report);
_report.attr1 = reportmodel.attr1;
_report.attr2 = reportmodel.attr2;
_report.attr3 = reportmodel.attr3;
if (db.SaveChanges() != 0)
{
return View(reportmodel)
}
Looks fine and indeed does the update to the database in the table "Report", but additionally it is being inserted in another table "ReportLog" the detail of the change (orginal value, new value), I believe this is being done somehow in the SaveChanges().
So my question is where can I find where those insertions to the log table are being executed?
I have checked in the model if the table "Report" has some stored procedure mapped in the update action, checked for triggers and stored procedures in the database and used Find(Ctrl+f) to check for "ReportLog" in the entire solution, but I couldn't find where the insertion is being executed.
And something really weird is that this happens for the "Report" table only, using SaveChanges() for other updates in other tables does only what is expected
I found a trigger on the Report table that was doing the inserts
I am writing a .net/entity framework code snippet that's supposed to update/delete a bunch of MS SQL rows with the latest data passed from UI.
Say the table originally has 20 rows and the latest collection contains 15 records. Out of the 15, 9 have changes and 6 remain the same. So 9 rows will be updated, and the 5 rows that are not in the latest collection, will be deleted from the table.
So my question is, what's the best way of doing this: If I iterate over all 20 rows and try to find each of them, it would be O(mn). Deleting all table rows and re-insert them may be faster but I am not sure.
All help appreciated!
So you have a user interface element filled with items of some class. You also have a database with a table filled with items of the same class.
IEnumerable<MyClass> userInterfaceElements = ...
IQueryable<MyClass> databaseElements = ...
Note: the query is not executed yet!
You want to update the database such, that after your update your database contains the items from your user interface elements.
User interface elements that are not in the database yet will be added
Database elements that are not in the user interface need to be removed
User interface elements that are also in the database need to be updated.
You didn't write how you decide whether a user interface element is also in the database.
Let's assume you don't invent primary keys. This means that elements with a default value (zero) for your primary key are elements that are not in the database.
var itemsToAdd = userInterfaceElements.Where(row => row.Id == 0);
var itemsToUpdate = userInterfaceElements.Where(row => row.Id != 0);
var idsItemsToKeep = itemsToUpdate.Select(row => row.Id);
var itemsToRemove = databaseElements.Where(row => !idsItemsToKeep.Contains(row.Id))
The last one: remove all items that have an Id that is not in your user interface elements anymore.
Note: we still have not executed any query!
Adding the items to your database will change databaseElements, so before you make any changes you need to materialize the items
var addList = itemsToAdd.ToList();
var updateList = itemsToUpdate.ToList();
var removeList = itemsToRemove.ToList();
By now you've queried your database exactly once: you fetched all items to remove. You can't order entity framework to remove items without fetching them first.
dbContext.MyClasses.RemoveRange(removeList);
dbContext.MyClasses.AddRange(addList);
To update in entity framework, the proper method would be to fetch the data and then change the properties.
Some people prefer to attach the items to the dbContext's change tracker and tell that it is changed. This might be dangerous however, if someone else has changed some properties of these items, especially if you don't show these values in your user interface elements. So only do this if you really have a long list of items to update.
Proper way:
foreach(var itemToUpdate in updateList)
{
var fetchedItem = dbContext.MyClasses.Find(itemToUpdate.Id);
// TODO: update changed properties of the fetchedItem with values from itemToUpdate
}
Dangerous method:
foreach(var itemToUpdate in updateList)
{
dbContext.Entry(itemToUpdate).State = entityState.Modified;
}
Finally:
dbContext.SaveChanges();
Improved delete method
You've got a problem when you filled your user interface element with database values, and some other process removed one of these values from your database.
When your code looks at the primary key, it will think it is in the database, however it is not there anymore. What to do with this element? Add it again? Act as if the user also wanted it to be deleted?
To solve this kind of problems, quite often people don't delete items from their database, but declare them obsolete instead. They add a Boolean column to the table that indicates whether the item is to be deleted in the near future. This solves the problem that people want to update items while others want them to be removed.
Regularly, every month or so, a process is started to remove all obsolete objects. The chance that you want to update an obsolete object are much lower.
If this needs to be full save: don't remember a Boolean obsolete, but the obsolete date. Periodically remove all items that are obsolete for a longer time.
The nice thing about the obsolete, is that if someone declared an item obsolete by accident, there is still some time to repair this.
I am trying to insert a record into my database based on an existing record which functions as template using Entity Framework.
The flow right now is:
var newModel = organization.Models.Where(m => m.IsTemplate == true).FirstOrDefault();
newModel.Name = "Something New";
newModel.IsTemplate = false;
organization.Models.Add(newModel);
_organizationRepo.SaveChanges();
Note that organization.Modelsis read from the repository beforehand.
My problem is that instead of creating a dublicate record with the new name and no tempalte flag, the template record is altered.
Criteria:
I want to create a new record which is a copy of an existing record
I want to alter the new record
I do NOT want to alter the existing record
I would assume Entity Framework would interpret the organization.Models.Add(newModel) statement as insert new record but it does not.
The solution to the problem was (as Arie commented on the original question) to use AsNoTracking() as described here when finding the template entry.
One catch using this is that this workaround is not meant as
"read values (template) -> Change as needed -> insert new record"
but rather a read-only operation. This meant that I had to manually eager load all references to the template in order to ensure these getting included in the Add() operation on the DataContext.
I did this by loading properties into a variable before saving the object eg.
var modelFromTemplate = organization.Models.FirstOrDefault(m => m.IsTemplate == true);
var eagerLoadParams = modelFromTemplate.Parameters.ToList();
//Change stuff as needed on the template here
modelFromTemplate.Name = "Something New";
modelFromTemplate.IsTemplate = false;
//Save the new model
_modelRepo.Add(modelFromTemplate);
_modelRepo.SaveChanges();
The goal is simple. I need to update the LastUpdated column in the Schedule table. I've tried several different methods to achieve that goal but with no success. I'm certain the code is pointing to the correct database and I'm also checking the correct [local] database for the changes. When a break point is set on SaveChanges(), the code halts at that point. I can see that "db" contains the updated Date/Time information for the correct record. Yet, it does not save it to the database.
Having gone through Stack Overflow, I've tried some suggestions like using Attach and setting the Entity State [to Modified]. Neither of those suggestions worked. HasChanges returns false, even though I can see the change is applied to the context variable.
Also, the class this method is in contains other methods that have no problem accessing the database and doing some inserts. The below code is just three different attempts to give you an idea on how I'm trying to do it. Any suggestions would be greatly appreciated.
public static void UpdateLastUpdated(int scheduleId)
{
using (var db = new MyContext())
{
var schedule = from s in db.Schedule where s.Id == scheduleId select s;
schedule.FirstOrDefault().LastUpdated = DateTime.Now;
db.SaveChanges();
var schedule2 = db.Schedule.Find(scheduleId);
schedule2.LastUpdated = DateTime.Now;;
db.SaveChanges();
var schedule3 = db.Schedule.Single(s => s.Id == scheduleId);
schedule3.LastUpdated = DateTime.Now;
db.SaveChanges();
}
}
You must indicate the change
db.Entry(schedule3).State = EntityState.Modified;
or
db.Entry(schedule3).Property(x => x.LastUpdated).IsModified = true;
So as it turns out, after a lot of trial and error... The issue was because the column was computed. I tried updating another column in the same table from that method and it worked fine. Then I did some research on computed columns and found that to be the problem. After removing the annotation, the code works fine. Now I just need to figure out how to get the default value set without the annotation.
Thank you to everyone who offered solutions and comments. Much appreciated!