Entity.SaveChanges() also saves removed object of a BindingList? - c#

I hope you can help me out, I've being scratching my head the whole night trying to figure out where this bug persist.
I'm writing an invoicing application in winform.
I have a grid on the form with its data source set to a BindingList object.
Let's just it's along the lines of:
BindingList<InvoiceLine> MyInvoiceLines = new BindingList<InvoiceLine> { };
Invoice MyInvoice = new Invoice();
Both InvoiceLine and Invoice are entity objects in my model.
I add lines to the grid via:
MyInvoiceLines.Add(new InvoiceLine());
I remove lines from the grid via:
MyInvoiceLines.Remove(LineToBeRemoved);
Where LineToBeRemoved is a property that gets the selected line when use wants to remove the line etc...
So eventually I want to save the invoice, so I do this...
foreach(var line in MyInvoiceLines)
{
MyInvoice.InvoiceLines.Add(line);
}
and then calls SaveChange(). However the lines that was removed from InvoiceLines BindingList are also saved... I've being scratching my head trying to work this out as NO WHERE in my code from start to finish does the InvoiceLines collection gets referenced or was connected with the data context object before this method which eventually action the save.
This is a simplified version of my code but I can't help thinking I must got some thing conceptually wrong either with the BindingList or with the data context object. It really isn't obvious for me as I'm a noob.
Any help would be appreciated, not after a fix, maybe some tools or method where I can further diagnose this problem...
Update: detaching the item before adding to the BindingList seemingly fixed it but deleting the object from entity has strange behaviours :/ thanks everyone.

You could try deleting the object explictly. i.e.
foreach(var object in deletedObjectCollection)
{
_currentContext.DeleteObject(order);
}
rather than rely on it's absence in a collection to activate a delete. In my experience (with EF4) that doesn't work. I use lazy loading and the absence of an object in the collection could be because it hasn't been loaded so it doesn't feel right to rely on it's absence. There is probably (almost certainly) more elegant ways to do this but it is currently working for me.
Generally I've had to do a lot more explicitly with EF than I though i would.

The entity that has been removed from the BindingList, has at also bee detached from the DBContext?
If the entity is still attached to the context it will still be tracked and therefore changes will be saved.

I think you have to set one dirty flag for unchanged record and then check it in Entity.SaveChages() event.
May be this help to you...

You do this multiple times? You may have added all the InvoiceLine items to your Invoice, then removed some from the BindingList (not your actual entity!), and then re-add them to your entity.
I suppose (but to be honest I'm a little unsure about this point) as the primary keys match, duplicates aren't saved twice. However, the items that are supposed to be removed are still there.

If your Invoice object is an Entity object it is context-aware and will be tracked by the context. Calling SaveChanges() will save all changes for all Entity objects unless they are detached.
Keep in mind that if you relate these Entity objects to an object graph and attach any node of the object graph to the context, the entire graph will be attched. So if you create a new entity object, like an InvoiceLine, and you relate this new InvoiceLine to an object graph:
MyInvoiceLines.Add(new InvoiceLine());
the entire graph should be tracked by the context at this point.

Related

Not able to remove items from database when user deletes a row in data grid

When a user hits the button, I'm executing the following code.
using (Context context = new Context())
{
foreach (Thing thing ViewModel.Things)
context.Things.AddOrUpdate(thing);
context.SaveChanges();
}
The updates are executed except for when the user selected a row and hit delete button. Visually, that post is gone but it's not really being removed from the database because it's not in the view model anymore. Hence, the loppification only ticks for the remaining things and not touching the removees.
I can think of two ways to handle that. One really bad - to remove everything from the context, save it and then recreate based on the view model. It's an idiotic solution so I'm only mentioning it for the reference's sake.
The other is to store each removed post in an array. Then, when the user invokes the code above, I could additionally perform the deletion of the elements in that array. This solution requires me to build the logic for that and I'm having this sense that it should be done automagically for me, if I ask nicely.
Am I right in my expectation and if so, how should I do it? If not, is there a smarter way to achieve my goal than creating this kill squad array?
At the moment, I do a double loop, first adding and updating what's left in the data grid. Then, removing anything that isn't found there. It's going to be painful if the number of elements grows. Also, for some reason I couldn't use Where because I need to rely on Contains and EF didn't let me do that. Not sure why.
using (Context context = new Context())
{
foreach (Thing thing in ViewModel.Things)
context.Things.AddOrUpdate(driver);
foreach (Thing thing in context.Things)
if (!ViewModel.Things.Contains(thing))
context.Things.Remove(thing);
context.SaveChanges();
}
The first thing I want to advice you is you should use the AddOrUpdate extension method only for seeding migrations. The job of AddOrUpdate is to ensure that you don’t create duplicates when you seed data during development.
The best way to achieve what you need you can find it in this link.
First in your ViewModel class you should have an ObservableCollection property of type Thing:
public ObservableCollection<Thing> Things {get;set;}
Then in the ViewModel's constructor (or in another place), you should set the Things property this way:
context.Things.Load();
Things = context.Things.Local;
From the quoted link:
Load is a new extension method on IQueryable that will cause the
results of the query to be iterated, in EF this equates to
materializing the results as objects and adding them to the DbContext
in the Unchanged state
The Local property will give you an ObservableCollection<TEntity> that
contains all Unchanged, Modified and Added objects that are currently
tracked by the DbContext for the given DbSet. As new objects enter the
DbSet (through queries, DbSet.Add/Attach, etc.) they will appear in
the ObservableCollection. When an object is deleted from the DbSet it
will also be removed from the ObservableCollection. Adding or Removing
from the ObservableCollection will also perform the corresponding
Add/Remove on the DbSet. Because WPF natively supports binding to an
ObservableCollection there is no additional code required to have two
way data binding with full support for WPF sorting, filtering etc.
Now to save changes, the only you need to do is create a command in your ViewModel class that call SaveThingsChanges method:
private void SaveThingsChanges()
{
context.SaveChanges();
}

How should a one to many relationship be managed in Entity Framework?

I am having trouble understanding at a fundamental level how a one to many relationship should be managed in Entity Framework. In my application I have two tables, DISPLAY_MASTER, and DISPLAY_ITEMS. Their relationship is like so:
DISPLAY_MASTER.DISPLAY_ID 1----->* DISPLAY_ITEMS.DISPLAY_ID
Entity Framework organizes this really intuitively. I am left with a strongly typed DISPLAY_MASTER object that has an ICollection property called DISPLAY_ITEMS.
My confusion lies within how to save the DISPLAY_ITEMS collection back to the database. In my application I am reading in all of the DISPLAY_ITEMS for the particular DISPLAY_MASTER using LINQ into a List<DISPLAY_ITEMS> object called _displayItems. This is then bound to a DataGrid for editing using MVVM. The user can edit existing DISPLAY_ITEMS, delete existing DISPLAY_ITEMS, or add new DISPLAY_ITEMS using the DataGrid. My binding works perfectly and these changes are reflected in _displayItems. Once it comes time to save is where I stop feeling confident in my code. When the user clicks save I am setting the DISPLAY_MASTER's ICollection like so:
_displayMaster.DISPLAY_ITEMS = _displayItems;
Is this the proper way to be working on an Entity Framework collection? Or should I be binding the DataGrid directly to the _displayMaster.DISPLAY_ITEMS object? Or some other method? The reason I am not confident is because if I try to validate the _displayMaster.DISPLAY_ITEMS entity using:
DbEntityValidationResult validationResults = _context.Entry(_displayMaster.DISPLAY_ITEMS).GetValidationResult();
I get an error saying 'List1' is not part of the collection, which obviously doesn't seem right.
Any advice or guidance would be appreciated.
Thanks.
It depends.
If you disconnect the entities from their database context when you bind them to the grid (i.e. if you dispose the context after loading the entities and create a new context when it comes to save the changes) then it's not so easy. You will have to load the master including the old items from the database, merge the changes into that collection based on your new edited collection from the grid and then save the changes. An example how to do that is here.
If you keep the entities attached to the context you have loaded them into while the user is editing it's much easier if you just directly bind _displayMaster.DISPLAY_ITEMS to the grid because EF is then able to track all the changes you are performing on the collection and update the object graph automatically to the database when you call SaveChanges.
Since you tagged the question with WPF you might have the second option (depending on your application's architecture). In web applications for example the second option doesn't exist at all because all editing happens in a browser which is disconnected of course from the context.

Getting an EntityCollection to talk to the database

I believe I have found what I want in terms of a main class for operations in Entity, but do not know how exactly to wire it up to the database, and MSDN is precious short on documentation (Google is even less useful).
Again, pardon my ignorance, as I am new to this, and only recently coming up to speed, but how would I get this to connect or reflect this change with the database? I am just not seeing a path between this, and say DbContext. Do I craft a query by hand? Is there some special class I am missing that provides some functionality I've been missing over the last three days that I've been trying to coax this framework into doing just anything minimally useful? Thanks to anyone who accepts this arduous task.
EntityCollection<T> newCollection = new EntityCollection<T>();
newCollection.Add(objectValue);
I mean, is EntityCollection just a List / Array for Entities? Or does it have some back-end hooks to the database? Can it perform updates / deletes when its methods are called, and are these magical in nature, or is there some prep work that makes the magic work?
As I understand, this class behaves just like the List<T> class - i.e. it's an in-memory collection.
Look at this answer: ObjectSet.AddObject() vs. EntityCollection.Add()
But if you'll look at the MSDN documentation about its Add method so you'll see that it could be related to the ObjectContext and eventually to the data-source(Database):
The Add method adds an object to an EntityCollection and
creates a relationship between the two objects. When the source object
is attached to an ObjectContext instance, the Add method also adds the
object to the ObjectContext. This operation is translated into an
insert operation in the data source when SaveChanges is called. For
more information, see Adding, Modifying, and Deleting Objects (Entity
Framework).

How do I get Entity Framework 5 to update stale data

I have an EF5 WPF/MVVM solution that's working without problems. The project is an order entry system but loading an order loads lots of related items so the context is used to track all the changes and then save it off, so the context is long lived. If user A loads an order and doesn't do anything with it, and then User B loads that order and updates it I have a refresh button that was intended to let User A update the stale data. Unfortunately, I can't seem to get EF5 to ignore the cache. I originally thought this would work:
_trackingContext.GetObjectContext().Refresh(RefreshMode.StoreWins, theOrders);
List<OrderLineItem> line_items = theOrders.SelectMany(x => x.OrderLineItems).ToList();
_trackingContext.GetObjectContext().Refresh(RefreshMode.StoreWins, line_items);
Where GetObjectContext() is just a wrapper
public ObjectContext GetObjectContext()
{
return (this as IObjectContextAdapter).ObjectContext;
}
Turns out this doesn't update the data. So I thought maybe I had to change the Merge option so I added
var set = _trackingContext.GetObjectContext().CreateObjectSet<OrderLineItem>();
set.MergeOption = MergeOption.OverwriteChanges;
and I also tried it for Orders (and with the PreserveChanges option) but nothing worked. Eventually, I just resorted to disposing and recreating the context and then recreating the search selection but it seems like this should be overkill. Is there some easier way to just get EF5 to update any stale data with fresh data from the database?
Update
OK - It turns out it was a testing methodology problem. After seeing #jure's reply and implementing it, and having it appear to not work I finally got smart. I broke out SQL Profiler. The right things were happening behind the scenes but I wasn't doing the right thing to update the view. Once I did that my original code worked.
There's a Reload method in the DbEntityEntry class, so you could do:
dbContext.Entry(entity).Reload();
but that is only for one object in the context that you need to refresh from Db.
Disposing and recreating the context is the way to go.

EF4 Relationships Not Updating Correctly

Ok, I'm working on a project and was just handed a bug that I'm having a bit of trouble with. The code is written in a "different" manner and I think the way the original developers approached this project set it up for some problems, one of which I'm dealing with today. Basically, we have something like this:
Review_Comment comment = commentContext.Review_Comment.First(c => c.CommentID == commentID);
commentContext.DeleteObject(comment);
commentContext.SaveChanges();
review.Review_Comment.Clear();
review.Review_Comment.Load(System.Data.Objects.MergeOption.OverwriteChanges);
context.SaveChanges();
Let me explain a few things and then I'll explain the problem:
"review" is an instance of the Review class, which is the parent of a set of "Review_Comments" (i.e. Review_Comments belong to a single Review).
The function above is to delete a comment.
The comments, for better or worse, use their own EF4 context (separate from the context that the "review" variable is attached to. This is important.
What the original developer tried to do, I think was load the comment in a separate context, delete it, then update the EntityCollection of Review_Comments in the separate "Review" class manually.
However, when context.SaveChanges() is called, we get the following error:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
I've seen this error described when people are trying to delete say, an Order object and the related OrderItems are not deleted correctly but this case is different. We're trying to delete a single child object and then update the EntityCollection on another entity using a separate context.
Hope that all makes sense, let me know if I can help clarify anything. Any thoughts?
EDIT: I should mention I was able to get this problem to go away by using the same context that the rest of the page uses. However, in this case, because of several dependencies previous developers have introduced, I have to keep the second context or else I have to rewrite a ton of code to remove the dependencies on the second context just to fix this bug. I'm hoping to find a solution that doesn't involve that much time. The goal is to delete the comment, then to reload a separate entity's Review_Comment EntityCollection and be able to call SaveChanges() without this error.
Your problem is that the .Clear() causes the second context to disassociate the Review_Comments from its Review, it never realizes that the Review_Comment was actually deleted.
You should do this instead
context.Refresh(RefreshMode.StoreWins, review.Review_Comment );
context.SaveChanges();
If you watch the entity state of the comment in "review.Review_Comment" you should see that after the Refresh, its state becomes "Detached" rather than "Modified"

Categories

Resources