实体框架硬级联删除

时间:2017-01-25 17:39:18

标签: c# entity-framework sqlite

我有一个使用Entity Framework映射的SQLite数据库。 共有2个表:集合(1:n)专辑。

删除集合时,也必须删除所有相关相册。 我使用CollectionRepo.Delete(collection);来实现这一目标。它使用以下代码:

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

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

  

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

似乎Entity Framework希望在外键上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; }
}

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

2 个答案:

答案 0 :(得分:3)

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

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

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

做两件事:将entityAlbum对象从entity.Albums附加到上下文,将entity标记为Deleted,然后(注意!) Album个对象为Modified。当您调用SaveChanges时,这会导致错误的行为,最后会生成相关的异常。

有两种方法(解决方法)可以解决这种不正确的行为。

第一个是用

替换上面的行
Context.Collections.Attach(entity);
Context.Collections.Remove(entity);

效果类似于上面描述的,导入和差异现在相关的Album对象arte标记为Deleted,这允许成功执行SaveChanges

缺点是现在SaveChanges在删除DELETE的命令之前为每个Album发出Collection命令,这是低效的并且没有多少因为级联删除会在数据库内完美地处理它。

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

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

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

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

选择权在你手中。

答案 1 :(得分:0)

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

此外,您将外键的映射设置为非可空和需要(顺便说一下是冗余的),但在数据库中外键可以为空。 EF假定它因你所说的而无效。

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

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