2017-01-25 156 views
2

我有一个SQLite数据库与实体框架映射。 有2个表格:集合(1:n)专辑。实体框架硬级联删除

当我删除一个集合时,所有相关的专辑也必须被删除。 我使用CollectionRepo.Delete(collection);来实现这一点。它使用下面的代码:

public int Delete(Collection entity) 
{ 
    Context.Entry(entity).State = EntityState.Deleted; 
    return Context.SaveChanges(); 
} 

的问题是:当我执行这个代码,Context.SaveChanges();给我一个例外:

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

看来,实体框架想在外键上null而不是删除条目。但这绝对不是我想要的,因为没有父母(至少在我的用例中),专辑没有意义。

我明显可以手动删除相册,然后删除空集合,但在我看来有点棘手。首先,在我看来,EF应该足够聪明,可以自己来简化代码;其次,如果我与收藏和专辑有很多关系,那么我最终会面临很大的难度,难以维护,代码库。


集合类

public class Collection 
{ 
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 
    public long Id { get; set; } 

    public virtual List<Album> Albums { get; set; } = new List<Album>(); 
} 

Album类

public class Album 
{ 
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 
    public long Id { get; set; } 

    [Required] 
    [ForeignKey("Collection")] 
    public long CollectionId { get; set; } 

    public virtual Collection Collection { get; set; } 
} 

的DbContext子类

public class DataEntities : DbContext 
{ 
    public virtual DbSet<Collection> Collections { get; set; } 
    public virtual DbSet<Album> Albums { get; set; } 

    public DataEntities() : base("name=Connection") 
    { 
     Configuration.ProxyCreationEnabled = false; 
    } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     modelBuilder.Entity<Album>() 
      .HasRequired(a => a.Collection) 
      .WithMany(c => c.Albums) 
      .HasForeignKey(a => a.CollectionId) 
      .WillCascadeOnDelete(true); 
     modelBuilder.Entity<Collection>() 
      .HasMany(c => c.Albums) 
      .WithRequired(a => a.Collection) 
      .WillCascadeOnDelete(true); 
    } 
} 
+0

听起来像它的使用SQLite不支持级联删除HTTP的问题://计算器。 com/questions/10719425/foreign-key-constraint-on-delete-cascade-not-working-in-sqlite-database-on-andro –

+0

3.14.2(System.Data.SQLite 1.0.103)。 它似乎并没有与SQLite恕我直言,因为我的表中的外键允许为空,加上异常堆栈跟踪最深的调用是'System.Data.Entity.Core.Objects.ObjectContext.PrepareToSaveChanges(SaveOptions选项) '这似乎还没有与SQLite相关。 –

+0

您在映射[必需] [ForeignKey]中将其指定为非空,这就是为什么EF令人困惑。 –

回答

1

在EF中应用分离的对象图修改一直不清楚。这是失败的原因之一。

假设Collection实体传递给Delete方法有Albums集合填充(至少这是我如何能够重现异常)。该生产线

Context.Entry(entity).State = EntityState.Deleted; 

做了两两件事:重视entity和所有Albumentity.Albums对象的背景,标志entityDeleted,和(注意!)的Album对象作为Modified。当您调用SaveChanges时,这会导致错误行为,并在最后生成有问题的异常。

解决此错误行为有两种方法(解决方法)。

第一个是与

Context.Collections.Attach(entity); 
Context.Collections.Remove(entity); 

的效果类似于以上所描述的,用的三个重要区别现在相关Album对象ARTE标记为Deleted,这允许成功地执行,以取代上面的行SaveChanges

的缺点是,现在SaveChanges用于删除Collection,这是低效的,并没有太大的意义,因为级联删除会处理,完美的数据库内发出对每个Album一个DELETE命令的命令之前。

第二个选项是附接实体之前保持代码原样,但清除相关的集合:

entity.Albums = null; 
Context.Entry(entity).State = EntityState.Deleted; 

这允许成功执行SaveChanges,而且只为实体生成单个DELETE命令。

缺点是您需要编写额外的代码,并且不要忘记任何支持级联删除的子集合,并且不需要为需要级联更新的集合(即具有可选关系,需要使用NULL更新FK字段)执行此操作。

选择是你的。

+0

它的工作原理,谢谢你的详细解释,即使这听起来对我来说非常不合逻辑/反直觉(EF,不是你的解释)。如果WillCascadeOnDelete(true)没有自然处理级联删除,那么它的作用是什么?你确定你的第一个解决方法是为每个'Album'使用'delete'命令而不是批处理'delete',就像'DELETE FROM Albums WHERE CollectionId = xxx'? –

+0

是的,我在回答之前对它进行了测试。它执行N个命令,如'DELETE FROM Albums WHERE Id = @ 0'。最新的EF6.1.3,SqlServer数据库。“WillCascadeOnDelete”的好处是什么?它用于在数据库中创建相应的约束。 –

+0

但我不认为数据库很重要,因为这似乎是由EF基础架构执行的,而数据库提供者只是生成相应的SQL命令。 –

0

根据您的意见,您正在映射到预先存在的数据库(EF没有生成它)。 CascadeOnDelete只影响数据库的生成。如果数据库没有在表上配置CascadeOnDelete,那么当EF尝试删除并且Sqlite不符合时,EF会感到困惑。

此外,您还将外键映射为不可为空且必需(冗余顺序),但在数据库中外键可为空。因为你说了什么,EF认为它是无效的。

如果您修复了您的映射(从CollectionID属性中删除所需的注释并将其类型更改为int?而不是int,那么您应该修复问题实际上,将DbContext类中的映射从HasRequired更改为HasOptional ... 。?从那里它应该工作

要么或更改数据库本身的表定义的sqlite的版本,你使用的是哪个

+0

我只在数据库中允许空值用于调试目的。现在我把它放回'NOT NULL'并且FK是'ON DELETE CASCADE'。我不想将我的映射从HasRequired更改为HasOptional,因为在我的设计中,一张专辑总是与一个集合关联(用户界面显示集合,然后显示专辑,所以如果专辑没有关联到集合,它会永远不会显示)。 –

+0

所以,你把它放回不为空,并删除级联做到这一点解决了这个问题? –

+0

不,这是最初的状态,在我改变之前确定它与DB无关。 Ivan Stoev解决了我的两个问题:首先提出了解决方案中的两个解决方法,然后指出DbContext应始终是唯一的。 –