I'm currently developing an MVVM app using a Model-Designer based code-first design. So far I've been able to get all the basic CRUD operations working on single entities, however I can't seem to change the properties of collection objects at all using SaveChanges() - I've used an SQL profiler to see that it's attempting to UPDATE with the old value, and a step right after SaveChanges() my changes get reverted to their old values!
Some other info:
my dbContext is loaded using DI from PRISM/Unity and kept as a Unit-of-Work for a "details" page the user will edit and then save.
My WPF UI is bound correctly and can modify the changes on an object-level.
I can successfully use Add() to insert entities.
I've verified the entity state of the entity in the child collection is Modified both by setting it and simplify debugging.
I've attempted to manually Attach() and AddOrUpdate() on any or all items.
I've turned off all Lazy Loading and instead manually included all collections.
I've manually set the Entry() properties of IsModified and CurrentValue to their desired settings.
I've tried binding my VM properties to their data by either
dbContext.Classes.Local.ToBindingList() or new ObservableCollection<Class>(Entity.Property).
Is there anything that I could be missing here? Here's one attempt I've tried:
// Assigning an Index object that contains relationships
Index = await _model.PersonIndexes.Include(i => i.PersonAddresses).FirstAsync(i => i.Id == IndexId);
// Grabbing a filtered set of Addresses based on their data
var query = Index.PersonAddresses.Where(e => e.Condition == true );
Addresses = new ObservableCollection<PersonAddress>(await query.ToListAsync());
// Ensure state is tracked (I've tried with and without all combinations of these)
foreach (PersonAddress addr in Addresses)
{
//_model.PersonAddresses.AddOrUpdate(a => a.Id, addr);
if (addr.PendingRemoval)
{
_model.PersonAddresses.Attach(addr);
_model.Entry(addr).Property(a => a.PendingRemoval).CurrentValue = true;
_model.Entry(addr).Property(a => a.PendingRemoval).IsModified = true;
}
}
// Saving (after this line the properties of the entities in the related collection get reverted to their old values - i.e. if I change a phone number in the UI, the save below will replace the new values with the previous number.
await _model.SaveChangesAsync();
So it turns out this was an unfortunate error of configuration and a bad coincidence:
1) Check your models and server schema to ensure they are in sync (especially if using generated code from EF). In my case they were not, which lead to...
2) SaveChanges() was overwriting my properties in question because I had not noticed they were incorrectly set to have their StoredGeneratorPattern set to Computed in my model code. This caused the changed values to be ignored and overwritten.
3) The test case surrounding this had only implemented the same properties that were marked incorrectly, making it appear that all changes were being reverted - causing the confusion on where the problem code actually was. If another column had been modified and watched along with the others, this might have been caught sooner.
Related
How would you Upsert without select? the upsert would be a collection of entities received by a method which contains DTOs that may not be available in the database so you can NOT use attach range for example.
One way theoretically is to load the ExistingData partially with a select like dbContext.People.Where(x => x exists in requested collection).Select(x => new Person { Id = x.Id, State = x.State }).ToList() which just loads a part of the entity and not the heavy parts. But here if you update one of these returned entityItems from this collection it will not update because of the new Person its not tracking it and you also cannot say dbContext.Entry<Person>(person).State = Modified because it will throw an error and will tell you that ef core is already "Tracking" it.
So what to do.
One way would be to detach all of them from the ChangeTracker and then do the state change and it will do the update but not just on one field even if you say dbContext.Entry<Person>(person).Property(x => x.State).Modified = true. It will overwrite every fields that you haven't read from the database to their default value and it will make a mess in the database.
The other way would be to read the ChangeTracker entries and update them but it will also overwrite and it will consider like everything is chanaged.
So techinically I don't know how ef core can create the following SQL,
update People set state = 'Approved' where state != 'Approved'
without updating anything else. or loading the person first completely.
The reason for not loading your data is that you may want to update like 14000 records and those records are really heavy to load because they contain byte[] and have images stored on them for example.
BTW the lack of friendly documentation on EFCore is a disaster compare to Laravel. Recently it has cost us the loss of a huge amount of data.
btw, the examples like the code below will NOT work for us because they are updating one field which they know that it exists in database. But we are trying to upsert a collection which some of those DTOs may not be available in the database.
try
{
using (var db = new dbContext())
{
// Create new stub with correct id and attach to context.
var entity = new myEntity { PageID = pageid };
db.Pages.Attach(entity);
// Now the entity is being tracked by EF, update required properties.
entity.Title = "new title";
entity.Url = "new-url";
// EF knows only to update the propeties specified above.
db.SaveChanges();
}
}
catch (DataException)
{
// process exception
}
Edit: The used ef core version is #3.1.9
Fantastic, I found the solution (You need to also take care about your unit tests).
Entityframework is actually working fine it can be just a lack of experience which I'm documenting here in case anyone else got into the same issue.
Consider that we have an entity for Person which has a profile picture saved as Blob on it which causes that if you do something like the following for let's say 20k people the query goes slow even when you've tried to have enough correct index on your table.
You want to do this query to update these entities based on a request.
var entityIdsToUpdate = request.PeopleDtos.Select(p => p.Id);
var people = dbContext.People.Where(x => entityIdsToUpdate.Contains(x.Id)).ToList();
This is fine and it works perfectly, you will get the People collection and then you can update them based on the given data.
In these kind of updates you normally will not need to update images even if you do, then you need to increase the `TimeOut1 property on your client but for our case we did not need to update the images.
So the above code will change to this.
var entityIdsToUpdate = request.PeopleDtos.Select(p => p.Id);
var people = dbContext.People
.Select(p => new Person {
Id = p.Id,
Firstname = p.Firstname,
Lastname = p.Lastname,
//But no images to load
})
.Where(p => entityIdsToUpdate.Contains(p.Id)).ToList();
But then with this approach, EntityFramework will lose the track of your entities.
So you need to attach it like this and I will tell you how NOT to attach it.
This is the correct way for a collection
dbContext.People.AttachRange(people); //These are the people you've already queried
Now DO NOT do this, you may want to do this because you get an error from the first one from EntityFramework which says the entity is already being tracked, trust it because it already is. I will explain after the code.
//Do not do this
foreach(var entry in dbContext.ChangeTracker.Entries())
{
entry.State = EntityState.Detached;
}
//and then on updating a record you may write the following to attach it back
dbContext.Entry(Person).State = EntityState.Modified;
The above code will cause EntityFramework not to follow the changes on the entities anymore and by the last line you will tell it literally everything edited or not edited is changed and will cause you to LOSE your unedited properties like the "image".
Note: Now what can u do by mistake that even messes up the correct approach.
Well since you are not loading your whole entity, you may assume that it is still fine to assign values to the unloaded ones even if the value is not different than the one in the database. This causes entity framework to assume that something is changed and if you are setting a ModifiedOn on your records it will change it for no good reason.
And now about testing:
While you test, you may get something out from database and create a dto from that and pass the dto with the same dbContext to your SystemUnderTest the attach method will throw an error here which says this entity is already bein tracked because of that call in your test method. The best way would be create a new dbContext for each process and dispose them after you are done with them.
BTW in testing it may happen that with the same dbContext you update an entity and after the test you want to fetch if from the database. Please take note that this one which is returning to you is the "Cached" one by EntityFramework and if you have fetched it in the first place not completely like just with Select(x => ) then you will get some fields as null or default value.
In this case you should do DbContext.Entry(YOUR_ENTRY).Reload().
It is a really complete answer it may not directly be related to the question but all of the things mentioned above if you don't notice them may cause a disaster.
I have seen other questions about this same error, but I am unable to correct the error with those suggestions in my code; I think that this is a different problem and not a duplicate.
I have an app that makes a series of rules, of which the user can set properties in the GUI. There is a table of Rules in a connected database, with the primary key on the Rule.Id. When the user saves changes to a rule, the existing rule gets "IsActive=0" to hide it, then a new database record is made with the properties from the GUI written to the database. It looks to the user as though they have edited the rule, but the database actually sees a new rule reflecting the new properties (this allows for a history to be kept), connected to the old rule by another reference field.
In the C# code for the app, the View Model for each rule contains an EF Rule object property. When the user clicks "save" I use the parameters set in the view to build the ruleViewModel.Rule for each ruleViewModel they want to save, with porperties matching the GUI. The MainViewModel contains the DbContext object called dbo, so I use the ruleViewModel.Rule to write to the mainViewModel.dbo.Entry which I save to the Entity Framework. Here are the three basic steps performed for each saveable Rule View Model:
// get the rule from the GUI and use it to make sure we are updating the right rule in EF (which is connected to the mainViewModel)
var dboItem = ruleViewModel.MainViewModel.dbo.Rules.Single(r => r.Id == ruleViewModel.Rule.Id);
// set the values in the EF item to be those we got from the GUI
ruleViewModel.MainViewModel.dbo.Entry(dboItem).CurrentValues.SetValues(ruleViewModel.Rule);
// Save the differences
ruleViewModel.MainViewModel.dbo.SaveChanges();
If the user only saves a single rule, it all works fine, but if they subsequently try to save another, or if they save more than one at once, they get the following error, which is return by the ..SetValues(..) line:
Message = "The property 'Id' is part of the object's key information and cannot be modified. "
I see from other questions on this subject that there is a feature of EF that stops you from writing the same object twice to the database with a different Id, so this error often happens within a loop. I have tried using some of the suggestions, like adding
viewModel.MainViewModel.dbo.Rules.Add(dboItem);
and
viewModel.MainViewModel.dbo.Entry(dboItem).Property(x => x.Id).IsModified = false;
before the SaveChanges() command, but that has not helped with the problem (not to mention changing the function of the code). I see that some other suggestions say that the Entry should be created within the loop, but in this case, the entries are all existing rules in the database - it seems to me (perhaps erroneously) that I cannot create them inside the save loop, since they are the objects over which the loop is built - for each entity I find, I want to save changes.
I'm really confused about what to do and tying myself increasingly in knots trying to fix the error. It's been several days now and my sanity and self-esteem is beginning to wane! Any pointers to get me working in the right direction to stop the error appearing and allow me to set the database values would be really welcome as I feel like I have hit a complete dead end! The first time around the loop, everything works perfectly.
Aside from the questionable location of the DbContext and view models containing entities, this looks like it would work as expected. I'm assuming from the MVVM tag that this is a Windows application rather than a web app. The only issue is that this assumes that the Rule entity in your ruleViewModel is detached from the DbContext. If the DbContext is still tracking that entity reference then getting the entity from the DbContext again would pass you back the same reference.
It would probably be worth testing this once in a debug session. If you add the following:
var dboItem = ruleViewModel.MainViewModel.dbo.Rules.Single(r => r.Id == ruleViewModel.Rule.Id);
bool isReferenceSame = Object.ReferenceEquals(dboItem, ruleViewModel.Rule);
Do you get an isReferenceSame value of True or False? If True, the DbContext in your main view model is still tracking the Rule entity and the whole get dboItem and SetValues isn't necessary. If False, then the ruleViewModel is detached.
If the entities are attached and being tracked then edits to the view model entities would be persisted when you call a SaveChanges on the DbContext. (No load & SetValues needed) This should apply to single or multiple entity edits.
If the entities are detached then normally the approach for updating an entity across DbContext instances would look more like:
var context = mainViewModel.dbo;
foreach( var ruleViewModel in updatedRuleViewModels)
{
// This should associate the Entity in the ruleViewModel with the DbContext and set it's tracking state to Modified.
context.Entry(ruleViewModel.Rule).State = EntityState.Modified;
}
context.SaveChanges();
There are a couple of potential issues with this approach that you should consider avoiding if possible. A DbContext should be kept relatively short lived, so seeing a reference to a DbContext within a ViewModel is a bit of a red flag. Overall I don't recommend putting entity references inside view models or passing them around outside of the scope of the DbContext they were created in. EF certainly supports it, but it requires a bit more care and attention to assess whether entities are tracked or not, and in situations like web applications, opens the domain to invalid tampering. (Trusting the entity coming in where any change is attached or copied across overwriting the data state)
In a DbSet entity collection of Entity Framework (6.1.3), when I add a new item, it is not returned from the collection afterwards. This is strange and unexpected. Here's some gathered sample code:
dbContext.Entities.ToArray();
// contains 3 entries
dbContext.Entities.Add(new Entity());
dbContext.Entities.ToArray();
// still contains 3 entries
How can this be? When I query dbContext.Entities in the immediate window in Visual Studio, it says something like "Local: Count = 4". Why does it hide the new item from me?
Update: If this collection doesn't do the obvious thing – returning what was added before – what do I need to do instead? It must return all records from the database when called first, and it must also include all changes (add and remove) when called later. SaveChanges is only called when the user has finished editing things. The collection is needed before that! SaveChanges might also be called somewhere in between when the user is finished editing, but the code might return and the view be displayed again at a later time.
A DbSet has a property Local. This is an ObservableCollection that contains all elements. The DbSet object itself represents just the query to the database.
From the documentation of the Local property:
Gets an ObservableCollection that represents a local view of all
Added, Unchanged, and Modified entities in this set. This local view
will stay in sync as entities are added or removed from the context.
Likewise, entities added to or removed from the local view will
automatically be added to or removed from the context.
So if you want to access the elements, always use the Local property to do it.
After adding new entity, you have to Save the change using dbContext object,Use
dbContext.SaveChanges(); or dbContext.EntityState.Added
You have to save the changes. Try
dbContext.State = EntityState.Added;
dbContext.SaveChanges();
you can use following code to get all the items,
foreach (var track in dbContext.ChangeTracker.Entries())
{
if (track.State == EntityState.Deleted)
Entity s = (Entity)track.OriginalValues.ToObject();
else
Entity s = (Entity)track.CurrentValues.ToObject();
}
We've been using EF STEs for a while, but our application has grown quite a bit and we decided to sue the new 4.1 DbContext so we can "evolve" a separate business layer on top of our data layer without having to use different types for it.
In the elementary evaluation for the DbContext way of doing things, I am facing a little problem.
I am used to query and preload required related data like:
return context.Orders.Include("Detail").SingleOrDefault(ord => ord.ID == ID);
And then send the returned object to the UI for modification, and when returned from the UI save the changes to the database.
From what I read so far, doing the "change saving" in DbContext is easily done using code like this:
context.Entry(order).State = EntityState.Modified;
The problem with this code is that it actually marks all properties in the object as modified, a thing that's not allowed for some properties in my model (a business rule).
I resorted to the following solution (which seems to require a lot of code for a relatively small requirement! BTW, changing a modified property state to Unchanged is not supported):
context.Orders.Attach(order);
DbEntityEntry<Order> ordEntity = context.Entry(order);
string[] arr =
{
ordEntity.Property(ord => ord.ID).Name,
ordEntity.Property(ord => ord.ClientID).Name,
};
foreach (string prop in ordEntity.OriginalValues.PropertyNames)
{
if (!arr.Contains(prop))
{
ordEntity.Property(prop).IsModified = true;
}
}
context.SaveChanges();
The problem I am facing with this code is that the "Attach" statement is throwing an exception saying that there is some sort of conflict in the navigation properties in the attached object, even if no changes were made to anything at all! (saving the object exactly as it was retrieved from the database).
The error message is something like:
"Conflicting changes to the role 'Detail' of the relationship 'OrdersDatamodel.FK_Order_Detail' have been detected."
The questions are:
Is there a more "elegant" way for preventing the modification of certain object properties?
Does anybody know what's going on with the exception raised when attaching the object to the context?
Thanks.
From what I read so far, doing the "change saving" in DbContext is easily done using code like this:
context.Entry(order).State = EntityState.Modified;
You rarely need to explicitly set the state. When you modify properties, assuming they are virtual, the state will automatically change to Modified without you having to set it. Otherwise, DetectChanges will pick this up during your call to SaveChanges.
I've an object which is called Uczestnik which just got saved to database
var konsultant = uczestnik.Konsultanci;
uczestnik.Konsultanci = null; // null attached object and reuse it's ID later on for SAVE purposes
uczestnik.KonsultantNazwa = konsultant.KonsultantNazwa;
uczestnik.Szkolenie = null; // null attached object and reuse it's ID later on for SAVE purposes
uczestnik.SzkolenieID = szkolenie.SzkolenieID;
context.SzkolenieUczestnicies.AddObject(uczestnik);
context.SaveChanges();
context.Detach(uczestnik); // detatch to prevent Context problems
uczestnik.Szkolenie = szkolenie;// reassign for use in ObjectListView
uczestnik.Konsultanci = konsultant; // reassign for use in ObjectListView
After it's saved it's back into ObjectListView where user decided to change some value and the value was changed (one value from multiple to be exact). If I check value's entity state it's in Unchanged state so calling .Attach and .SaveChanges() won't do anything. I can use ChangeObjectState but if there's nothing changed then there's no sense to do so.
context.SzkolenieUczestnicies.Attach(uczestnik);
//context.ObjectStateManager.ChangeObjectState(uczestnik, EntityState.Modified);
context.SaveChanges();
How can I detect the change and prevent unnecessary traffic (I can imagine situation where nothing is changed in the object that holds files 5mb big) so resaving it makes no sense. Unless Entity is smart enough to detect that only one field was changed from 15 and change only that field?
If the entity is detached from a context you can't find out what has changed unless you are reloading the original entity from the database or you are using self-tracking entities or manage a tracking somehow yourself.
If you reload the entity you can use ApplyCurrentValues:
var originalEntity = context.MyEntities.Single(e => e.Id == detachedEntity.Id);
context.MyEntities.ApplyCurrentValues(detachedEntity);
context.SaveChanges();
This method marks the properties as modified which have different values between original and detached entity. SaveChanges will create an UPDATE statement which includes only those changed properties. If no property did change, SaveChanges does nothing.
But you are not completely free from "unnecessary traffic" because you have to load the original entity, you will save an unnecessary UPDATE statement though.
If you Detach entity it is not tracked by the context. In such case you are responsible for detecting when the object has changed and inform the context about changes by using ChangeObjectState. So you must track what user has modified or implement something directly to your entities. For example implement INotifyPropertyChanged (if you are using EntityObject based entities this interface should be already implemented).