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();
Related
I am creating a new database using EF code-first which contain the following classes:
Address, Contact, Account, Customer (a sub-class of Account), and SalesOrder.
The Address class is the one giving me problems at the moment, it can have no foreign key because it can be linked to any of the other five classes (with more to come), and each of the other classes can have one or more navigation properties pointing back to it.
The navigation properties should look as follows:
Contact.AddressId?
Contact.Address
Account.AddressId?
Account.Address
Customer.DeliveryAddresses
SalesOrder.InvoiceAddressId
SalesOrder.InvoiceAddress
SalesOrder.DeliveryAddressId?
SalesOrder.DeliveryAddress
It should be possible for these classes to share the same Address record, e.g. an Account has an Address, this can also be linked to a SalesOrder, a different Address, linked to the Customer, could be linked to another SalesOrder. All Addresses linked to Accounts and Customers should be unique, but other classes should be able to share links to these Addresses.
I have tried setting it up with all the possible fluent configurations I can think of, with my DbContext having a DbSet property and without (ultimately I don't think it should have it's own DbSet property, as the Addresses should only be accessible from the various root objects, but if that's the only way to get it to work I'm happy to manage the inserts/deletes myself).
I tried making all the navigation properties nullable (ideally SalesOrder.InvoiceAddressId should not be nullable), and also had to remove the Customer.DeliveryAddresses Many-to-Many mapping at one point because that was confusing the issue.
I get various errors depending on how I have it set up, either Multiplicity conflicts due to non-nullable fields, or Cascade on Delete errors when I have no DbSet property and I try and let EF handle the inserts and deletes.
I also end up with unwanted null rows when I do have a DbSet property set. e.g:
add three Address records to the DbSet (Address(1), Address(2), Address(3),
add two Accounts to the DbSet (Account(1) & Account(2)),
add multiple SalesOrders,
set Account(1).AddressId = 1
set Account(2).AddressId = 2,
set SalesOrder(n).InvoiceAddressId = 1,
set SalesOrder(n).DeliveryAddressId = 3
This will correctly create the Address records, but the related keys will only be set correctly if the various Id foreign-key properties are used, rather than the navigation property, and even if the Id properties are used the foreign keys all look correct, but orphaned records for each SalesOrder (or two per order if both navigation properties are used) end up in my Address table with all their fields bar Id set to NULL.
The only thing I can think of that I haven't tried would be to create multiple sub-classes of Address and use each one with it's related class (e.g. SalesOrderDeliveryAddress), but that doesn't seem ideal. I'd rather not do that unless I have to.
Is what I'm looking for possible to set up in EF, or is there some other way to go about doing it?
Thanks,
David
There are several issues making this confusing. To start with I would switch off the default cascade on delete to get rid of multiple cascade paths and come back to that later.
Then read about adding disconnected trees, foreign keys and navigation properties here: http://msdn.microsoft.com/en-us/magazine/dn166926.aspx
Then I would set up the entities you way you want them and repost a more specific issue. (You have tried lots of stuff so it's hard to work out what happens when here)
Once you've got adding and updating working you can come back and work out where you can put in cascade delete and where it needs to be manual
I have master table and details table. For example master table is ObjectSet<'MasterObject> and details table is ObjectSet<'DetailObject>. So each MasterObject contains EntityCollection<'DetailObject>.
As I understand I can remove DetailObject from database using following:
EntityCollection<'DetailObject> ec = masterObject.DetailObjects;
// as navigation property
ec.Remove(deleting_detail_object); // deleting_detail_object will be removed and marked for deleting.
context.SaveChanges(); // I have exception
After Remove() the deleting_detail_object.MasterObject (navigation property) is null. It is normal. But context.SaveChanges() give me 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."
I can delete this DetailObject using context.RemoveObject(), but is it possible to do it using EntityCollection<>?
I do not think this problem has something to do with EntityCollection, I think you have a problem with your database schema.
If your master table have a FK to details that is not nullable you cannot remove the detail table row since that would violate your database schema.
Either change it so that the FK can be null or redesign your database schema.
If this is an operation you need to support, maybe you should have a FK in your details table referencing your master table. So, that you can delete detail rows.
If this does not help please supply a script that shows how you create your database tables.
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"
I have an Entity called Cost, which has a required property of CostType
The Cost class has a GetNew() method which sets all the Cost's defaults:
public static GetNew()
{
Cost cost = new Cost ();
foo.CostType = Lists.CostTypes.FirstOrDefault();
// Other Default Values
return foo;
}
The Lists.CostTypes is a static list which is pulled from EF at startup and used in ComboBoxes
I am having problems setting the CostType within my code, after first setting it in the GetNew() method.
For example, the following code reads an excel file, and sets the default type based on a column in the Excel file, or null if it can't find a match
Cost cost = Cost.GetNew();
cost.CostType = Lists.CostTypes.FirstOrDefault(t => t.Name == row[0].ToString());
My problem is, during the Save operation I 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.
My Add Operation looks like this:
public static void AddObject(EntityObject obj, string entitySetName)
{
context.AddObject(entitySetName, obj);
context.SaveChanges();
}
If I remove the line of code that manually sets the Cost when it reads the excel file, the save works fine.
If I change the line of code to read Lists.Costs[2], it saves fine.
If I remove the line of code in GetNew() which sets the default, I get an error telling me that I violated the PK rule of CostTypes, meaning it's trying to insert the Cost Type.
Changing the ComboBox showing Type to something else still gives the same error.
After loading costs from the excel file, my regular Add/Edit forms throw the same error when I change the Type and try and save. If I don't load an excel file, they work fine.
I'm still learning Entity Framework, but so far it has been nothing but a frustration and a headache to use. Does someone know what my problem is and how I can fix it?
EDIT
Here's the info requested by Slauma. I am keeping it simple and excluding unrelated objects
Costs are in one table and CostTypes are in another table. In the database, the Costs.TypeId column is not allowed to be null, and is a Foreign Key to CostTypes. The Id field for both tables is auto-generated.
My EF model is just a generic one with the two database tables added. The only change I made to it was to rename some fields and remove the CostTypes.Costs Navigation Property.
The Excel file that gets imported maps most costs to their matching CostType.Name, however it IS possible that the string in the excel file doesn't match a CostType, so Lists.CostTypes.FirstOrDefault(t => t.Name == row[0].ToString()) can assign aNULLvalue to theCost.Typeproperty. That doesn't seem to be a problem though, because the form still comes up with the list of costs and their default selected items. Item's with aNULLCostType do not have an item selected in the CostTypeComboBox` and trigger a validation error that must be corrected before saving.
The code to load the CostType list is
public static List<T> GetList<T>(string sortProperty)
where T : EntityObject
{
using (var context = new TContext())
{
return ApplyOrder<T>(context.CreateObjectSet<T>(), sortProperty, "OrderBy").ToList();
}
}
The ApplyOrder code can be found here.
The GetList method is called from
public static class Lists
{
public static List<CostType> CostTypes { get; private set; }
static Lists()
{
CostTypes = DAL<CostEntities>.GetList<CostType>("Name");
}
}
I figured it out.... it was a mix of a few different things
Creating a new Cost and setting the Type was adding the cost to the shared data context. If that Cost wasn't included in the list of costs to save, or it failed it's validation error, or the user cancelled out of the Import dialog, the cost still existed in context.ObjectStateManager._addedObjects, even though I never called AddObject or AttachObject. Once I realized that I started callling DeleteObject on costs that were not going to be saved and it cleared up the 1st error I was getting.
The 2nd error I was getting (duplicate PK) was because I was looping through my new Costs and calling AddObject and SaveChanges on each one. Since setting Cost.Type to an attached CostType was automatically adding my Cost to the context, the first cost to get saved was actually adding all the new Costs to the database while the 2nd cost was trying to call AddObject/SaveChanges on what EF saw as an object that already existed
Here is not really a satisfying answer but a mix of guesses and open questions based on your infos in the question and in the comments to your question:
First of all: Your list Lists.CostTypes contains obviously entities which are detached from the context where you are later adding and saving new objects in. Because you have a using block: using (var context = new TContext()) you are retrieving your CostType entities in another context.
To tell EF that these CostType entities already exist in the database you must attach the entities to your second context (context.CostTypes.Attach(costType)) where you save your changes in (or use the same context in your method where you retrieve the list). I don't see in your code that you do this. (CostType is a navigation reference property, not a foreign key property, right?)
On the other hand when the CostType entities are not attached you should get duplicated CostTypes in your database because EF will consider them as new objects (to insert in the DB) when you call AddObject for your Cost entity since EF will always put the whole object graph of detached entities into Added state. Do you get duplicated CostTypes in the DB in your working examples? If not, something important is missing in your code snippets.
The last paragraph assumes that the key for CostType is autogenerated in the DB, as you said. If not, you would get a PK constraint violation instead of duplicated entities.
If the keys for CostType and Cost are really autogenerated identities, I am wondering where the PK violation you mentioned can come from. Every insertion would create a new unique primary key. There could never a PK violaton occur. Can you show the exception message in detail?
Did you check that all Cost entities you want to save really have a non-null CostType property (after the user has fixed all validation errors)? I cannot see any other possible reason in your code why you would get your "Relationship-could-not-be-changed-exception", except that at least for one of the Cost objects CostType is null.
New to the EF, I have an issue.
OK, so, I have an Entity with a related Table (one-to-many relationship). The entity holds a collection of child objects from the related table. I want to remove an object from the related collection but not from the child table.
However, when I call <entity>.myRelatedChildTable.Remove( childEntity ) and then call the _context.SaveChanges( ), I get an exception about ForeignKey Constraints. Now, if I call the _context.DeleteObject() and then _context.SaveChanges() we have no problem. But, now we have no child entity as well--it is deleted from the db.
here is the text of the 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.
anyone have an idea/suggestion how I can fix this?
It sounds like you're basically trying to decouple EF from the database. Unfortunately save changes pushes the changes back to the database which obviously you don't want. You can just not call SaveChanges and continue to work with the collection. EF will account for your deletion and not return that information. At the end you can discard your changes and move on.
If you do actually want to delete the item without deleting the child you can't in the current schema. You would have to allow nulls in the DB.