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();
}
Related
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.
I have a control in WPF app that binds to this property
public ObservableCollection<Entity.Account> Accounts
{
get
{
_context.Accounts.Load();
return _context.Accounts.Local;
}
}
I expected that every time control reaches for data, local collection gets reloaded from scratch from database because of Load() method, but apparently I was wrong.
So I have two questions: what exactly Load() does if it's not loading entities from context to local? And how can i populate local collection by other means but Load()?
More details:
First here's how it's bound:<TextBox Text="{Binding Name}"/>
and in code-behind DataContext = _viewmodel.Accounts;
This Accounts property is exactly the one I wrote above. And the Name propery it binds to is a part of Account entity.
If you edit the account's name and won't call EntityContext.SaveChanges() it will change in local collection but won't change in database and calling Load() method won't refresh local collection. It only refreshes when program restarts (when context is created anew)
what exactly Load() does if it's not loading entities from context to local?
The Load is an extension method on IQueryable that enumerates the results of the query. This is equivalent to calling ToList without actually creating the list.
read here from MSDN
how can i populate local collection by other means but Load()?
Do you mean refresh? if yes then you can use the Reload
which will reload the entity from the database overwriting any property values with values from the database.
as:
foreach (var entity in _context.ChangeTracker.Entries())
{
entity.Reload();
}
Reload Method MSDN
I have a model called 'target' retreived by entityframework with a collection addresses.
After removing all items that are not in excesting in another collection i'm saving my entity framework context.
However, when checking my database the records are still there. While my linq code made sure to remove the items from the collection.
Here is my linq code:
using (IUnitOfWork uow = _uow.CreateUnitOfWork())
{
var target = _repository.GetByBron(uow, bron.BronId);
target.Adressen.RemoveAll(x => source.Adressen.All(y => !y.Equals(x)));
//Which calls Context.SaveChanges(); inside the unit of work class
uow.Save(_logger);
}
Update: The problem is not removing my record from the collection. Its when i'm calling the save on the context. The relation record in my database is still there... nothing has been deleted... aldo it was removed from the collection.
Solved
I'm directly removing it from the context now. (with a seperated repository object)
This is very dependent upon the configuration of the relationships. See Entity Framework .Remove() vs. .DeleteObject()
Since your relationship sounds to be a many to many, you will likely need to call DeleteObject for the addresses themselves, as EF won't automatically delete the orphaned records.
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 using entity framework code first and exposing the northwind database through a WCF REST HTTP interface.
I've not exposed the OrderDetails table (order items) as it doesn't make sense creating an order and then adding each required OrderDetail seperately through another service. To my mind it needs to be an atomic transaction that either succeeds or fails as one. Therefore I include the Order.OrderDetails collection when passing to the client and assume I'm going to get one when an order is created or updated.
The problem however seems to be detecting changes to the OrderDetails collection when reattaching the Order entity for an update. The order itself can be set as modified to update those properties but this doesn't cascade to the OrderDetail items. So I can manually go through and set updated ones to modified but the problem lies in figuring out which ones are updated in the first place. Setting a new OrderDetail to modified will cause an error when trying to save.
I read a recommendation to set the Id of new collection items to 0 and in the server use that to decide whether it's new or existing. Northwind however uses a composite key between OrderID and ProductID for OrderDetails. These will both have to be set by the client, so I can't find a way to detect whats new. Furthermore, a deleted OrderDetail won't exist in the detached graph and I will need to figure out what has been deleted and explicitly remove it.
Any advice would be much appreciated.
public override Order Update(Order entity)
{
dbset.Attach(entity);
DataContext.Entry(entity).State = EntityState.Modified;
foreach (var orderDetail in entity.OrderDetails)
{
DataContext.Entry(orderDetail).State = EntityState.Modified;
}
return entity;
}
I've recently been allowed to open source some work I did for my employer a while ago (with some changes of course). I actually wrote an extension method to solve this problem, you can get it at http://refactorthis.wordpress.com/2012/12/11/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a-graph-of-detached-entities/
Hope it helps!
This is common and complex issue and there is no magic which will do it for you. My solution (and the only one which works in all scenarios) was to load the Order again in your update method and manually merge changes:
public override Order Update(Order entity)
{
// No attach of entity
var attached = DataContext.Orders.Include(o => o.OrderDetails).SingleOrDefault(...);
if (attached == null) ...
// Merge changes from entity to attached - if you change any property
// it will be marked as modified automatically
foreach (var detail in attached.OrderDetails.ToList())
{
// ToList is necessary because you will remove details from the collection
// if detail exists in entity check if it must be updated and set its state
// if detail doesn't exists in entity remove if from collection - if it is \
// aggregation (detail cannot exists without Order) you must also delete it
// from context to ensure it will be deleted from the database
}
foreach (var detail in entity.OrderDetails)
{
// if it doesn't exists in attached create new detail instance,
// fill it from detail in entity and add it to attached entity -
//you must not use the same instance you got from the entity
}
DataContext.SaveChanges();
return entity;
}
There can be also need to manually check timestamps if you use them.
Alternative scenario is what you have described with 0 used for new details and negative ID for deleted details but that is logic which must be done on client. It also works only in some cases.