C# 实体框架硬级联删除
我有一个用实体框架映射的SQLite数据库。 有两个表:集合(1:n)相册 当我删除收藏时,所有相关的相册也必须删除。 我使用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(); } 问题是:当我执
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
,例如