Can I track changes of child entities using ef 5?
Example:
Domain objects: Book (one to many) Page
var oldBook = context.Books.Include("Pages");
context.Entry(oldBook).CurrentValues.SetValues(updatedBook);
This code will update simple properties of the old book object with values of simple properties from updatedBook object.
It there any way to track children collection?(Pages in this case). Or any best practices how to do it?
Your questions is a bit ambiguous.
Can I track changes of child entities using ef 5?
Of course, unless you explicitly disable change tracking using IQueryable.AsNoTracking() or MergeOption.NoTracking then you can track changes of any entity attached to the DBContext ObjectStateManager.
If you are really asking if there's a function where you can do this:
context.Entry(oldBook).CurrentValues.SetValues(updatedBook);
And have the current values of the entire object graph -- where oldbook is the root -- set to the updated values of the updatedBook object graph then, no.
You have to loop through and call context.Entry(oldPage).CurrentValues.SetValues(updatedPage) for each page you wished to update.
I take it that you are in a disconnected scenario where you can't just pull the entities from the database and use automatic change tracking and set the modified values directly on the tracked entities, otherwise you could just use a single object graph attached to the context instead of working with two instances (ie oldbook, updated book).
If you already have a detached modified object graph that you need to work with an alternative to retrieving the entity from the db and using SetValues() is to attach the entities to the context as modified. You still need to loop through and do the same for all entities in the object graph.
context.Entry(updatedBook).State = EntityState.Modified;
foreach(var p in updatedBook.Pages) context.Entry(p).State = EntityState.Modified;
context.SaveChanges();
Related
I have this problem where I have 2 entities connected by foreign key.
AEntity: id, idOfEntityB (foreign key, constraint), fields...
BEntity: id, fields...
I save both of them to the database with SaveChanges(), later when I try to get AEntity's idOfEntityB, I succeed but when I try to get BEntity according to the id I got from AEntity, I get nothing:
context.AEntities.Add(new AEntity {
BEntity = new BEntity { ... }
});
context.SaveChanges();
.
.
.
var id1 = context.AEntities.Select(x => x.idOfEntityB);
var bEntities = context.BEntities.Where(x => id1.Contains(x.id));
bEntities has nothing in it. but the fact I was able to have values in id1 is even more confusing since they have foreign key relations (with constraint) and furthermore, id could not be created if it was not saved to the DB.
Later, when I look in the DB I see both entities as should be.
It happens sometimes and I cant reproduce the problem, I cant give more then this as an example since there's a lot of code, I believe it has something to do with caching, and therefore would like to ask if something like that is possible or not and how.
is there a way entities are saved to the DB while the context (a different one used from the context that saved) does not hold all of them in completion?
This is likely the issue you are encountering if you are relying on seeing changes between state changes between different DbContext instances. When a DbContext has loaded entities, then another DbContext instance makes changes to those records or the records change behind the scenes in the database, that original DbContext will not refresh the entities from the database.
EF does support the ability to reload entities from the database, but when dealing with child collections it gets a bit more complicated to perform a full refresh. You effectively need to tell the DbContext to forget all of the child collections, stop tracking the parent, clear the parent's child collection, then re-attach and reload the child collection. I recently covered this in the answer for this question: Replacing a entity collection in Entity Framework Core causes DbContext to fetch the new values when not saved to db. How to reload the collection?
Ultimately a DbContext lifespan should be kept as short as possible.
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).
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.
I want to convert a list to EntityCollection.
List<T> x = methodcall();
EntityCOllection<T> y = new EntityCollection<T>();
foreach(T t in x)
y.Add(t);
I get this error.
The object could not be added to the
EntityCollection or EntityReference.
An object that is attached to an
ObjectContext cannot be added to an
EntityCollection or EntityReference
that is not associated with a source
object.
Anyone know about this error?
It sounds like x is the result of an ObjectContext query. Each ObjectContext tracks the entities it reads from the database to enable update scenarios. It tracks the entities to know when (or if) they are modified, and which properties are modified.
The terminology is that the entities are attached to the ObjectContext. In your case, the entities in x are still attached to the ObjectContext that materialized them, so you can't add them to another EntityCollection at the same time.
You may be able to do that if you first Detach them, but if you do that, the first ObjectContext stops tracking them. If you never want to update those items again, it's not a problem, but if you later need to update them, you will have to Attach them again.
Basically all entity objects are controlled by an object context which serves as change tracker. The idea here is that the entities themselves are dumb to their environment, but the object context knows what's going on.
This is an inversion of the DataSet model where the tables track their own changes.
So objects are added to an object context and its entity collections directly. Here you've created an EntityCollection that's not associated with an object context and therefore can't have other objects added to them. They must first be attached to the object context.
Really what you probably want is to return IQueryable instead of IList. That would allow you to execute queries against the results of methodcall().
I'm returning a List (own class) from Silverlight to a service, in this list there are old entities with updated values and completely new entities, how do I save this back to the database using the entity framework?
Using cEnts.CardItems.AddObject gives me duplicates obviously.
You want to use Attach() instead of AddObject().
Attach will take your disconnect object and let the container know to consider it for updates. The new objects, without a PrimaryKey, will be added.
If you are using the same entity context for selecting and update/insert you have to call AddTo...() method to insert the new entities and ApplyPropertyChanges to the changed ones.
If you are using different contexts the problem is more complicated because you have to detach entities from one context and attach them to another. Once detached entities lose their changed state and you have to explicitly specify which properties have been changed (For more info check this: http://www.abadjimarinov.net/blog/2009/12/13/AttachAlreadyChangedObjectToADataContextInEntityFramework.xhtml ).