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"
Related
Context
I am using Entity Framework Core, SQLite and its spellfix1 extension.
There are two classes: MetaMovie and FuzzyMetaMovie
FuzzyMetaMovie is a subclass of MetaMovie
Description
At first, I get an IQueryable<MetaMovie> correspondingMovies using db.MetaMovies.Where(...). If no result, then I use db.FuzzyMetaMovies.FromRawSql(...) that is assigned to the same variable of type IQueryable<MetaMovie>. To finish, I call List<MetaMovie> tempList = await correspondingMovies.ToListAsync();.
Issue
The issue is coming from that last line. I am sometimes, rarelly, getting an InvalidCastException: Unable to cast object of type 'Castle.Proxies.MetaMovieProxy' to type 'com.cyberinternauts.all.MediaRecognizer.Models.Metas.FuzzyMetaMovie'.
I, then, tried calling the code with one of those specifically failing. No issue!! Any of the ones failing tried alone aren't creating the issue. So, I mean here it goes exactly through the same code path and the exception is not thrown.
I sincerely have no idea why is this happening. I know, you have no working code here. But, I am not able to circumvent the issue, thus no easy way to reproduce it.
Any idea?
Edit #1
I wrote a patch solution that does the trick (see my solution below). Though, I will explain how to reproduce it:
Context
Having two classes one is the child of the other: A being the parent of B.
The classes share the same table with a Discriminator column.
AsNoTracking() is not used.
Steps
Make a query that loads an object A with Id == 1
Make another query that loads an object B with Id ==1
Underlying reason of the crash
It is caused by EntityFramework that tries to load an entity of type B that has already been cached as type A. Thus, the downcasting failure from A to B.
The issue was coming from a previous call that loads at least one of the movies that is fetched by the raw query.
To fix it, I used (so no cache is used):
List<MetaMovie> tempList = await correspondingMovies.AsNoTracking().ToListAsync();
I opened an issue in GitHub for the efcore project: https://github.com/dotnet/efcore/issues/27802
We have a Repository class. While this class has some custom behavior, it wraps a standard ObjectContext object and so it's primarily standard Entity Framework.
Currently, we have the following code block, which works but performs horribly. (target is a CostingLineItem. The code changes the AddedByEmployee reference for this CostingLineItem.)
if (target.AddedByEmployee != null)
target.AddedByEmployee.CostingLineItems.Remove(target);
byEmployee.CostingLineItems.Add(target);
target.AddedByEmployee = byEmployee;
Because we have lazy loading enabled, this bit of code loads both the target.AddedByEmployee.CostingLineItems and byEmployee.CostingLineItems collections, which could be many thousands of rows.
Unfortunately, I can't change the lazy loading setting for this code. I need to find a way to make it more efficient, but nothing seems to work for me.
Here's what I've tried so far.
Method 1:
target.AddedByEmployeeId = byEmployee.Id;
Has no effect. The AddedByEmployeeId column still contains the original value. Apparently, the original employee reference is still there and takes priority.
Method 2:
target.AddedByEmployee = byEmployee;
Throws the following exception. Again, seems the original employee reference is still there.
Multiplicity constraint violated. The role 'Employees' of the relationship 'Leo.Domain.FK_CostingLineItem_Employees' has multiplicity 1 or 0..1.
Method 3:
Repository.Detach(target.AddedByEmployee);
target.AddedByEmployee = byEmployee;
Detach() (which in turn calls ObjectContext.Detach()) throws the following exception:
The object cannot be detached because it is not attached to the ObjectStateManager.
Method 4:
target.AddedByEmployee.CostingLineItems.Remove(target);
target.AddedByEmployee = byEmployee;
This works! But it's still loading target.AddedByEmployee.CostingLineItems, which I would like to avoid.
I realize I have not shown all of our code here (that's just not possible). But the code is using a standard ObjectContext underneath. What I would really like is someone who has some insights into Entity Framework and can offer some ideas for what else I can try, or what else I can check.
Note: We are currently running Visual Studio 2012. I will see if we can update to 2015. I would love to know if anything has changes since the version we are using that could make some of the methods above work where they didn't before.
Have you tried removing the target directly from the DbSet<> rather that removing it from the attached CostingLineItems.
context.Set<CostingLineItems>().Remove(target)
This should remove the target without loading the collection.
After some extensive research and testing, I think I determined what my issues were.
The problem was that the entity in question had just been cloned in the database. The cloning code follows a bunch of rules that dictate if related items are also cloned or only the references are cloned.
Either way, the AddedByEmployee had already been set. And so trying to set it a second time, or simply trying to set the ID has problems because of the conflict with what was already set.
I have a repository in EF that needs to Update detached entities but ones which exist in the ObjectStateManager. Imagine you have a session open on a DBContext where you have loaded a particular entity, but then you receive a request to update from a detached version of the object. The only way I have found to do this is to GET the existing object in the state manager, then one by one, update the fields to the fields of the passed-in object. Then set the state of the object state manager version to modified, and save the context.
This works for simple entities that don't contain navigation properties.
I am now trying to do so on an entity that has a many-to-many relationship.
Imagine you have a BlogPost object, and a Hashtag object. This is a many to many relationship. I have defined this and in the database I can see I have three tables, the BlogPost, the HashTag and the mapping table.
What I want to be able to do is edit the blog post on the front end, pass in the updated blog post with it's new list of hashtags that apply to it, and update the database.
The problem is the list of hashtags could be completely unrelated to the old one, so I first have to clear out all the previous mappings, then add the new ones in. If they are the same, this will be a necessary redundancy but the only way to achieve it.
I cannot figure out how to clear out the previous mappings in the many to many relationship though. I have tried
foreach (var tag in dbBlogPost.Hashtags)
dbItem.Hashtags.Remove(tag);
I then add the new hashtags to the empty collection, then do
Work.Context.Entry(dbItem).State = EntityState.Modified;
Work.Context.Save();
But when I save the repository, I get the following exception
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.
Can anyone suggest what I am doing wrong?
The DbSet<TEntity>.Local represents a local view of all Added, Unchanged, and Modified entities in this set. So you can clear all the collection before make any change.
dbBlogPost.Hashtags.Local.Clear();
We're implementing Entity Framework inside a winforms application using DbContext/Code First and have the following question regarding the proper way to check/handle when an entity has been deleted/updated in another context.
For example, we have some auxiliary table data (e.g. StateCodes) and the user could go in another and add/remove states as needed. This auxiliary editor form utilizes it's own DbContext and saves the changes once the user exits the form. Upon returning to the main form, the main context is unaware of the changes made to the database so we'd like to reload the DbSet for the entity. Unfortunately, it appears that if we remove the "MI" state code it still exists in the Local property of the DbSet with an EntityState of unchanged even after we call "Load" to bring in everything.
Outside of completely disposing of the main context would the following be the best way to check and see if what entities have been removed from the database?
foreach (State state in db.States.Local)
{
DbEntityEntry entry = db.Entry(state);
DbPropertyValues databaseValues = entry.GetDatabaseValues();
if (databaseValues == null)
{
db.States.Remove(state);
}
else
{
entry.OriginalValues.SetValues(databaseValues);
}
}
Thank you for your help
You shouldn't keep the context live past its unit of work. The context should only survive as long as its needed, otherwise you're bound to run in to caching pitfalls like you're observing. (Also, the context really isn't that heavy where instantiating it when you need it is overly time-consuming/resource intensive).
If you really must keep it alive, you may want to look in to passing the context to the auxiliary form.
Mirrored from my comment, figured it's best served as an answer
First, what Brad said. Only keep the context alive for the specific unit of work and dispose it. Not doing this will lead to nothing but headaches.
You can also check the entity's state by using the ObjectStateManager and pass in the object or entity key. You can also use the
public void Refresh(
RefreshMode refreshMode,
IEnumerable collection
)
method off of the Context. Also, you can check the entry state.
http://msdn.microsoft.com/en-us/library/bb503718.aspx
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.