我的应用程序中有以下模型:
public interface IBaseEntityObject
{
public int Id {get; set;}
}
public abstract class BaseEntityObject : IBaseEntityObject
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
}
public class Folder : BaseEntityObject
{
[DataMember]
public string Name {get; set;}
[DataMember]
public virtual List<Letter> Letters {get; set;}
}
public abstract class Letter : BaseEntityObject
{
[DataMember]
public string Title {get; set;}
[DataMember]
public string Content {get; set;}
public virtual Folder Folder {get; set;}
[DataMember]
public int FolderId {get; set;}
[DataMember]
public DateTime CreationDate {get; set;}
}
public class OutgoingLetter : Letter
{
// .. OutgoingLetter properties
}
public class ReceviedLetter : Letter
{
// .. ReceviedLetter properties
}
public class MyDbContext : DbContext
{
public DbSet<Folder> Folders {get; set;}
public DbSet<Letter> Letters {get; set;}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Folder <-> Letters
modelBuilder.Entity<Letter>()
.HasRequired(t => t.Folder)
.WithMany(f => f.Letters)
.HasForeignKey(t => t.FolderId)
.WillCascadeOnDelete(true);
}
}
但是,无论我尝试什么,删除文件夹都会导致访问密钥违规。
之后,我尝试删除文件夹中的所有字母,然后删除它(我不喜欢) 它导致了另一个异常 - 非可空成员的空值。
删除文件夹的正确和最有效的方法是什么?
修改:
我尝试删除文件夹的代码:
public abstract class EFRepository<T> : IRepository<T>
{
protected readonly DbContext Context;
public EFRepository(DbContext context)
{
Context = context;
}
public abstract List<T> Get();
public void Add(T item)
{
Context.Set<T>().Add(item);
}
public virtual Remove(T item)
{
Context.Set<T>().Remove(item);
}
public void Update(T item)
{
Context.Entry(item).State = EntityState.Modified;
}
public void Dispose()
{
Context.Dispose();
}
public int SaveChanges()
{
return Context.SaveChanges();
}
public T FindById(int id)
{
return Context.Set<T>().Find(id);
}
}
public FoldersRepository : EFRepository<Folder>
{
public FoldersRepository(DbContext context) : base(context) {}
public void override Remove(Folder folder)
{
var letters = folder.Letters;
for (int index = 0 ; index < letters.Count; index++)
{
Context.Set<Letter>().Remove(letters[0]);
}
base.Remove(folder);
}
}
这个问题有解决方法吗? 仍然找不到一个
答案 0 :(得分:1)
我建议更改EF模型设置并设置.HasOptional(t =&gt; t.Folder)。 EF行为真的很奇怪。但是当您加载与文件夹相关的字母时,情况之间存在差异。这是指向解释此差异Working with Cascade Delete的文章的链接。
无论如何.HasOptional(t =&gt; t.Folder)应该解决问题。 你无法改变
public int FolderId {get; set;}
到
public int? FolderId {get; set;}
修改强>
如果HasOptional(t =&gt; t.Folder)不支持,您有两种选择:
设置WillCascadeOnDelete( false )。加载所有文件夹的字母,删除所有字母和文件夹。对于考试
var folder = Context.Set<Folder>().Include(f => f.Letters).First(f => f.Id==id);
foreach(var letter in folder.Letters)
Context.Set<Letter>().Remove(letter);
Context.Set<Folder>().Remove(folder);
Context.SaveChanges();
设置WillCascadeOnDelete( true )。不要为要删除的文件夹加载任何字母。删除文件夹。例如:
var folder = new Folder(){ Id = id };
Context.Set<Folder>().Attach(folder);
Context.Set<Folder>().Remove(folder);
Context.SaveChanges();
<强> EDIT2:强> 我使用相同的EF模型类(Code First)创建了一些测试。一切正常,没有错误。我使用最新的EntityFramework 6.1.3版本。 以下是测试的源代码:
public class Folder {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public List<Letter> Letters { get; set; }
}
public class Letter {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public virtual Folder Folder { get; set; }
public int FolderId { get; set; }
public DateTime CreationDate { get; set; }
}
public class OutgoingLetter : Letter {
public string AddressTo { get; set; }
}
public class ReceviedLetter : Letter {
public string AddressFrom { get; set; }
}
public class MyDbContext : DbContext {
public virtual DbSet<Folder> Folders { get; set; }
public virtual DbSet<Letter> Letters { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Folder <-> Letters
modelBuilder.Entity<Letter>()
.HasRequired(t => t.Folder)
.WithMany(f => f.Letters)
.HasForeignKey(t => t.FolderId)
.WillCascadeOnDelete(true);
}
}
// ...........................................
// TODO: Insert three Folders and related Letters.
// Delete Folders and Leterrs in a three different ways.
// In all cases Letters deleted throught WillCascadeOnDelete constraint.
static void Main(string[] args)
{
using (var dtCntx = new MyDbContext())
{
var folder1 = new Folder() { Name = "Folder1" };
var letters1 = new List<Letter>() {
new OutgoingLetter{Title = "Folder1-Letter1", CreationDate=DateTime.Now, Folder=folder1 },
new ReceviedLetter{Title = "Folder1-Letter2", CreationDate=DateTime.Now, Folder=folder1 }
};
var folder2 = new Folder() { Name = "Folder2" };
var letters2 = new List<Letter>() {
new OutgoingLetter{Title = "Folder2-Letter1", CreationDate=DateTime.Now, Folder=folder2 },
new ReceviedLetter{Title = "Folder2-Letter2", CreationDate=DateTime.Now, Folder=folder2 }
};
var folder3 = new Folder() { Name = "Folder3" };
var letters3 = new List<Letter>() {
new OutgoingLetter{Title = "Folder3-Letter1", CreationDate=DateTime.Now, Folder=folder3 },
new ReceviedLetter{Title = "Folder3-Letter2", CreationDate=DateTime.Now, Folder=folder3 }
};
dtCntx.Folders.Add(folder1);
dtCntx.Letters.AddRange(letters1);
dtCntx.Folders.Add(folder2);
dtCntx.Letters.AddRange(letters2);
dtCntx.Folders.Add(folder3);
dtCntx.Letters.AddRange(letters3);
dtCntx.SaveChanges();
}
int id = 0;
using (var dtCntx = new MyDbContext())
id = dtCntx.Folders.First().Id;
// Remove [Folder] and related [Letters] without loading [Folder] from DB
using (var dtCntx = new MyDbContext())
{
var folder = new Folder { Id = id };
dtCntx.Folders.Attach(folder);
dtCntx.Folders.Remove(folder);
dtCntx.SaveChanges();
}
// Load [Folder] from DB and delete it
using (var dtCntx = new MyDbContext())
{
var folder = dtCntx.Folders.FirstOrDefault();
dtCntx.Folders.Remove(folder);
dtCntx.SaveChanges();
}
// Load [Folder] and all related [Letters]. Delete [Folder] and [Letters]
using (var dtCntx = new MyDbContext())
{
var folder = dtCntx.Folders.Include(f => f.Letters).FirstOrDefault();
dtCntx.Folders.Remove(folder);
dtCntx.SaveChanges();
}
Console.WriteLine("Successfully !!!");
Console.ReadKey();
}
<强> EDIT3:强> 我改变了文件夹模型类。
public class Folder
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Letter> Letters { get; set; }
public override bool Equals(object obj)
{
var folder = obj as Folder;
return folder.Id == this.Id && folder.Name == this.Name &&
folder.Letters.SequenceEqual(this.Letters);
}
public override int GetHashCode()
{
return String.Concat(GetHashParts()).GetHashCode();
}
private IEnumerable<string> GetHashParts()
{
yield return Id.ToString();
yield return Name;
foreach (var letter in Letters) {
yield return "_";
yield return letter.Id.ToString();
}
}
public static bool operator ==(Folder x, Folder y)
{
return x.Equals(y);
}
public static bool operator !=(Folder x, Folder y)
{
return !x.Equals(y);
}
}
我重写了Equals,== opeartor和GetHashCode。所有旧测试再次成功完成。但是没有调用任何新函数。我知道EF的行为在最新版本中发生了变化。在EF 5. *中可能是不同的行为。我使用lates EF 6.1.3。我稍微更改了测试并添加了一个代码以引起Equals函数调用。但代码可以再次正常工作。
using (var dtCntx = new MyDbContext())
{
var folder = dtCntx.Folders.Include(f => f.Letters).FirstOrDefault();
var folder2 = dtCntx.Folders.FirstOrDefault(f => f.Id == folder.Id);
if (folder == folder2) Console.WriteLine("Equals"); // Call Folder.Equals
dtCntx.Folders.Remove(folder);
dtCntx.SaveChanges();
}