3
votes

I'm trying to insert a parent entity with n children that can further have n of their own children. When I insert an object that has no grandchildren everything works fine, but as soon as the input object contains grandchildren the following error is presented on Context.SaveChanges():

"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."

Parent:

 public class Parent : Entity
    { 
        public Parent()
        {
            this.Children = new HashSet<Children>();
        }        
        public virtual ICollection<Child> Children { get; set; }
    }

Child:

 public class Child : Entity
    {
        public Child ()
        {
            this.GrandChildren = new HashSet<GrandChild>();
        }
        public virtual ICollection<GrandChild> GrandChildren { get; set; }      
        public int ParentId { get; set; }
        public virtual Parent Parent { get; set; }
    }

Grandchild:

 public class GrandChild : Entity
    {
        public int ChildId { get; set; }       
        public virtual Child Child { get; set; }
    }  

Here's my DBContext:

    modelBuilder.Entity<Child>().ToTable("Children")
        .HasRequired<Parent>(x => x.Parent);

    modelBuilder.Entity<GrandChild>().ToTable("GrandChildren")
        .HasRequired<Child>(y => y.Child);

    modelBuilder.Entity<Parent>().ToTable("Parents")
        .HasMany(z => z.Child)
        .WithRequired(i => i.Parent); 

Then finally my insert is as follows, I build a new parent-child-grandchild object conditionally based on another input object (I'm trying to save the initial state of a questionnaire based on a similar parent-child-grandchild questionnaire object hierarchy):

public Parent Insert(List<AnotherObject> input)    
        {                   
            Parent parent = new Parent();

            // Set parent attributes
            foreach (var x in input)
            {
                Child child = new Child();
                // Set child attributes
                // EDIT: I also set an attribute based on the list of 
                // entities from the input
                child.OtherObjectId =  x.Id;
                child.Parent = parent;                                

                if (x.Children.Count > 0)
                {
                    foreach (var y in x.Children)
                    {
                        GrandChild grandChild = new GrandChild();
                        // Set grandChild attributes

                        grandChild.Child = child;
                        child.GrandChildren.Add(grandChild);
                    }
                }
                parent.Children.Add(child);
            }

            Context.Parents.Add(parent);
            Context.SaveChanges();
        }

I've checked the DB and entities several times so I'm hoping there's some kind of flaw in my insert logic instead.

EDIT: This is where the input list (selected) comes from in case that helps to determine something:

        Random rand = new Random(DateTime.Now.ToString().GetHashCode());
        var selected = diffParent.DiffChild.OrderBy(x => rand.Next()).Take(diffParent.AmountShown).ToList();
        foreach (var q in selected)
        {
            var listOne = new List<DiffChild>();
            var listTwo = new List<DiffChild>();
            if (q.CountAttribute != null)
                listOne  = q.DiffChild.Where(c => c.Attribute == true).OrderBy(x => rand.Next()).Take((int)q.CountAttribute).ToList();
            if (q.OtherCountAttribute != null)
                listTwo = q.DiffChild.Where(d => d.Attribute != true).OrderBy(y => rand.Next()).Take((int)q.OtherCountAttribute).ToList();
            q.DiffChildren = listOne.Concat(listTwo).ToList();
        }

EDIT: The issue seems to stem from the selected list and to be more specific from the for loop where I try to select specific entities from the full list, if I pass only this:

var selected = diffParent.DiffChild.OrderBy(x => rand.Next()).Take(diffParent.AmountShown).ToList();

The insert seems to work without issues. Seems I've been hunting for issues in the wrong place.

1
You probably made this code for the question, which is OK, but it's not likely that the exception occurs in this exact scenario. Could it be that in your real code, you reassign existing (grand)children to other parents? - Gert Arnold
Thanks for the comment, you're right the code is indeed for presentation. As for the existing part: I generate all the children and grandchildren as new objects and the only time I touch existing entities is when I iterate through the input entities list. - Kim Lindqvist
Could you modify your code to make it more closely resemble the real code. Parts of interest are whether or not input entities are attached to the context and if while setting child properties any reference properties are set. - Gert Arnold
I reference the Id of each entity in the input list and add it as an attribute for each child. I added that to the code listing. The other attributes are just either hardcoded values or then values that come in as input attributes and none of them are linked to any entities. - Kim Lindqvist
parent.Child.Add(child); Should that be parent.Children.Add(child);? Also, why do you do that after the loop, if you did it inside the loop already? child.GrandChild.Add(grandChild); alkso looks like it should be child.GrandChildren.Add(grandChild); Could you double check your code, and make sure it reflects your actual code, before we start hunting errors in the wrong code? - oerkelens

1 Answers

0
votes

True indeed, I knew this was going to be a fixed max 3 depth so I went with this design but I'll keep what you said in mind going forward. On topic I managed to fix this issue by mapping the diffParent to a DTO, then doing the picking and after mapping it back to a list of entities that I passed to the insert method.