C# 实体框架硬级联删除

C# 实体框架硬级联删除,c#,entity-framework,sqlite,C#,Entity Framework,Sqlite,我有一个用实体框架映射的SQLite数据库。 有两个表:集合(1:n)相册 当我删除收藏时,所有相关的相册也必须删除。 我使用CollectionRepo.Delete(collection)来实现这一点。它使用以下代码: public int Delete(Collection entity) { Context.Entry(entity).State = EntityState.Deleted; return Context.SaveChanges(); } 问题是:当我执

我有一个用实体框架映射的SQLite数据库。 有两个表:集合(1:n)相册

当我删除收藏时,所有相关的相册也必须删除。 我使用
CollectionRepo.Delete(collection)来实现这一点。它使用以下代码:

public int Delete(Collection entity)
{
    Context.Entry(entity).State = EntityState.Deleted;
    return Context.SaveChanges();
}
问题是:当我执行这段代码时,
Context.SaveChanges()给我一个例外:

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

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

显然,我可以先手动删除相册,然后删除空的收藏,但在我看来这有点棘手。首先,在我看来,EF应该足够聪明,能够独立完成这项工作,以简化代码;其次,如果我与收藏和专辑有几十种关系,我最终会拥有一个相当大、难以维护的代码库


收集类

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

    public virtual List<Album> Albums { get; set; } = new List<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; }
}
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);
    }
}
DbContext子类

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

    public virtual List<Album> Albums { get; set; } = new List<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; }
}
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);
    }
}
公共类数据实体:DbContext
{
公共虚拟数据库集集合{get;set;}
公共虚拟数据库集相册{get;set;}
公共数据实体():基(“名称=连接”)
{
Configuration.ProxyCreationEnabled=false;
}
模型创建时受保护的覆盖无效(DbModelBuilder modelBuilder)
{
modelBuilder.Entity()
.HasRequired(a=>a.Collection)
.WithMany(c=>c.Albums)
.HasForeignKey(a=>a.CollectionId)
.WillCascadeOnDelete(真);
modelBuilder.Entity()
.HasMany(c=>c.Albums)
.WithRequired(a=>a.Collection)
.WillCascadeOnDelete(真);
}
}

应用分离对象图修改在EF中一直不清楚。这是其中一种没有充分理由就失败的情况

假设传递给
Delete
方法的
Collection
实体已填充
Albums
集合(至少这是我能够重现异常的方式)。线路

Context.Entry(entity).State = EntityState.Deleted;
执行两项操作:将
实体
实体中的所有
相册
对象附加到上下文中,将
实体
标记为
已删除
,以及(注意!)将
相册
对象标记为
已修改
。当您调用
SaveChanges
时,这会导致不正确的行为,并在最后生成有问题的异常

有两种方法(变通方法)可以修复此错误行为

第一个是将上面的行替换为

Context.Collections.Attach(entity);
Context.Collections.Remove(entity);
其效果与上述类似,重要和不同之处在于,现在相关的
相册
对象被标记为
已删除
,这允许成功执行
保存更改

缺点是,现在
SaveChanges
在删除
集合
的命令之前,为每个
相册
发出一个
DELETE
命令,这效率很低,没有多大意义,因为级联删除可以在数据库中完美地处理这一点

第二个选项是保持代码不变,但在附加实体之前清除相关集合:

entity.Albums = null;
Context.Entry(entity).State = EntityState.Deleted;
这允许成功执行
SaveChanges
,并仅为实体生成一个
DELETE
命令

缺点是,您需要编写额外的代码,并且不要忘记任何支持级联删除的子集合,对于需要级联更新的集合(即,使用需要使用
NULL
更新FK字段的可选关系),也不要这样做


您可以选择。

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

此外,外键的映射是不可空的和必需的(顺便说一句,这是多余的),但在数据库中,外键是可空的。EF假设它是无效的,因为你告诉了它什么

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


或者更改数据库本身的表定义。

您使用的是哪个版本的sqlite?听起来sqlite不支持cascade delete 3.14.2(System.Data.sqlite 1.0.103)是个问题。它似乎与SQLite IMHO无关,因为我的表中的外键允许null,加上异常堆栈跟踪调用是
System.Data.Entity.Core.Objects.ObjectContext.PrepareToSaveChanges(SaveOptions选项)
which,它似乎还与SQLite无关。您在映射中将它指定为不可为null[必需][ForeignKey]这就是为什么EF会感到困惑。它很有效,感谢您的详细解释,即使我觉得它听起来很不合逻辑/违反直觉(EF,不是您的解释)。WillCascadeOnDelete的作用是什么(真)
如果它不能自然处理级联删除?您确定您的第一个解决方法是为每个
相册
使用
delete
命令,而不是批量
delete
,例如