I am using EF 6.1 to track changes to all objects inheriting from the abstract base class HistoryData and keep each of its Versions in a collection of a parent object.
They are hooked up like this:
modelBuilder.Entity<Foo>()
.HasMany(f => f.Versions)
.WithRequired(fv => fv.Parent)
.HasForeignKey(fv => fv.ParentID);
Instead of modifying an entry, I hook into the DbContext and do this:
public override async Task<int> SaveChangesAsync()
{
foreach (var entry in ChangeTracker.Entries<HistoryData>().Where(e => e.State == EntityState.Modified))
{
InsertInsteadOfUpdate(entry);
}
return await base.SaveChangesAsync();
}
private void InsertInsteadOfUpdate(DbEntityEntry<HistoryData> entry)
{
// ValidFrom is not set manually -> Set it automatically to now
if (entry.OriginalValues["ValidFrom"].Equals(entry.CurrentValues["ValidFrom"]))
{
entry.Entity.ValidFrom = DateTime.Now;
}
// Insert instead of Update
entry.State = EntityState.Added;
}
This works fine so far and the entity is inserted instead of updated.
However, the new HistoryData entity is not loaded automatically when I call Foo.Versions.
So now I do something like this at the end of InsertInsteadOfUpdate:
// Mark collection of parent as not loaded so that it gets lazy loaded on the next call
var parent = entry.Reference("Parent").CurrentValue;
Entry(parent).Collection("Versions").Load();
That works, but it reloads the Collection instantly. If I insert several HistoryData objects before querying Versions, this is a lot of unnecessary work.
All I really want to do is mark the Collection() of the parent to be reloaded the next time I query it. Is there some way to do this?
I tried
Entry(parent).Collection("Versions").IsLoaded = false;
but for some reason that did not cause the Collection to be lazy loaded on the next call.
Related
I know there is a Load method.
_dbContext.Entry(blog).Collection(b => b.Posts).Load()
But I'm try to handle concurrency conflicts, I've been add a post into blog.Posts. if call Load, it do not clear the blog.Posts, just append the existing Posts to it.
I had try:
blog.Posts = null;
_dbContext.Entry(blog).Collection(b => b.Posts).Load()
But blog.Posts become a empty collection (Zero Count).
So I want a Reload.
Unfortunately although EntityEntry has Reload method, there is no such method for ReferenceEntry and CollectionEntry (or in general, for NavigationEntry which the base of the previous two). And Reload methods refreshes just the primitive properties, so it can't be used to refresh the navigation properties.
Fortunately it's not that hard to create a custom one. It needs to detach (or reload?) all the current collection items, set IsLoaded to false and CurrentValue to null before calling Load.
Something like this (put it in a static class of your choice and add the necessary usings):
public static void Reload(this CollectionEntry source)
{
if (source.CurrentValue != null)
{
foreach (var item in source.CurrentValue)
source.EntityEntry.Context.Entry(item).State = EntityState.Detached;
source.CurrentValue = null;
}
source.IsLoaded = false;
source.Load();
}
so you can use the desired
_dbContext.Entry(blog).Collection(b => b.Posts).Reload();
I just noticed that I can't refresh the values fetched from the DB. Storage (i.e. from the client to the DB) works as supposed to. Loading the first time works as charm as well.
However, if someone deletes a row in the DB (say, using SQL Management Studio), that change isn't in effect in my client until I reinstantiate the whole view model. Only calling Refresh() doesn't fetch the change. The same goes for altering the values of loaded in records.
However, additions to the table are brought in...
I (re)load the values shown in the view from the DB by calling the following method in the view model.
public ViewModel()
{
Reload();
...
}
public void Reload()
{
_data.Set<Order>().Load();
_data.Set<TimeFrame>().Load();
Orders = _data.Set<Order>().Local;
TimeFrames = _data.Set<TimeFrame>().Local;
...
}
readonly Data _data;
private ObservableCollection<Order> _orders;
private ObservableCollection<TimeFrame> _timeFrames;
public ObservableCollection<Order> Orders
{
get { return _orders; }
set { _orders = value; OnPropertyChanged("Orders"); }
}
public ObservableCollection<TimeFrame> TimeFrames
{
get { return _timeFrames; }
set { _timeFrames = value; OnPropertyChanged("TimeFrames"); }
}
What am I missing?
Here the problem is that you load your values, then you use the local property.
The Local collection contains all the values that have been loaded in your context.
if the first time, you load values (1,2,3,4) your Local collection will contain values (1,2,3,4). The second time you load it, you will perhaps load values (1, 4, 5) your Local collection will contain values (1,2,3,4,5)
you should do something like
Orders = new ObservableCollection(_data.Set<Order>());
if it is a read only scenario and you don't need to update and save your data, you should even load your data AsNoTracking, so you won't have caching issues and you will have less EF overhead as your entities won't be tracked
Orders = new ObservableCollection(_data.Set<Order>().AsNoTracking());
I've created a code first context with a DbSet property
I work with Windows form. If I bind as follow:
_context.Schedules.Load();
scheduleBindingSource.DataSource = _context.Schedules.Local.ToBindingList();
All works great and when I save as follow:
this.Validate();
scheduleBindingSource.EndEdit();
_context.SaveChanges();
The data persists; But when I bind the data like this:
var res = _context.Schedules.Where(k => k.EmployeeName.Equals(employeeComboBox.Text)).ToList();
scheduleBindingSource.DataSource = res;
When I save data doesn't persis!
I'm thinking that the ToList() method is not good, but I can't find alternative to get a BindingList connected to the Local set of data inside the context.
Thanks,
Andrea
You can try this:
_context.Schedules.Where(k => k.EmployeeName.Equals(employeeComboBox.Text)).Load();
scheduleBindingSource.DataSource = _context.Schedules.Local.ToBindingList();
That should only bring the schedules that meet the condition. When you call the Load method after the Where method, it is going to bring to memory only the records that meet the condition. Later, when you call the Local property,it will give you an ObservableCollection<Schedule> that contains all the objects that are currently tracked by the DbContext which thy are going to be the elements you loaded before. At the end, when you call the ToBindingList extension method, it will returns an BindingList<Schedule> that stays in sync with the given ObservableCollection<Schedules>.
The reason that caused the non-persistance of the data is caused by DataGridView (or the BindingSource), that don't add to context the new istance of the just added row.
So I've disabled the AllowUserToAddRow property (now I'm using BindingNavigator Add Button)
And implemented these two events as follow:
private void scheduleBindingSource_AddingNew(object sender, AddingNewEventArgs e)
{
_scheduleAdding = true;
}
private void scheduleBindingSource_CurrentChanged(object sender, EventArgs e)
{
if (_scheduleAdding)
{
Schedule s = (Schedule)scheduleBindingSource.Current;
s.EmployeeName = employeeComboBox.Text;
s.From = new DateTime(dateTimePicker1.Value.Year, dateTimePicker1.Value.Month, 1);
_context.Schedules.Add(s);
_scheduleAdding = false;
}
}
I've just read this question:
How to cancel an edit to an object using MVVM?
I have the exact same question and would like to have a simple solution. The first one looked very promising, however, I'm using entity framework and my classes are automatically generated, so that's not an option.
How can I do this easily with EF?
EDIT:
My ViewModel:
public List<Player> Players
{
get { return repository.Players.OrderBy(x => x.Firstname).ToList(); }
}
public Player CurrentPlayer
{
get { return currentPlayer; }
set
{
if (currentPlayer != value)
{
currentPlayer = value;
RaisePropertyChanged("CurrentPlayer");
}
}
}
Players is bound to a datagrid, CurrentPlayer to the selecteditem of that. Below the datagrid, I have textboxes where the user can edit the player info.
When the user presses the save button, this code is executed:
private void SaveExecute(object parameter)
{
repository.SavePlayer(currentPlayer);
Editing = false;
}
Very easy. When the user presses the cancel button, this is executed:
private void CancelExecute(object parameter)
{
if (currentPlayer.Id == 0) // id = 0 when a new player is being added
{
CurrentPlayer = null;
}
else
{
// here, the CurrentPlayer should be set back to it's previous state.
}
Editing = false;
}
CurrentPlayer is an object of Player, which is an entity class generated by EF.
I don't understand the problem. If the user is editing a new item (State == ObjectState.Added) then you discard that, (and maybe set the CurrentPlayer to what it was before pressing the "New" Button?), else just retrieve the entity from the database again and that's it...
A better way to solve this problem is to have your CRUD and your List VMs have separate instances of the entity.
For example, when I create a List view (Datagrid or otherwise), usually the data displayed in that is just a subset of the whole data displayed in the full CRUD View. So, in order to show the entity in the CRUD, I need to Get() the entity again using the necessary Includes. This resolves the whole cancel problem, because the entity instance you're modifying is actually not the same as the one shown in the List view. If the user presses Save, you can replace the instance shown in the list view with the edited one, and if the user presses cancel, don't do anything.
Edit: Also be aware that if your entities are being generated by a T4 Template such as the Entity Framework STE Template, you can modify the .tt file and customize it to generate whatever code you need in your entities.
I have a parent entity (Treatment) with a collection of child entities (Segments). I have a save method that take a treatment, determines if it's new or existing, and then either adds it to the objectContext or attaches it to the object context based on whether it is new or existing.
It does the same thing with the children within the main entity. It iterates over the collection of child entities, and then adds or updates as appropriate.
What I'm trying to get it to do, is to delete any child objects that are missing. The problem is, when I'm updating the parent object and then I attach it to the object context, the parent object then has a collection of child objects from the DB. Not the collection I originally passed in. So if I had a Treatment with 3 segments, and I remove one segment from the collection, and then pass the Treatment into my save method, as soon as the Treatment object is attached to the objectcontext, the number of segments it has is changed from 2 to 3.
What am I doing wrong?
Here is the code of my save method:
public bool Save(Treatment myTreatment, modelEntities myObjectContext)
{
bool result = false;
if (myObjectContext != null)
{
if (myTreatment.Treatment_ID == 0)
{
myObjectContext.Treatments.AddObject(myTreatment);
}
else
{
if (myTreatment.EntityState == System.Data.EntityState.Detached)
{
myObjectContext.Treatments.Attach(myTreatment);
}
myObjectContext.ObjectStateManager.ChangeObjectState(myTreatment, System.Data.EntityState.Modified);
myObjectContext.Treatments.ApplyCurrentValues(myTreatment);
}
foreach (Segment mySegment in myTreatment.Segments)
{
if (mySegment.SegmentID == 0)
{
myObjectContext.ObjectStateManager.ChangeObjectState(mySegment, System.Data.EntityState.Added);
myObjectContext.Segments.AddObject(mySegment);
}
else
{
if (mySegment.EntityState == System.Data.EntityState.Detached)
{
myObjectContext.Segments.Attach(mySegment);
}
myObjectContext.ObjectStateManager.ChangeObjectState(mySegment, System.Data.EntityState.Modified);
myObjectContext.Segments.ApplyCurrentValues(mySegment);
}
}
}
result = (myObjectContext.SaveChanges(SaveOptions.None) != 0);
return result;
}
*EDIT****
Based on some of the feedback below, I have modified the "Save" method. The new method implementation is below. However, it still does not delete Segments that have been removed from the myTreatments.Segments collection.
public bool Save(Treatment myTreatment, tamcEntities myObjectContext)
{
bool result = false;
if (myObjectContext != null)
{
if (myTreatment.Treatment_ID == 0)
{
myObjectContext.Treatments.AddObject(myTreatment);
}
else
{
if (myTreatment.EntityState == System.Data.EntityState.Detached)
{
myObjectContext.Treatments.Attach(myTreatment);
}
myObjectContext.ObjectStateManager.ChangeObjectState(myTreatment, System.Data.EntityState.Modified);
}
foreach (Segment mySegment in myTreatment.Segments)
{
if (mySegment.SegmentID == 0)
{
myObjectContext.ObjectStateManager.ChangeObjectState(mySegment, System.Data.EntityState.Added);
}
else
{
myObjectContext.ObjectStateManager.ChangeObjectState(mySegment, System.Data.EntityState.Modified);
}
}
}
result = (myObjectContext.SaveChanges(SaveOptions.None) != 0);
return result;
}
FINAL EDIT
I have finally got it to work. Here is the updated Save method that is working properly. I had to save the initial list of Segments in a local variable and then compare it to the myTreatments.Segments list after it was attached to the DB, to determine a list of Segments to be deleted, and then iterate over that list and delete matching Segments from the newly attached myTreatment.Segments list. I also removed the passing in of the objectcontext per advice from several responders below.
public bool Save(Treatment myTreatment)
{
bool result = false;
List<Segment> myTreatmentSegments = myTreatment.Segments.ToList<Segment>();
using (tamcEntities myObjectContext = new tamcEntities())
{
if (myTreatment.Treatment_ID == 0)
{
myObjectContext.Treatments.AddObject(myTreatment);
}
else
{
if (myTreatment.EntityState == System.Data.EntityState.Detached)
{
myObjectContext.Treatments.Attach(myTreatment);
}
myObjectContext.ObjectStateManager.ChangeObjectState(myTreatment, System.Data.EntityState.Modified);
}
// Iterate over all the segments in myTreatment.Segments and update their EntityState to force
// them to update in the DB.
foreach (Segment mySegment in myTreatment.Segments)
{
if (mySegment.SegmentID == 0)
{
myObjectContext.ObjectStateManager.ChangeObjectState(mySegment, System.Data.EntityState.Added);
}
else
{
myObjectContext.ObjectStateManager.ChangeObjectState(mySegment, System.Data.EntityState.Modified);
}
}
// Create list of "Deleted" segments
List<Segment> myDeletedSegments = new List<Segment>();
foreach (Segment mySegment in myTreatment.Segments)
{
if (!myTreatmentSegments.Contains(mySegment))
{
myDeletedSegments.Add(mySegment);
}
}
// Iterate over list of "Deleted" segments and delete the matching segment from myTreatment.Segments
foreach (Segment mySegment in myDeletedSegments)
{
myObjectContext.ObjectStateManager.ChangeObjectState(mySegment, System.Data.EntityState.Deleted);
}
result = (myObjectContext.SaveChanges(SaveOptions.None) != 0);
}
return result;
}
Maybe I am missing something, but to me this code looks overly cumbersome.
Please bear with me, if I am on the wrong track here and misunderstood you.
Regarding the objects that should be deleted, I suggest that you store these in a separate collection that only holds the deleted items. You can the delete them from the ObjectContext.
Instead of calling ApplyCurrentValues I would, I would simply call myObjectContext.SaveChanges(). ApplyCurrentValues has, in this case, the downside that it does not take care of any other entity that has relations to the one you are saving.
MSDN documentation:
Copies the scalar values from the supplied object into the object in
the ObjectContext that has the same key.
As the other Segments are already attached to your Treatment, by using SaveChanges(), they will be added to the context automatically or updated if they were already added.
This should make all that manual handling of the EntityStates unnecessary.
EDIT:
Now I see where this is going...
Somewhere in you you code - outside of this Save() method - you are deleting the Segment instances. The trouble lies in the problem that your ObjectContext is totally unaware of this. And how should it be...?
You may have destroyed the instance of a certain Segment entity, but as the entities are detached, this means that they have no connection to the ObjectContext. Therefore the context has absolutely no idea of what you have done.
As a consequence, when you attach the treament to it, the context still believes that all Segments are alive because it does not know about the deletion and adds them again to the Treatment like nothing ever happened.
Solution:
Like I already said above, you need to keep track of your deleted entities.
In those spots, where you delete the Segments, do not actually delete them, but:
Remove() it from the Treatment instance.
Move the "deleted" Segment into a collection, e.g. List<Segment>. Let's call it deletedSegments.
Pass the deletedSegments collection into the Save() method
Loop through this collection and ObjectContect.Delete() them.
Do the rest of the remaining save logic as necessary.
Also, like Tomas Voracek mentioned, it is preferable to use contexts more locally. Create it only within the save method instead of passing it as an argument.
Ok try again.
When you say "remove" do you mean mark as deleted.
You are calling ChangeObjectState to change the state to modified.
So if you send 3 down, one deleted, one modified and one unchanged; then all will get marked as modified before save changes is called.