Entity Framework TPT 删除基础类型

Entity Framework TPT Removing base type

我的应用程序中有以下模型:

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

这个问题有解决办法吗? 还是没找到

我建议更改 EF 模型设置并设置 .HasOptional(t => t.Folder)。这真是奇怪的 EF 行为。但是当您加载与文件夹相关的字母时,情况有所不同。这里有一篇 link 文章解释了这种差异 Working with Cascade Delete

无论如何 .HasOptional(t => t.Folder) 应该可以解决问题。 而且你不能改变

    public int FolderId {get; set;} 

    public int? FolderId {get; set;}

编辑:

如果 HasOptional(t => t.Folder) 不可接受,你有两个选择:

  1. =============

设置 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();
  1. =============

设置 WillCascadeOnDelete(true)。不要为应该删除的文件夹加载任何字母。删除文件夹。例如:

    var folder = new Folder(){ Id = id };
    Context.Set<Folder>().Attach(folder);
    Context.Set<Folder>().Remove(folder);
    Context.SaveChanges();

编辑2: 我使用相同的 EF 模型 classes (Code First) 创建了一些测试。所有工作都没有错误。我用的是最新的EntityFramework6.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();
    }

编辑3: 我更改文件夹模型 class。

     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。所有旧测试再次成功完成。但是none的新函数还没有被调用。我知道 EF 的行为在最新版本中发生了变化。有可能在 EF 5.* 中是不同的行为。我使用最新的 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();
        }