How to find foreign key field for navigation property? - entity-framework-core

For a given entity I can get all its references by calling
var references = dbContext.Entry(entity).References;
Now I want to set all references to null.
foreach (var reference in references)
{
reference.CurrentValue = null;
}
context.Update(entity);
context.SaveChanges();
But apparently it is not sufficient to set the reference to null, one must set the foreign key property as well. But how to find the foreign key field for a given reference?

You can use Metadata property to get the associated INavigation, then ForeignKey property to get the associated IForeignKey, then the Properties property to get the associated properties of type IProperty, and finally the Name property which can be passed to Property method to obtain the associated PropertyEntry for manipulating the value.
Putting all that in action:
var entry = dbContext.Entry(entity);
foreach (var reference in entry.References)
{
reference.CurrentValue = null;
foreach (var fkProperty in reference.Metadata.ForeignKey.Properties)
entry.Property(fkProperty.Name).CurrentValue = null;
}

Related

greenDao generator is not generating certain variables in auto generated classes

Below is my schema generation code, Two variables (storeIdForSurvey & questionIdForAnswer) are not being auto generated in the model class( Survey & Question), though they are present the in auto generated dao classes (SurveyDao & QuestionDao).
Object Oriented description of the domain is as : User has Store, Store has Survey,Survey has FollowupItems, Survey has Category, Category has Question, Question has History, Question has Answer.
private static void addUser(Schema schema) {
//User
Entity user = schema.addEntity("User");
user.addIdProperty();
user.addStringProperty("districtId");
user.addStringProperty("employeeId");
user.addStringProperty("name");
user.addStringProperty("sessionToken");
user.addStringProperty("userId");
//Store
Entity store = schema.addEntity("Store");
// foreign key
Property userIdForStore = store.addLongProperty("userIdForStore").getProperty();
store.addToOne(user, userIdForStore);
user.addToMany(store, userIdForStore);
store.addIdProperty();
store.addStringProperty("storeId");
store.addStringProperty("address");
store.addStringProperty("city");
store.addStringProperty("storeName");
store.addStringProperty("state");
store.addStringProperty("zip");
store.addStringProperty("storeManagerName");
store.addBooleanProperty("isSurveyHistoryAvailable");
//Survey
Entity survey = schema.addEntity("Survey");
//foreign key
Property storeIdForSurvey = survey.addLongProperty("storeIdForSurvey").getProperty();
survey.addToOne(store, storeIdForSurvey); // one store can have one survey at a time
store.addToOne(survey, storeIdForSurvey);
survey.addIdProperty();
survey.addStringProperty("surveyId");
survey.addStringProperty("dmSignImagePath");
survey.addStringProperty("dmSignImageName");
survey.addStringProperty("smSignImagePath");
survey.addStringProperty("smSignImageName");
survey.addStringProperty("startlatitude");
survey.addStringProperty("startlongitude");
survey.addStringProperty("submitLatitude");
survey.addStringProperty("submitLongitude");
survey.addStringProperty("acknowledgedBy");
survey.addStringProperty("deliveredBy");
survey.addStringProperty("name");
survey.addStringProperty("createdBy");
survey.addStringProperty("description");
survey.addStringProperty("storeId");
survey.addStringProperty("districtManager");
survey.addDateProperty("startDate");
survey.addDateProperty("submitDate");
survey.addDateProperty("syncDate");
survey.addDateProperty("createdDate");
survey.addDateProperty("actionItemAssignDate");
survey.addDateProperty("actionItemDueDate");
survey.addDoubleProperty("score");
//FolloupItems
Entity followupItem = schema.addEntity("FollowupItem");
//foreign key
Property surveyIdForFollowupItem = followupItem.addLongProperty("surveyIdForFollowupItem").getProperty();
followupItem.addToOne(survey, surveyIdForFollowupItem);
survey.addToMany(followupItem, surveyIdForFollowupItem);
followupItem.addIdProperty();
followupItem.addStringProperty("assignedTo");
followupItem.addStringProperty("comment");
followupItem.addStringProperty("photoName");
followupItem.addStringProperty("photoURL");
followupItem.addDateProperty("assignedDate");
followupItem.addDateProperty("dueDate");
followupItem.addDateProperty("expeireDate");
//Category
Entity category = schema.addEntity("Category");
//foreign key
Property surveyIdForCategory = category.addLongProperty("surveyIdForCategory").getProperty();
category.addToOne(survey, surveyIdForCategory);
survey.addToMany(category, surveyIdForCategory);
category.addIdProperty();
category.addStringProperty("categoryId");
category.addStringProperty("name");
category.addStringProperty("weight");
category.addStringProperty("surveyId");
category.addDoubleProperty("totalScore");
category.addIntProperty("sortOrder");
category.addBooleanProperty("completionStatus");
category.addBooleanProperty("hasActionItem");
//Question
Entity question = schema.addEntity("Question");
//foreign key
Property categoryIdForQuestion = question.addLongProperty("categoryIdForQuestion").getProperty();
question.addToOne(category, categoryIdForQuestion);
category.addToMany(question, categoryIdForQuestion);
question.addIdProperty();
question.addStringProperty("questionId");
question.addDateProperty("startDate");
question.addDateProperty("endDate");
question.addStringProperty("statement");
question.addStringProperty("type");
question.addStringProperty("weight");
question.addStringProperty("surveyCategoryName");
question.addIntProperty("displayOrder");
question.addBooleanProperty("naFlag");
question.addBooleanProperty("isRequired");
//Question History
Entity questionHistory = schema.addEntity("questionHistory");
//foreign key
Property questionIdForQuestionHistory = questionHistory.addLongProperty("questionIdForQuestionHistory").getProperty();
questionHistory.addToOne(store, questionIdForQuestionHistory);
question.addToMany(questionHistory, questionIdForQuestionHistory);
questionHistory.addIdProperty();
questionHistory.addStringProperty("questionId");
questionHistory.addStringProperty("secondLastHistory");
questionHistory.addStringProperty("lastHistory");
//Answer
Entity answer = schema.addEntity("Answer");
//foreign key
Property questionIdForAnswer = answer.addLongProperty("questionIdForAnswer").getProperty();
question.addToOne(answer, questionIdForAnswer);
answer.addToOne(question, questionIdForAnswer);
answer.addIdProperty();
answer.addStringProperty("projectType");
answer.addStringProperty("assignedTo");
answer.addStringProperty("comment");
answer.addStringProperty("photoUrl");
answer.addStringProperty("photoNmae");
answer.addStringProperty("selectedOption");
answer.addDateProperty("assignedDate");
answer.addDateProperty("dueDate");
answer.addDateProperty("expireDate");
answer.addDoubleProperty("score");
}
please read the documentation carefully:
public ToOne addToOne(Entity target, Property fkProperty)
Adds a to-one relationship to the given target entity using the given given
foreign key property (which belongs to this entity).
This means the following statement is correct:
Property storeIdForSurvey = survey.addLongProperty("storeIdForSurvey").getProperty();
survey.addToOne(store, storeIdForSurvey);
but the next statement is incorrect since the Property storeIdForSurvey is not member of the Entity store:
store.addToOne(survey, storeIdForSurvey);
Try to use this statement instead:
store.addToOneWithoutProperty("Survey", survey, "storeIdForSurvey");

Entity Framework - trying to insert null values when creating a new object

I'm having the same problem that a few of you have had - when trying to insert a new object, EF inserts null values for some of their properties, and the insert fails.
First let me describe the structure of our DB. Its an event management system, in which each event needs to be associated with a practice group, stored in a cache table but ultimately fetched from Active Directory. I manually created the join table - is that a problem? Anyway, so Event has a foreign key pointing to EventPracticeGroup, which has a foreign key pointing to PracticeGroupCache. PracticeGroupCache also has a RegionId pointing to the Regions table.
The problem comes when trying to insert a new EventPracticeGroup object. Below is the code I'm currently using:
var eventPracticeGroup = new EventPracticeGroup();
if (TryUpdateModel<EventPracticeGroup>(eventPracticeGroup))
{
/*
var eventId = EventScheduleRepository.GetById(Convert.ToInt32(Request.QueryString["EventScheduleId"])).EventId;
eventPracticeGroup.Event = EventRepository.GetById(eventId);
eventPracticeGroup.PracticeGroupCache = PracticeGroupCacheRepository.GetById(eventPracticeGroup.PracticeGroupCacheId);
eventPracticeGroup.PracticeGroupCache.Region = RegionRepository.GetById(eventPracticeGroup.PracticeGroupCache.RegionId);
EventPracticeGroupRepository.Add(eventPracticeGroup);
*/
var eventId = EventScheduleRepository.GetById(Convert.ToInt32(Request.QueryString["EventScheduleId"])).EventId;
var theEvent = new Event() { Id = eventId };
EventRepository.Repository.UnitOfWork.Context.AttachTo("Events",theEvent);
var practiceGroupCache = new PracticeGroupCache() { Id = eventPracticeGroup.PracticeGroupCacheId };
practiceGroupCache.Region = new Region() { Id = eventPracticeGroup.PracticeGroupCache.RegionId };
eventPracticeGroup.PracticeGroupCache = practiceGroupCache;
EventPracticeGroupRepository.Add(eventPracticeGroup);
EventPracticeGroupRepository.Save();
return RedirectToAction("Index");
}
Anyway... as you can see, I've just tried using stub objects (no help), and I've also tried actually fetching and setting the objects. The error I get is:
Cannot insert the value NULL into column 'Name', table 'XXXX.dbo.Regions'; column does not allow nulls. INSERT fails. The statement has been terminated.
Obviously name is not a key field. I have checked the EDMX XML - only the Id (primary key columns) have StoreGeneratedPattern set to Identity, as they should (they are int32 identity columns). Not a single foreign key has StoreGeneratedPattern set to identity.
if I set Regions.Name to allow nulls, PracticeGroupCaches.Description throws the same error. It seems that every linked object gets set to null. I did have a look with the debugger, when I used the now commented out code, nothing was null and everything had a value. I even got the RegionRepository to return all of the regions, just to see if one of them somewhere had a null name. None did. There are only 2 in my test DB. Our object context is shared per HTTP request.
Please can anyone help. At this point I would settle for using the dirtiest workaround as long as it worked.
Regards,
Jonathan.
Look what happens when you call this line:
EventPracticeGroupRepository.Add(eventPracticeGroup);
You are adding a new eventPracticeGroup to the context. But eventPracticeGroup has the other related objects:
eventPracticeGroup -> PracticeGroupCache -> Region
And you create new objects for those:
var practiceGroupCache = new PracticeGroupCache() {
Id = eventPracticeGroup.PracticeGroupCacheId };
practiceGroupCache.Region = new Region() {
Id = eventPracticeGroup.PracticeGroupCache.RegionId };
eventPracticeGroup.PracticeGroupCache = practiceGroupCache;
When you add the eventPracticeGroup to the context this whole object graph gets added which means that EF considers all three objects as new which have to be added to the DB. Since you only fill the Id properties other string properties (like Name or Description) are null. Because they are not nullable in the database the INSERT command fails.
But I guess that you don't want to insert the related entities into the DB anyway but only the eventPracticeGroup. So you need to attach them to the context before you add the new object, something like:
var practiceGroupCache = new PracticeGroupCache() {
Id = eventPracticeGroup.PracticeGroupCacheId };
EventRepository.Repository.UnitOfWork.Context.AttachTo(
"PracticeGroupCaches",practiceGroupCache);
practiceGroupCache.Region = new Region() {
Id = eventPracticeGroup.PracticeGroupCache.RegionId };
EventRepository.Repository.UnitOfWork.Context.AttachTo(
"Regions",practiceGroupCache.Region);
eventPracticeGroup.PracticeGroupCache = practiceGroupCache;
EventPracticeGroupRepository.Add(eventPracticeGroup);
BTW as a side note: About this EventRepository.Repository.UnitOfWork.Context.XXX take a look at Ladislav Mrnka's answer here: EF 4.0 IsAttachedTo extension method and error An object with the same key already exists
Try to Add: [DatabaseGenerated(DatabaseGeneratedOption.None)]
On your Id field, Like:
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
It seems like you already found the problem and the solution. In your DB schema it seems like the columns don't allow NULL values. So either change all these columns to allow NULL or don't insert null (this is what you currently are trying to do)

The relationship could not be changed because one or more of the foreign-key properties is non-nullable

I am getting this error when I GetById() on an entity and then set the collection of child entities to my new list which comes from the MVC view.
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 don't quite understand this line:
The relationship could not be changed
because one or more of the foreign-key
properties is non-nullable.
Why would I change the relationship between 2 entities? It should remain the same throughout the lifetime of the whole application.
The code the exception occurs on is simple assigning modified child classes in a collection to the existing parent class. This would hopefully cater for removal of child classes, addition of new ones and modifications. I would have thought Entity Framework handles this.
The lines of code can be distilled to:
var thisParent = _repo.GetById(1);
thisParent.ChildItems = modifiedParent.ChildItems();
_repo.Save();
You should delete old child items thisParent.ChildItems one by one manually. Entity Framework doesn't do that for you. It finally cannot decide what you want to do with the old child items - if you want to throw them away or if you want to keep and assign them to other parent entities. You must tell Entity Framework your decision. But one of these two decisions you HAVE to make since the child entities cannot live alone without a reference to any parent in the database (due to the foreign key constraint). That's basically what the exception says.
Edit
What I would do if child items could be added, updated and deleted:
public void UpdateEntity(ParentItem parent)
{
// Load original parent including the child item collection
var originalParent = _dbContext.ParentItems
.Where(p => p.ID == parent.ID)
.Include(p => p.ChildItems)
.SingleOrDefault();
// We assume that the parent is still in the DB and don't check for null
// Update scalar properties of parent,
// can be omitted if we don't expect changes of the scalar properties
var parentEntry = _dbContext.Entry(originalParent);
parentEntry.CurrentValues.SetValues(parent);
foreach (var childItem in parent.ChildItems)
{
var originalChildItem = originalParent.ChildItems
.Where(c => c.ID == childItem.ID && c.ID != 0)
.SingleOrDefault();
// Is original child item with same ID in DB?
if (originalChildItem != null)
{
// Yes -> Update scalar properties of child item
var childEntry = _dbContext.Entry(originalChildItem);
childEntry.CurrentValues.SetValues(childItem);
}
else
{
// No -> It's a new child item -> Insert
childItem.ID = 0;
originalParent.ChildItems.Add(childItem);
}
}
// Don't consider the child items we have just added above.
// (We need to make a copy of the list by using .ToList() because
// _dbContext.ChildItems.Remove in this loop does not only delete
// from the context but also from the child collection. Without making
// the copy we would modify the collection we are just interating
// through - which is forbidden and would lead to an exception.)
foreach (var originalChildItem in
originalParent.ChildItems.Where(c => c.ID != 0).ToList())
{
// Are there child items in the DB which are NOT in the
// new child item collection anymore?
if (!parent.ChildItems.Any(c => c.ID == originalChildItem.ID))
// Yes -> It's a deleted child item -> Delete
_dbContext.ChildItems.Remove(originalChildItem);
}
_dbContext.SaveChanges();
}
Note: This is not tested. It's assuming that the child item collection is of type ICollection. (I usually have IList and then the code looks a bit different.) I've also stripped away all repository abstractions to keep it simple.
I don't know if that is a good solution, but I believe that some kind of hard work along these lines must be done to take care of all kinds of changes in the navigation collection. I would also be happy to see an easier way of doing it.
The reason you're facing this is due to the difference between composition and aggregation.
In composition, the child object is created when the parent is created and is destroyed when its parent is destroyed. So its lifetime is controlled by its parent. e.g. A blog post and its comments. If a post is deleted, its comments should be deleted. It doesn't make sense to have comments for a post that doesn't exist. Same for orders and order items.
In aggregation, the child object can exist irrespective of its parent. If the parent is destroyed, the child object can still exist, as it may be added to a different parent later. e.g.: the relationship between a playlist and the songs in that playlist. If the playlist is deleted, the songs shouldn't be deleted. They may be added to a different playlist.
The way Entity Framework differentiates aggregation and composition relationships is as follows:
For composition: it expects the child object to a have a composite primary key (ParentID, ChildID). This is by design as the IDs of the children should be within the scope of their parents.
For aggregation: it expects the foreign key property in the child object to be nullable.
So, the reason you're having this issue is because of how you've set your primary key in your child table. It should be composite, but it's not. So, Entity Framework sees this association as aggregation, which means, when you remove or clear the child objects, it's not going to delete the child records. It'll simply remove the association and sets the corresponding foreign key column to NULL (so those child records can later be associated with a different parent). Since your column does not allow NULL, you get the exception you mentioned.
Solutions:
1- If you have a strong reason for not wanting to use a composite key, you need to delete the child objects explicitly. And this can be done simpler than the solutions suggested earlier:
context.Children.RemoveRange(parent.Children);
2- Otherwise, by setting the proper primary key on your child table, your code will look more meaningful:
parent.Children.Clear();
This is a very big problem. What actually happens in your code is this:
You load Parent from the database and get an attached entity
You replace its child collection with new collection of detached children
You save changes but during this operation all children are considered as added becasue EF didn't know about them till this time. So EF tries to set null to foreign key of old children and insert all new children => duplicate rows.
Now the solution really depends on what you want to do and how would you like to do it?
If you are using ASP.NET MVC you can try to use UpdateModel or TryUpdateModel.
If you want just update existing children manually, you can simply do something like:
foreach (var child in modifiedParent.ChildItems)
{
context.Childs.Attach(child);
context.Entry(child).State = EntityState.Modified;
}
context.SaveChanges();
Attaching is actually not needed (setting the state to Modified will also attach the entity) but I like it because it makes the process more obvious.
If you want to modify existing, delete existing and insert new childs you must do something like:
var parent = context.Parents.GetById(1); // Make sure that childs are loaded as well
foreach(var child in modifiedParent.ChildItems)
{
var attachedChild = FindChild(parent, child.Id);
if (attachedChild != null)
{
// Existing child - apply new values
context.Entry(attachedChild).CurrentValues.SetValues(child);
}
else
{
// New child
// Don't insert original object. It will attach whole detached graph
parent.ChildItems.Add(child.Clone());
}
}
// Now you must delete all entities present in parent.ChildItems but missing
// in modifiedParent.ChildItems
// ToList should make copy of the collection because we can't modify collection
// iterated by foreach
foreach(var child in parent.ChildItems.ToList())
{
var detachedChild = FindChild(modifiedParent, child.Id);
if (detachedChild == null)
{
parent.ChildItems.Remove(child);
context.Childs.Remove(child);
}
}
context.SaveChanges();
I found this answer much more helpful for the same error.
It seems that EF does not like it when you Remove, it prefers Delete.
You can delete a collection of records attached to a record like this.
order.OrderDetails.ToList().ForEach(s => db.Entry(s).State = EntityState.Deleted);
In the example, all of the Detail records attached to an Order have their State set to Delete. (In preparation to Add back updated Details, as part of an Order update)
I've no idea why the other two answers are so popular!
I believe you were right in assuming the ORM framework should handle it - after all, that is what it promises to deliver. Otherwise your domain model gets corrupted by persistence concerns. NHibernate manages this happily if you setup the cascade settings correctly. In Entity Framework it is also possible, they just expect you to follow better standards when setting up your database model, especially when they have to infer what cascading should be done:
You have to define the parent - child relationship correctly by using an "identifying relationship".
If you do this, Entity Framework knows the child object is identified by the parent, and therefore it must be a "cascade-delete-orphans" situation.
Other than the above, you might need to (from NHibernate experience)
thisParent.ChildItems.Clear();
thisParent.ChildItems.AddRange(modifiedParent.ChildItems);
instead of replacing the list entirely.
UPDATE
#Slauma's comment reminded me that detached entities are another part of the overall problem. To solve that, you can take the approach of using a custom model binder that constructs your models by attempting to load it from the context. This blog post shows an example of what I mean.
If you are using AutoMapper with Entity Framework on the same class, you might hit this problem. For instance if your class is
class A
{
public ClassB ClassB { get; set; }
public int ClassBId { get; set; }
}
AutoMapper.Map<A, A>(input, destination);
This will try to copy both properties. In this case, ClassBId is non Nullable. Since AutoMapper will copy destination.ClassB = input.ClassB; this will cause a problem.
Set your AutoMapper to Ignore ClassB property.
cfg.CreateMap<A, A>()
.ForMember(m => m.ClassB, opt => opt.Ignore()); // We use the ClassBId
I had same problem, but I knew it had worked OK in other cases, so I reduced the problem to this:
parent.OtherRelatedItems.Clear(); //this worked OK on SaveChanges() - items were being deleted from DB
parent.ProblematicItems.Clear(); // this was causing the mentioned exception on SaveChanges()
OtherRelatedItems had a composite Primary Key (parentId + some local column) and worked OK
ProblematicItems had their own single-column Primary Key, and the parentId was only a FK. This was causing the exception after Clear().
All I had to do was to make the ParentId a part of composite PK to indicate that the children can't exist without a parent. I used DB-first model, added the PK and marked the parentId column as EntityKey (so, I had to update it both in DB and EF - not sure if EF alone would be enough).
Once you think about it, it's a very elegant distinction that EF uses to decide if children "make sense" without a parent (in this case Clear() won't delete them and throw exception unless you set the ParentId to something else/special), or - like in the original question - we expect the items to be deleted once they are removed from the parent.
I just had the same error.
I have two tables with a parent child relationship, but I configured a "on delete cascade" on the foreign key column in the table definition of the child table.
So when I manually delete the parent row (via SQL) in the database it will automatically delete the child rows.
However this did not work in EF, the error described in this thread showed up.
The reason for this was, that in my entity data model (edmx file) the properties of the association between the parent and the child table were not correct.
The End1 OnDelete option was configured to be none ("End1" in my model is the end which has a multiplicity of 1).
I manually changed the End1 OnDelete option to Cascade and than it worked.
I do not know why EF is not able to pick this up, when I update the model from the database (I have a database first model).
For completeness, this is how my code to delete looks like:
public void Delete(int id)
{
MyType myObject = _context.MyTypes.Find(id);
_context.MyTypes.Remove(myObject);
_context.SaveChanges();
}
If I hadn´t a cascade delete defined, I would have to delete the child rows manually before deleting the parent row.
This happens because the Child Entity is marked as Modified instead of Deleted.
And the modification that EF does to the Child Entity when parent.Remove(child) is executed, is simply setting the reference to its parent to null.
You can check the child's EntityState by typing the following code into Visual Studio's Immediate Window when the exception occurs, after executing SaveChanges():
_context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Modified).ElementAt(X).Entity
where X should be replaced by the deleted Entity.
If you don't have access to the ObjectContext to execute _context.ChildEntity.Remove(child), you can solve this issue by making the foreign key a part of the primary key on the child table.
Parent
________________
| PK IdParent |
| Name |
|________________|
Child
________________
| PK IdChild |
| PK,FK IdParent |
| Name |
|________________|
This way, if you execute parent.Remove(child), EF will correctly mark the Entity as Deleted.
This type of solution did the trick for me:
Parent original = db.Parent.SingleOrDefault<Parent>(t => t.ID == updated.ID);
db.Childs.RemoveRange(original.Childs);
updated.Childs.ToList().ForEach(c => original.Childs.Add(c));
db.Entry<Parent>(original).CurrentValues.SetValues(updated);
Its important to say that this deletes all the records and insert them again.
But for my case (less then 10) it´s ok.
I hope it helps.
I ran into this problem today and wanted to share my solution. In my case, the solution was to delete the Child items before getting the Parent from the database.
Previously I was doing it like in the code below. I will then get the same error listed in this question.
var Parent = GetParent(parentId);
var children = Parent.Children;
foreach (var c in children )
{
Context.Children.Remove(c);
}
Context.SaveChanges();
What worked for me, is to get the children items first, using the parentId (foreign key) and then delete those items. Then I can get the Parent from the database and at that point, it should not have any children items anymore and I can add new children items.
var children = GetChildren(parentId);
foreach (var c in children )
{
Context.Children.Remove(c);
}
Context.SaveChanges();
var Parent = GetParent(parentId);
Parent.Children = //assign new entities/items here
You must manually clear the ChildItems collection and append new items into it:
thisParent.ChildItems.Clear();
thisParent.ChildItems.AddRange(modifiedParent.ChildItems);
After that you can call DeleteOrphans extension method which will handle with orphaned entities (it must be called between DetectChanges and SaveChanges methods).
public static class DbContextExtensions
{
private static readonly ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>> s_navPropMappings = new ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>>();
public static void DeleteOrphans( this DbContext source )
{
var context = ((IObjectContextAdapter)source).ObjectContext;
foreach (var entry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
{
var entityType = entry.EntitySet.ElementType as EntityType;
if (entityType == null)
continue;
var navPropMap = s_navPropMappings.GetOrAdd(entityType, CreateNavigationPropertyMap);
var props = entry.GetModifiedProperties().ToArray();
foreach (var prop in props)
{
NavigationProperty navProp;
if (!navPropMap.TryGetValue(prop, out navProp))
continue;
var related = entry.RelationshipManager.GetRelatedEnd(navProp.RelationshipType.FullName, navProp.ToEndMember.Name);
var enumerator = related.GetEnumerator();
if (enumerator.MoveNext() && enumerator.Current != null)
continue;
entry.Delete();
break;
}
}
}
private static ReadOnlyDictionary<string, NavigationProperty> CreateNavigationPropertyMap( EntityType type )
{
var result = type.NavigationProperties
.Where(v => v.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
.Where(v => v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One || (v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne && v.FromEndMember.GetEntityType() == v.ToEndMember.GetEntityType()))
.Select(v => new { NavigationProperty = v, DependentProperties = v.GetDependentProperties().Take(2).ToArray() })
.Where(v => v.DependentProperties.Length == 1)
.ToDictionary(v => v.DependentProperties[0].Name, v => v.NavigationProperty);
return new ReadOnlyDictionary<string, NavigationProperty>(result);
}
}
I've tried these solutions and many others, but none of them quite worked out. Since this is the first answer on google, I'll add my solution here.
The method that worked well for me was to take relationships out of the picture during commits, so there was nothing for EF to screw up. I did this by re-finding the parent object in the DBContext, and deleting that. Since the re-found object's navigation properties are all null, the childrens' relationships are ignored during the commit.
var toDelete = db.Parents.Find(parentObject.ID);
db.Parents.Remove(toDelete);
db.SaveChanges();
Note that this assumes the foreign keys are setup with ON DELETE CASCADE, so when the parent row is removed, the children will be cleaned up by the database.
I used Mosh's solution, but it was not obvious to me how to implement the composition key correctly in code first.
So here is the solution:
public class Holiday
{
[Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int HolidayId { get; set; }
[Key, Column(Order = 1), ForeignKey("Location")]
public LocationEnum LocationId { get; set; }
public virtual Location Location { get; set; }
public DateTime Date { get; set; }
public string Name { get; set; }
}
If you are using Auto mapper and facing the the issue following is the good solution, it work for me
https://www.codeproject.com/Articles/576393/Solutionplusto-aplus-Theplusoperationplusfailed
Since the problem is that we're mapping null navigation properties, and we actually don't need them to be updated on the Entity since they didn't changed on the Contract, we need to ignore them on the mapping definition:
ForMember(dest => dest.RefundType, opt => opt.Ignore())
So my code ended up like this:
Mapper.CreateMap<MyDataContract, MyEntity>
ForMember(dest => dest.NavigationProperty1, opt => opt.Ignore())
ForMember(dest => dest.NavigationProperty2, opt => opt.Ignore())
.IgnoreAllNonExisting();
This issue arise because we try to delete the parent table still child table data is present.
We solve the problem with help of cascade delete.
In model Create method in dbcontext class.
modelBuilder.Entity<Job>()
.HasMany<JobSportsMapping>(C => C.JobSportsMappings)
.WithRequired(C => C.Job)
.HasForeignKey(C => C.JobId).WillCascadeOnDelete(true);
modelBuilder.Entity<Sport>()
.HasMany<JobSportsMapping>(C => C.JobSportsMappings)
.WithRequired(C => C.Sport)
.HasForeignKey(C => C.SportId).WillCascadeOnDelete(true);
After that,In our API Call
var JobList = Context.Job
.Include(x => x.JobSportsMappings) .ToList();
Context.Job.RemoveRange(JobList);
Context.SaveChanges();
Cascade delete option delete the parent as well parent related child table with this simple code. Make it try in this simple way.
Remove Range which used for delete the list of records in the database
Thanks
I also solved my problem with Mosh's answer and I thought PeterB's answer was a bit of since it used an enum as foreign key. Remember that you will need to add a new migration after adding this code.
I can also recommend this blog post for other solutions:
http://www.kianryan.co.uk/2013/03/orphaned-child/
Code:
public class Child
{
[Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Heading { get; set; }
//Add other properties here.
[Key, Column(Order = 1)]
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
Using the solution of Slauma, I created some generic functions to help update child objects and collections of child objects.
All my persistent objects implement this interface
/// <summary>
/// Base interface for all persisted entries
/// </summary>
public interface IBase
{
/// <summary>
/// The Id
/// </summary>
int Id { get; set; }
}
With this I implemented these two functions in my Repository
/// <summary>
/// Check if orgEntry is set update it's values, otherwise add it
/// </summary>
/// <param name="set">The collection</param>
/// <param name="entry">The entry</param>
/// <param name="orgEntry">The original entry found in the database (can be <code>null</code> is this is a new entry)</param>
/// <returns>The added or updated entry</returns>
public T AddOrUpdateEntry<T>(DbSet<T> set, T entry, T orgEntry) where T : class, IBase
{
if (entry.Id == 0 || orgEntry == null)
{
entry.Id = 0;
return set.Add(entry);
}
else
{
Context.Entry(orgEntry).CurrentValues.SetValues(entry);
return orgEntry;
}
}
/// <summary>
/// check if each entry of the new list was in the orginal list, if found, update it, if not found add it
/// all entries found in the orignal list that are not in the new list are removed
/// </summary>
/// <typeparam name="T">The type of entry</typeparam>
/// <param name="set">The database set</param>
/// <param name="newList">The new list</param>
/// <param name="orgList">The original list</param>
public void AddOrUpdateCollection<T>(DbSet<T> set, ICollection<T> newList, ICollection<T> orgList) where T : class, IBase
{
// attach or update all entries in the new list
foreach (T entry in newList)
{
// Find out if we had the entry already in the list
var orgEntry = orgList.SingleOrDefault(e => e.Id != 0 && e.Id == entry.Id);
AddOrUpdateEntry(set, entry, orgEntry);
}
// Remove all entries from the original list that are no longer in the new list
foreach (T orgEntry in orgList.Where(e => e.Id != 0).ToList())
{
if (!newList.Any(e => e.Id == orgEntry.Id))
{
set.Remove(orgEntry);
}
}
}
To use it i do the following:
var originalParent = _dbContext.ParentItems
.Where(p => p.Id == parent.Id)
.Include(p => p.ChildItems)
.Include(p => p.ChildItems2)
.SingleOrDefault();
// Add the parent (including collections) to the context or update it's values (except the collections)
originalParent = AddOrUpdateEntry(_dbContext.ParentItems, parent, originalParent);
// Update each collection
AddOrUpdateCollection(_dbContext.ChildItems, parent.ChildItems, orgiginalParent.ChildItems);
AddOrUpdateCollection(_dbContext.ChildItems2, parent.ChildItems2, orgiginalParent.ChildItems2);
Hope this helps
EXTRA: You could also make a seperate DbContextExtentions (or your own context inferface) class:
public static void DbContextExtentions {
/// <summary>
/// Check if orgEntry is set update it's values, otherwise add it
/// </summary>
/// <param name="_dbContext">The context object</param>
/// <param name="set">The collection</param>
/// <param name="entry">The entry</param>
/// <param name="orgEntry">The original entry found in the database (can be <code>null</code> is this is a new entry)</param>
/// <returns>The added or updated entry</returns>
public static T AddOrUpdateEntry<T>(this DbContext _dbContext, DbSet<T> set, T entry, T orgEntry) where T : class, IBase
{
if (entry.IsNew || orgEntry == null) // New or not found in context
{
entry.Id = 0;
return set.Add(entry);
}
else
{
_dbContext.Entry(orgEntry).CurrentValues.SetValues(entry);
return orgEntry;
}
}
/// <summary>
/// check if each entry of the new list was in the orginal list, if found, update it, if not found add it
/// all entries found in the orignal list that are not in the new list are removed
/// </summary>
/// <typeparam name="T">The type of entry</typeparam>
/// <param name="_dbContext">The context object</param>
/// <param name="set">The database set</param>
/// <param name="newList">The new list</param>
/// <param name="orgList">The original list</param>
public static void AddOrUpdateCollection<T>(this DbContext _dbContext, DbSet<T> set, ICollection<T> newList, ICollection<T> orgList) where T : class, IBase
{
// attach or update all entries in the new list
foreach (T entry in newList)
{
// Find out if we had the entry already in the list
var orgEntry = orgList.SingleOrDefault(e => e.Id != 0 && e.Id == entry.Id);
AddOrUpdateEntry(_dbContext, set, entry, orgEntry);
}
// Remove all entries from the original list that are no longer in the new list
foreach (T orgEntry in orgList.Where(e => e.Id != 0).ToList())
{
if (!newList.Any(e => e.Id == orgEntry.Id))
{
set.Remove(orgEntry);
}
}
}
}
and use it like:
var originalParent = _dbContext.ParentItems
.Where(p => p.Id == parent.Id)
.Include(p => p.ChildItems)
.Include(p => p.ChildItems2)
.SingleOrDefault();
// Add the parent (including collections) to the context or update it's values (except the collections)
originalParent = _dbContext.AddOrUpdateEntry(_dbContext.ParentItems, parent, originalParent);
// Update each collection
_dbContext.AddOrUpdateCollection(_dbContext.ChildItems, parent.ChildItems, orgiginalParent.ChildItems);
_dbContext.AddOrUpdateCollection(_dbContext.ChildItems2, parent.ChildItems2, orgiginalParent.ChildItems2);
I was face same problem when I am going to delete my record than some issue was occur , for this issue solution is that when you are going to delete your record than you missing some thing before deleting header/master record you must write to code for delete its detail before header/Master I hope you issue will be resolve.
I had the same issue when I was trying to modify the scalar property of the targeted entity and realized I have accidentally referenced the target entity's parent:
entity.GetDbContextFromEntity().Entry(entity).Reference(i => i.ParentEntity).Query().Where(p => p.ID == 1).Load();
Just an advice by making sure the target entity does not reference any parent.
I've met this problem before several hours and try everything, but in my case the solution was diferent from the listed above.
If you use already retrieved entity from the database and try to modify it's childrens the error will occure, but if you get fresh copy of the entity from the database there should not be any problems.
Do not use this:
public void CheckUsersCount(CompanyProduct companyProduct)
{
companyProduct.Name = "Test";
}
Use this:
public void CheckUsersCount(Guid companyProductId)
{
CompanyProduct companyProduct = CompanyProductManager.Get(companyProductId);
companyProduct.Name = "Test";
}

How do insert more than one row into a table using Entity Framework when one column is a foreign key?

Please help me. I can't find this anywhere. I want to do this with Entity framework when inserting:
Add 1 row to table tParent
Add 4 rows to table tChildren.
tChildren has a column that is a FK of tParent called ParentID.
I'm on 4.0 framework.
You will have to add the children to the parent using a loop but need to call save changes once, something like as follows, say the parent is Order and child is called OrderItem
public void AddOrder(OrderDto orderToAdd)
{
var newOrder = Order.CreateOrder(orderToAdd.OrderID, .....); //set all other non nullable attributes
neworder.OrderDate = orderToAdd.OrderDate;
//set other properties
'
'
//then add the order items to the newOrder
//note orderToAdd.OrderItemsDto is the ChildList
foreach (OrderItemDto o in orderToAdd.OrderItemsDto)
{
var orderItem = OrderItem.CreateOrderItem(orderToAdd.OrderID, o.OrderItemID); //set all other non nullable attributes
orderItem.OrderQuantity = o.OrderQuantity;
//set other properties
'
'
newOrder.OrderItems.Add(orderItem);
}
context.AddToOrders(newOrder);
context.SaveChanges();
}
The key is that you have navigational properties setup on the entities.
tParent should have a navigational property called tChildrens, and (optionally), tChildren should have a navigational property called tParent.
The multiplicity should be tParent 1..* tChildren.
With that in mind, this should do the trick:
var parent = new tParent();
var child1 = new tChild();
var child2 = new tChild();
// set properties you need, etc etc
parent.tChildrens.Add(child1);
parent.tChildrens.Add(child2);
context.SaveChanges();
EF is smart enough to add the parent, grab the identity, then insert the children with the FK set to the identity just created.

Updating a list of foreign keys in EF4, using MVC 2 Repository Viewmodel Pattern

Okay, I'm really struggling with how to update a list of foreign keys in MVC2/EF4.
I have a one to many relationship between a Template object which can have many or no TemplateScenario objects.
Essentially, I have an edit method in a controller that is trying to do this:
// POST: /Modes/Edit/1
[HttpPost]
public ActionResult Edit(int id, FormCollection formValues)
{
Template template = _templateRepository.GetTemplate(id);
TemplateCreateViewModel viewModel = new TemplateCreateViewModel();
viewModel.Template = template;
viewModel.TemplateScenarioList = template.TemplateScenarios.ToList();
//Update the model
UpdateModel(viewModel);
UpdateModel(viewModel.Template.TemplateScenarios, "TemplateScenarioList", new[] { "ScenarioID", "TemplateID" });
_templateRepository.Save();
return RedirectToAction("Edit", new { id = template.TemplateID });
}
This code successfully updates the 'template' object. It also adds the 'templatescenario' child objects BUT only if it is the first time I have added 'templatescenarios' to this particular template. If any templatescenario objects already exist for a given template, and I try to update them based on the new list, I get this 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."
The _templateRepository.Save(); is just calling the entities.SaveChanges() EF4 method.
I can solve this in a dirty way by passing down a list of templatescenario ids to my repository class in a custom 'update' method that looks like this:
public void Update(Template template, IList<int> templateScenarios)
{
//Delete Old Entries
foreach (TemplateScenario ts in entities.TemplateScenarios)
{
if (ts.TemplateID == template.TemplateID)
{
if (templateScenarios == null)
entities.TemplateScenarios.DeleteObject(ts);
else if (!templateScenarios.Where(tsl => tsl == ts.ScenarioID).Any())
entities.TemplateScenarios.DeleteObject(ts);
}
}
//Don't need to add anything if they are null.
if (templateScenarios == null)
return;
//Add New Entries
foreach (int ts in templateScenarios)
{
if (!entities.TemplateScenarios.Where(tsc => tsc.ScenarioID == ts && tsc.TemplateID == template.TemplateID).Any())
{
TemplateScenario tempScenToAdd = new TemplateScenario();
tempScenToAdd.ScenarioID = ts;
tempScenToAdd.TemplateID = template.TemplateID;
entities.TemplateScenarios.AddObject(tempScenToAdd);
}
}
}
But that just feels dirty and I think I'm so close with the first, more automatic method. I've scoured the internet and found some similar posts on stackoverflow but am finding it difficult to reach that 'aha' moment.
Thanks,
Tom.
Incidently, I sorted out my problem.
The problem was my joining table was incorrectly using it's own primary key instead of using a composite key based on two foreign keys. This is obviously wrong /bad practice and EF4 and UpdateModel() don't play nice.
I had inherited the DB design from an ex-collegue and thus had taken the db design as correct without thinking too much about it. Very stupid of me, I know.