I have the following classes defined with my Entity Framework Code First;
public class Parent: Entity<int>
{
public Guid Reference { get; set; } = Guid.NewGuid();
public int UserId { get; set; }
public virtual ICollection<Alert> Child1 { get; set; } = new HashSet<Child1>();
public virtual ICollection<History> Child2 { get; set; } = new HashSet<Child2>();
}
public class Child1: Entity<int>
{
public int ParentId { get; set; }
public Parent Parent { get; set; }
public DateTime Date { get; set; }
}
public class Child2 : Entity<int>
{
public int ParentId{ get; set; }
public Parent Parent { get; set; }
public string Title { get; set; }
}
These are referenced in my DataContext
as follows;
public virtual DbSet<Parent> Parents{ get; set; }
public virtual DbSet<Child1> Child1 { get; set; }
public virtual DbSet<Child2> Child2 { get; set; }
I have disabled Cascade Delete for everything explicitly, by overriding OnModelCreating and adding;
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
At this point, all works well. I publish my database and test using the following;
var parent = new Parent {UserId = 1};
parent.Child1.Add(new Child1 { Date = DateTime.Now});
parent.Child1.Add(new Child1 { Date = DateTime.Now});
parent.Child1.Add(new Child1 { Date = DateTime.Now});
parent.Child2.Add(new Child2 { Title = "Title" });
parent.Child2.Add(new Child2 { Title = "Title" });
parent.Child2.Add(new Child2 { Title = "Title" });
parent.Child2.Add(new Child2 { Title = "Title" });
context.Monitors.Add(monitor);
context.SaveChanges();
Looking into the database, I can see the records and all correct.
If I then add the following to the same code;
context.Monitors.Remove(monitor);
context.SaveChanges();
I get an error, as I would expect;
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.
At this point I looked to enabled Cascade Delete for these specific classes, leaving all the others in my project as explicit delete. I added the following to my datacontext;
modelBuilder.Entity<Parent>()
.HasOptional(p => p.Child1)
.WithMany()
.WillCascadeOnDelete(true);
modelBuilder.Entity<Parent>()
.HasOptional(p => p.Child2)
.WithMany()
.WillCascadeOnDelete(true);
I reset my migrations, added an initial migration, deleted and published the database entirely at this point.
I can see the following added to my Initial MIgration;
.ForeignKey("Parent.Child1", t => t.Child1_Id, cascadeDelete: true)
.ForeignKey("Parent.Child2", t => t.Child2_Id, cascadeDelete: true)
Now, when I run the same code as above to add records, I now get the following error;
System.InvalidCastException occurred HResult=0x80004002
Message=Unable to cast object of type 'System.Collections.Generic.HashSet1[Child1]' to type 'Child1'.
1.AddToLocalCache(IEntityWrapper wrappedEntity, Boolean applyConstraints) at System.Data.Entity.Core.Objects.EntityEntry.TakeSnapshotOfSingleRelationship(RelatedEnd relatedEnd, NavigationProperty n, Object o) at System.Data.Entity.Core.Objects.EntityEntry.TakeSnapshotOfRelationships() at System.Data.Entity.Core.Objects.Internal.EntityWrapperWithoutRelationships
Source=EntityFramework StackTrace: at System.Data.Entity.Core.Objects.DataClasses.EntityReference1.TakeSnapshotOfRelationships(EntityEntry entry) at System.Data.Entity.Core.Objects.ObjectContext.AddSingleObject(EntitySet entitySet, IEntityWrapper wrappedEntity, String argumentName) at System.Data.Entity.Core.Objects.ObjectContext.AddObject(String entitySetName, Object entity) at System.Data.Entity.Internal.Linq.InternalSet
1.<>c__DisplayClassd.b__c() at System.Data.Entity.Internal.Linq.InternalSet1.ActOnSet(Action action, EntityState newState, Object entity, String methodName) at System.Data.Entity.Internal.Linq.InternalSet
1.Add(Object entity)
at System.Data.Entity.DbSet`1.Add(TEntity entity) at Kinexus.Tools.TestApplication.Program.Main(String[] args) in D:\Personal\Kinexus_TFS\Toolset\Kinexus.Tools\Kinexus.Tools.TestApplication\Program.cs:line 180
The error occurs at this line of code, which has not changed;
context.Monitors.Add(monitor);
I get what the error is saying, but I am struggling to understand why and what to do to resolve.
The fluent configuration does not match your model, which is causing EF to assume additional relationships, hence to create additional FK columns.
In order to produce the intended relationship mappings with fluent API, it's crucial to use the correct overloads which describe the presence/absence of the navigation and/or FK property. In your case, the parameterless WithMany()
calls are telling EF that the relationship is unidirectional (i.e. without collection navigation property), hence when EF discovers the unmapped collection navigation properties, it creates another unidirectional relationship, this time without reference navigation property and with default FK column name EntityName_Id
.
Also looking at your mappings, you've totally reversed the reference and collection property configuration. Has/WithOptional
should specify the reference while Has/WithMany
- the collection. And since the FK is not nullable, you should use Has/WithRequired
instead of Has/WithOptional
.
With that being said, to fix the issue simply apply the correct mappings:
modelBuilder.Entity<Parent>()
.HasMany(p => p.Child1)
.WithRequired(c => c.Parent)
.HasForeignKey(c => c.ParentId)
.WillCascadeOnDelete(true);
modelBuilder.Entity<Parent>()
.HasMany(p => p.Child2)
.WithRequired(c => c.Parent)
.HasForeignKey(c => c.ParentId)
.WillCascadeOnDelete(true);
User contributions licensed under CC BY-SA 3.0