将Cascade Delete添加到EntityFramework会导致Invalid Cast异常更改为未更改的代码

时间:2017-07-27 08:34:49

标签: c# entity-framework

我使用我的实体框架代码优先定义了以下类;

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; }
}

我的DataContext中引用了这些内容,如下所示;

public virtual DbSet<Parent> Parents{ get; set; }
public virtual DbSet<Child1> Child1 { get; set; }
public virtual DbSet<Child2> Child2 { get; set; }

通过重写OnModelCreating并添加,我已经禁用了Cascade Delete的所有内容;

modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

此时,一切运作良好。我使用以下内容发布数据库并进行测试;

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();

查看数据库,我可以看到记录,并且一切正确。

如果我然后将以下内容添加到相同的代码中;

context.Monitors.Remove(monitor);
context.SaveChanges();

我得到了一个错误,正如我所料;

操作失败:无法更改关系,因为一个或多个外键属性不可为空。当对关系进行更改时,相关的外键属性将设置为空值。如果外键不支持空值,则必须定义新关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象。

此时我查看为这些特定类启用了Cascade Delete,将我项目中的所有其他类保留为显式删除。我将以下内容添加到我的datacontext中;

modelBuilder.Entity<Parent>()
    .HasOptional(p => p.Child1)
    .WithMany()
    .WillCascadeOnDelete(true);

modelBuilder.Entity<Parent>()
    .HasOptional(p => p.Child2)
    .WithMany()
    .WillCascadeOnDelete(true);

我重置了我的迁移,添加了初始迁移,删除并完全发布了数据库。

我可以看到以下内容添加到我的初始MIgration中;

.ForeignKey("Parent.Child1", t => t.Child1_Id, cascadeDelete: true)
.ForeignKey("Parent.Child2", t => t.Child2_Id, cascadeDelete: true)

现在,当我运行与上面相同的代码来添加记录时,我现在得到以下错误;

  

发生System.InvalidCastException HResult = 0x80004002
  Message =无法转换类型的对象   “System.Collections.Generic.HashSet 1[Child1]' to type 'Child1'.
Source=EntityFramework StackTrace: at System.Data.Entity.Core.Objects.DataClasses.EntityReference
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()   在   System.Data.Entity.Core.Objects.Internal.EntityWrapperWithoutRelationships 1.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&LT;&GT; c__DisplayClassd.b__c()   at System.Data.Entity.Internal.Linq.InternalSet 1.ActOnSet(Action action, EntityState newState, Object entity, String methodName) at System.Data.Entity.Internal.Linq.InternalSet 1.Add(Object entity)
  在System.Data.Entity.DbSet`1.Add(TEntity实体)at   Kinexus.Tools.TestApplication.Program.Main(String [] args)in   d:\个人\ Kinexus_TFS \工具箱\ Kinexus.Tools \ Kinexus.Tools.TestApplication \的Program.cs:行   180

错误发生在这行代码中,但没有改变;

context.Monitors.Add(monitor);

我得到错误所说的内容,但我很难理解为什么以及如何解决。

1 个答案:

答案 0 :(得分:2)

fluent配置与您的模型不匹配,这会导致EF假设其他关系,从而创建其他FK列。

为了使用流畅的API生成预期的关系映射,使用描述导航和/或FK属性的存在/不存在的正确重载是至关重要的。在您的情况下,无参数WithMany()调用告诉EF该关系是单向(即没有集合导航属性),因此当EF发现未映射的集合导航时属性,它创建另一个单向关系,这次没有引用导航属性,默认FK列名称为EntityName_Id

同时查看您的映射,您完全颠倒了引用和集合属性配置。 Has/WithOptional应在Has/WithMany - 集合时指定引用。由于FK不可为空,因此您应使用Has/WithRequired代替Has/WithOptional

话虽如此,解决问题只需应用正确的映射:

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);