Attach and update boolean or int doesn't always work - c#

Using Entity Framework 5, Given an object messenger that exists in the database, with bit property published, in previous versions of EF I would update it like this:
using (var c = new EFContext())
{
Data.Messenger cm = new Messenger { MessageId = messageId };
c.Messengers.Attach(cm);
cm.Published = newPublishedValue;
c.SaveChanges();
}
However, using EF5, this works if newPublishedValue is true, but if newPublishedValue is false, no changes are made to the database. Likewise, this pattern will not set integer values to 0.
What's going on here?
Replacing
c.Messengers.Attach(cm);
with
c.Entry(cm).State = System.Data.EntityState.Modified;
Fixes the issue, but if EF5 won't update properties of my new Data.Messenger that are of a default struct value, doesn't that render the Attach method a bit useless?
What am I missing here?

It only worked in previous versions probably when you have used entities derived from EntityObject or POCOs with dynamic proxy change tracking. In that case the property setter is overridden with code that sets a Modified flag for this property when you assign a value (= call the setter) no matter what the old value was.
Using POCOs without dynamic change tracking proxies it doesn't work - for no version of EF - because the property setter is just an assignment of a data field in the entity class. Change tracking happens by snapshot change tracking which means that EF compares the values of a snapshot of the entity taken when you call Attach with the values the entity has when you call SaveChanges. If the values didn't change EF detects no changes and doesn't write an UPDATE statement for the property.
Attach is only for adding an entity to the context in Unchanged state. In your case you have to mark the property as Modified explicitly to ensure that it will be updated in the DB, no matter what the old value was:
c.Entry(cm).Property(x => x.Published).IsModified = true;

Related

Overwrites collection item's properties back to old data on SaveChanges()

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.

Set IsModified to false on few columns of entity in EF 4.0

How can i set IsModified to false on certain properties of my EntityObject.
here is my code
message.To = message.To.Decrypt(encryptionKey);
now i want to set message.To as IsModified= false, so that while save changes, EF will ignore changes to this column.
I am using EF 4.0
Interesting question! As far as I realized EF 4.0 doesn't have such straightforward solution for this (fortunately later versions of EF solved this in a nice manner).
Anyway, I'll tackle this problem in 3 stages:
1- if message is already attached to the context and since the time of attachement it has been modified, we first record its modified properties in a list
var modifiedProps = context.ObjectStateManager.GetObjectStateEntry(message).GetModifiedProperties().Where(p=>p.Equals('To')==false).ToList();
If it was not attached, we attach it
context.Attach(message); // the sate of the message will be UnChanged
var modifiedList = any property changed based on what you have done so far;
2- Change the state of the object to UnChanged, this way every property is excluded from Update's SETclause
var entry = context.ObjectStateManager.GetObjectStateEntry(message);
entry.ChangeState(EntityState.Unchanged);
3- Mark previously modified properties as Modified except To property
modifiedProps.ForEach(p=> entry.SetModifiedProperty(p));
Finally save your changes through context.SaveChanges();
Edit:
I forgot to exclude property To in Step #1. It is now corrected
the following did the job for me
wrote the below line immediately after decrypting the properties
_db.ObjectStateManager.ChangeObjectState(message, EntityState.Unchanged);
setting the whole object as unmodifed. Though this is not what i asked for, but EF 4.0 does not provide facility to set a Property as Unchanged directly.

Why can't you attach new objects to tracked objects using navigation references?

In short, why does this fail (myChildObject is not added to the database). Note that it works with ObjectContext:
using (var db = new dbEntities())
{
var myParentObject = db.parentObjects.First(); // this definitely exists in the db
// this **should** attach myChildObject to the context,
// and therefore add it to the db on .SaveChanges() - it doesn't!
var myChildObject = new childObject(){
parentObject = myParentObject
};
db.SaveChanges();
}
This MSDN Blog Post says
You can add a new entity to the context by hooking it up to another entity that is already being tracked. This could be by adding the new entity to the collection navigation property of another entity or by setting a reference navigation property of another entity to point to the new entity.
Surely the above code should work because myChildObject references myParentObject which is a tracked object. EF should be smart enough to figure out that it needs adding into the childObjects collection. It worked fine when I was using ObjectContext and now I'm finding that I need to rewrite all of my code to get it to work with dbContext.
To get it to work I have to rewrite it like this:
using (var db = new dbEntities())
{
var myParentObject = db.parentObjects.First(); // this definitely exists in the db
var myChildObject = new childObject();
myParentObject.childObjects.Add(myChildObject);
db.SaveChanges();
}
If you were using POCO entities with ObjectContext it worked indeed. But not because EF's change tracking worked differently than with DbContext but because the POCO entities generated by the EF 4 T4 templates contained "relationship fixup" methods.
Basically the property setter for the line parentObject = myParentObject wasn't only an object assignment but the setter included a call to a method that in the end exactly did what you are doing manually now, namely: myParentObject.childObjects.Add(myChildObject). At this point the rule "You can add a new entity to the context by hooking it up to another entity that is already being tracked" applies and myChildObject gets added to the context and inserted into the database.
For the T4 templates that generate POCO entities for DbContext those fixup methods have been removed because they were causing trouble in other scenarios. Especially when lazy loading is involved your reference assignment and the automatic call of myParentObject.childObjects... in the property setter would trigger lazy loading on the collection and load all childObjects first that are already stored for myParentObject before the new child is added to the collection. If those are thousands this is a huge unnecessary overhead, performance gets disastrous and the reason for a suddenly bad performance (just because you assigned a single reference property) isn't easy to detect if you are not aware of the fixup methods that run behind the scenes.
Here and here and here and here are examples about the confusion that relationship fixup methods were causing.
You could modify the T4 templates and add relationship fixup methods again - or if you are using Code-First just write them by hand in your entity classes - to get the old behaviour back. But this might be more complex than and at least as much work as changing your existing code the way you've outlined in your last code snippet - which I would certainly prefer over having those bothersome fixup methods back.
#theyetiman you're doing a little interpretation mistake of the blog text.
see:
[...]
or by setting a reference navigation property of another entity to point to the new entity.
In this part the blog said you can set a reference navigation property of a tracked object with a new entity.
like this:
[tracked entity].NavigationProperty = [new entity];
But you tring to do:
[new entity].Navigation Property = [tracked entity];
This not works. If your childObject was tracked and parentObject not you would be able to add parentObject setting it in childObject property, but the opposite is not true.

How to find out if an Entity Framework object has changed?

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

Entity Framework 4.1 - default EntityState for a FK?

I'm having a small problem with ASP.NET MVC and Entity Framework 4. I have an entity called "UF" and another one called "Pais", and they have this relation:
UF [* ... 0..1] Pais
I can access the Pais object directly from UF using a navigation property:
UF.Pais.Whatever = "foobar";
Currently I have a View which inserts a new item into the database, and it has an editor for "Pais.Codigo" ("Codigo" is the primary key for Pais). So when I insert a new UF, the framework creates an instance of class UF with a reference to an instance of class Pais. Then this is done:
if (ModelState.IsValid)
{
db.UFs.AddObject(uf);
db.SaveChanges();
return View();
}
The problem is that the EF is inserting a new Pais into the database, so it basically ignores the existing one.
For example, if let's say my object UF has a Pais with an ID of 1. The current value of uf.Pais.Codigo is 1. Other attributes, like the description, is currently null. When I execute the SaveChanges, both "uf" and "uf.Pais" are with the state of Added. The correct state for "uf.Pais" should be Unchanged, since it already exists on the database.
My question is: there's some way of changing the default relationship EntityState for Unchanged? The following code solves the problem, but adding it to each function with adds a new entry to the database (and for each FK) is overkill!
db.ObjectStateManager.ChangeObjectState(uf.Pais, EntityState.Unchanged);
That's it. I'm not sure if I was clear enough. Feel free to ask more information if needed. And sorry for any english mistakes!
Thanks,
Ricardo
PS: "Pais" stands for Country and "UF" for State.
My question is: there's some way of changing the default relationship
EntityState for Unchanged?
Yes by calling Attach instead of Unchanged.
The following code solves the problem, but adding it to each function
with adds a new entry to the database (and for each FK) is overkill!
No it is not overkill, it is a solution because either Attach or AddObject will always make the operation for all entities and associations in entity graph. That means that calling AddObject will make everything what context is not aware of yet as Added and Attach will make everything what context is not aware of as Unchanged (so you will in turn have to set each modified or inserted entity to its correct state). That is how EF works if you are using detached entities.
Another solution for the problem is making the connection after the UF is added:
// Here UF.Pais is null
db.UFs.AddObject(uf);
// Create dummy Pais
var pais = new Pais { Id = "Codigo" };
// Make context aware of Pais
db.Pais.Attach(pais);
// Now make the relation
uf.Pais = pais;
db.SaveChanges();
If you are working with detached entities you are always responsible for setting the correct state for each entity (and independent association). So you will either use attached entities to let EF make the magic for you (as shown in the example) or you will use the approach you dislike. In more complex scenarios you can find out that best approach is to load entity graph again and merge incoming changes into the attached graph.

Categories