简体   繁体   English

实体框架硬级联删除

[英]Entity Framework hard cascade delete

I have a SQLite DB mapped with Entity Framework. 我有一个与实体框架映射的SQLite数据库。 There are 2 tables : Collections (1:n) Albums. 有2个表:收藏(1:n)相册。

When I delete a collection, all related albums have to be deleted as well. 删除收藏集时,所有相关相册也必须删除。 I use CollectionRepo.Delete(collection); 我使用CollectionRepo.Delete(collection); to achieve that. 实现这一目标。 It uses the following code : 它使用以下代码:

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

The problem is: when I execute this code, Context.SaveChanges(); 问题是:执行此代码时, Context.SaveChanges(); give me an exception: 给我一个例外:

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. 如果外键不支持空值,则必须定义新的关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象。

It seems that Entity Framework wants to null on the foreign keys instead of deleting the entries. 似乎Entity Framework希望对外键设置为null ,而不是删除条目。 But this is absolutely not what I want because an album makes no sense without a parent (in my use case at least). 但这绝对不是我想要的,因为没有父母(至少在我的用例中),专辑毫无意义。

I could obviously manualy delete the albums first and then delete the empty collection but it seems to me a bit tricky. 很明显,我可以先手动删除相册,然后再删除空的收藏集,但在我看来有点棘手。 First, it seems to me that EF should be smart enough to do it on it's own to simplify the code and second, what if I have dozens of relations to collections and albums, I would end up with quite a big, hard to maintain, code base. 首先,在我看来,EF应该足够聪明才能独自完成,以简化代码;其次,如果我与唱片集和专辑有数十种关系,最终我会变得很大,难以维护,代码库。


Collection Class 收藏类

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

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

Album class 专辑类

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

Applying detached object graph modifications has always been unclear in EF. 在EF中,应用分离的对象图修改始终不清楚。 This is one of the cases where it fails without a good reason. 这是没有充分理由而失败的情况之一。

Assuming the Collection entity passed to the Delete method has Albums collection populated (at least this is how I was able to reproduce the exception). 假设传递给Delete方法的Collection实体已填充Albums集合(至少这是我能够重现异常的方式)。 The line 线

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

does two things: attaches entity and all Album objects from the entity.Albums to the context, marks entity as Deleted , and (note!) the Album objects as Modified . 做了两两件事:重视entity和所有Album从对象entity.Albums上下文,标记entityDeleted ,和(注意!)的Album对象作为Modified This leads to incorrect behavior when you call SaveChanges , and at the end generates the exception in question. 当您调用SaveChanges ,这会导致错误的行为,并最终生成所讨论的异常。

There are two ways (workarounds) to fix this incorrect behavior. 有两种方法(解决方法)来修复此错误行为。

The first one is to replace the above line with 第一个是将以上行替换为

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

The effect is similar to the described above, with the importand difference that now the related Album objects arte marked as Deleted , which allows successfully executing the SaveChanges . 效果与上述类似,不同之处在于现在将相关的Album对象标记为Deleted ,从而可以成功执行SaveChanges

The drawback is that now the SaveChanges issues a DELETE command for each Album before the command for deleting the Collection , which is inefficient and doesn't make much sense since the cascade delete would handle that perfectly inside the database. 缺点是,现在SaveChanges在删除Collection的命令之前为每个Album发出DELETE命令,这效率低下并且没有多大意义,因为级联删除可以完美地在数据库内部进行处理。

The second option is to keep the code as is, but clear the related collection before attaching the entity: 第二种选择是保持代码不变,但是在附加实体之前清除相关集合:

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

This allows successfully executing SaveChanges and it generates a single DELETE command only for the entity. 这样可以成功执行SaveChanges并且仅为实体生成单个DELETE命令。

The drawback is that you need to write additional code and not forget any child collection which supports cascade delete, and not doing that for collections that need cascade update (ie with optional relation which requires updating the FK field with NULL ). 缺点是您需要编写其他代码,并且不要忘记任何支持级联删除的子集合,并且不要对需要级联更新的集合执行此操作(即具有需要更新FK字段为NULL可选关系)。

The choice is yours. 这是你的选择。

Per your comments, you're mapping to a pre-existing database (EF did not generate it). 根据您的评论,您正在映射到一个预先存在的数据库(EF不会生成它)。 CascadeOnDelete only affects the generation of the database. CascadeOnDelete仅影响数据库的生成。 If the database doesn't have CascadeOnDelete configured on the table, then EF will be confused when it attempts to delete and Sqlite doesn't comply. 如果数据库未在表上配置CascadeOnDelete,则EF在尝试删除且Sqlite不符合要求时会感到困惑。

Also you have the mapping for the foreign key as non-nullable and required (redundant by the way) but in the database the foreign key is nullable. 同样,您将外键的映射设置为不可为空并且是必需的(顺便说一下是冗余的),但是在数据库中外键是可为空的。 EF assumes that it's not valid because of what you told it. EF假定由于您所说的原因它无效。

If you fix your mapping (remove the required annotation from the CollectionID property and change its type to int? instead of just int you should fix your problem. Actually change the mapping in the DbContext class from HasRequired to HasOptional...from there it should work. 如果您修复了映射(从CollectionID属性中删除所需的批注,然后将其类型更改为int?而不是int,则应该解决您的问题。实际上,将DbContext类中的映射从HasRequired更改为HasOptional ...从那里应该工作。

Either that or change the table definitions on your database itself. 要么更改数据库本身上的表定义,要么更改数据库定义。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM