8

Tôi có ứng dụng .NET4.0 với Entity Framework 5.0 e Sql Server CE 4.0.Xóa cha mẹ có con trong mối quan hệ một đến nhiều

Tôi có hai thực thể có mối quan hệ một đến nhiều (cha/con). Tôi đã cấu hình nó để cascade xóa trên cha mẹ loại bỏ, nhưng đối với một số lý do nó không có vẻ làm việc.

Đây là một phiên bản đơn giản hóa của các đơn vị của tôi:

public class Account 
    { 
     public int AccountKey { get; set; } 
     public string Name { get; set; } 

     public ICollection<User> Users { get; set; } 
    } 

    internal class AccountMap : EntityTypeConfiguration<Account> 
    { 
     public AccountMap() 
     { 
      this.HasKey(e => e.AccountKey); 
      this.Property(e => e.AccountKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 
      this.Property(e => e.Name).IsRequired(); 
     } 
    } 


    public class User 
    { 
     public int UserKey { get; set; } 
     public string Name { get; set; } 

     public Account Account { get; set; } 
     public int AccountKey { get; set; } 
    } 

    internal class UserMap : EntityTypeConfiguration<User> 
    { 
     public UserMap() 
     { 
      this.HasKey(e => e.UserKey); 
      this.Property(e => e.UserKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 
      this.Property(e => e.Name).IsRequired(); 


      this.HasRequired(e => e.Account) 
       .WithMany(e => e.Users) 
       .HasForeignKey(e => e.AccountKey); 
     } 
    } 

    public class TestContext : DbContext 
    { 
     public TestContext() 
     { 
      this.Configuration.LazyLoadingEnabled = false; 
     } 

     public DbSet<User> Users { get; set; } 
     public DbSet<Account> Accounts { get; set; } 

     protected override void OnModelCreating(DbModelBuilder modelBuilder) 
     { 
      modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); modelBuilder.Conventions.Remove<StoreGeneratedIdentityKeyConvention>(); 
      modelBuilder.LoadConfigurations(); 
     } 

    } 

Chuỗi kết nối:

<connectionStrings> 
    <add name="TestContext" connectionString="Data Source=|DataDirectory|\TestDb.sdf;" providerName="System.Data.SqlServerCe.4.0" /> 
    </connectionStrings> 

Và một phiên bản đơn giản của việc ứng dụng của tôi:

static void Main(string[] args) 
{ 
    try 
    { 
     Database.SetInitializer(new DropCreateDatabaseAlways<TestContext>()); 
     using (var context = new TestContext()) 
      context.Database.Initialize(false); 

     Account account = null; 
     using (var context = new TestContext()) 
     { 
      var account1 = new Account() { Name = "Account1^" }; 
      var user1 = new User() { Name = "User1", Account = account1 }; 

      context.Accounts.Add(account1); 
      context.Users.Add(user1); 

      context.SaveChanges(); 

      account = account1; 
     } 

     using (var context = new TestContext()) 
     { 
      context.Entry(account).State = EntityState.Deleted; 
        context.SaveChanges(); 
     } 
    } 
    catch (Exception e) 
    { 
     Console.WriteLine(e.ToString()); 
    } 

    Console.WriteLine("\nPress any key to exit..."); 
    Console.ReadLine(); 
} 

Khi tôi cố gắng để xóa đối tượng gốc, nó ném:

Không thể thay đổi mối quan hệ vì một hoặc nhiều thuộc tính ngoại khóa không thể vô hiệu. Khi thay đổi được thực hiện cho mối quan hệ , thuộc tính khóa ngoài có liên quan được đặt thành giá trị null. Nếu khóa ngoài không hỗ trợ giá trị null, mối quan hệ mới phải được xác định, thuộc tính khóa ngoài phải được gán giá trị không null khác hoặc đối tượng không liên quan phải bị xóa.

Tôi tin rằng cấu hình mối quan hệ của tôi là ok (followed the documentation). Tôi cũng đã tìm kiếm guidelines on deleting detached entities.

Tôi thực sự không thể hiểu tại sao thao tác xóa đó không hoạt động. Tôi muốn tránh tải tất cả các trẻ em, xóa từng người một và họ xóa phụ huynh, bởi vì phải có một giải pháp tốt hơn thế.

Trả lời

12

Đặt trạng thái của một thực thể thành Deleted và gọi DbSet<T>.Remove cho đối tượng này không giống nhau.

Sự khác biệt là thiết lập nhà nước chỉ thay đổi trạng thái của đối tượng gốc (một trong những bạn vượt qua thành context.Entry) để Deleted nhưng không phải là nhà nước của các đơn vị có liên quan trong khi Remove thực hiện điều này nếu mối quan hệ được cấu hình với tầng xóa.

Nếu bạn nhận được một ngoại lệ thực sự phụ thuộc vào trẻ em (tất cả hoặc chỉ một phần) được gắn vào ngữ cảnh hay không. Điều này dẫn đến hành vi có phần khó thực hiện:

  • Nếu bạn gọi Remove bạn không có ngoại lệ, cho dù trẻ em có được tải hay không.Hiện vẫn còn một sự khác biệt:
    • Nếu trẻ em được gắn liền với bối cảnh, EF sẽ tạo ra một tuyên bố DELETE cho mọi trẻ em đính kèm, sau đó cho phụ huynh (vì Remove đã đánh dấu tất cả như Deleted)
    • Nếu trẻ em không được gắn với bối cảnh EF sẽ chỉ gửi một tuyên bố DELETE cho phụ huynh vào cơ sở dữ liệu và vì xóa tầng được kích hoạt cơ sở dữ liệu cũng sẽ xóa các trẻ em.
  • Nếu bạn đặt trạng thái của thực thể gốc để Deleted bạn có thể thể nhận được một ngoại lệ:
    • Nếu trẻ em được gắn liền với bối cảnh tình trạng của họ sẽ không được thiết lập để Deleted và EF sẽ phàn nàn rằng bạn đang cố gắng xóa một hiệu trưởng (thực thể gốc) trong một mối quan hệ yêu cầu mà không xóa người phụ thuộc (trẻ em) hoặc ít nhất là không đặt khóa ngoại của họ cho một thực thể gốc khác không ở trạng thái Deleted. Đó là ngoại lệ bạn có: account là gốc rễ và user1 là một phụ thuộc của account và gọi context.Entry(account).State = EntityState.Deleted; cũng sẽ đính kèm user1 trong tiểu bang Unchanged với bối cảnh (hoặc thay đổi phát hiện trong SaveChanges sẽ làm điều đó, tôi không chắc chắn giáp đó). user1 là một phần của bộ sưu tập account.Users vì việc sửa lỗi mối quan hệ đã thêm nó vào bộ sưu tập trong ngữ cảnh đầu tiên của bạn mặc dù bạn không thêm nó một cách rõ ràng vào mã của mình.
    • Nếu không có trẻ em nào được gắn vào bối cảnh, trạng thái gốc là Deleted sẽ gửi câu lệnh DELETE đến cơ sở dữ liệu và xóa bỏ tầng lớp trong cơ sở dữ liệu cũng sẽ xóa các con. Điều này làm việc mà không có ngoại lệ. Sau đó, mã của bạn sẽ hoạt động nếu bạn đặt account.Users = null trước khi đặt trạng thái thành Deleted trong ngữ cảnh thứ hai hoặc trước khi nhập ngữ cảnh thứ hai.

Theo tôi sử dụng Remove ...

using (var context = new TestContext()) 
{ 
    context.Accounts.Attach(account); 
    context.Accounts.Remove(account); 
    context.SaveChanges(); 
} 

... rõ ràng là cách ưa thích vì hành vi của Remove là nhiều hơn nữa như bạn mong chờ cho một mối quan hệ cần thiết với tầng xóa (là trường hợp trong mô hình của bạn). Sự phụ thuộc của hành vi thay đổi trạng thái thủ công đối với các trạng thái của các thực thể khác khiến cho việc sử dụng trở nên khó khăn hơn. Tôi sẽ xem nó như là sử dụng nâng cao chỉ dành cho các trường hợp đặc biệt.

Sự khác biệt không được biết đến rộng rãi hoặc được ghi lại. Tôi đã nhìn thấy rất ít bài viết về nó. Người duy nhất mà tôi có thể tìm thấy ngay bây giờ một lần nữa, là this one by Zeeshan Hirani.

+1

Rất khai sáng, @Slauma! Tôi đã tìm kiếm rất nhiều về một số đầu mối trước khi đăng câu hỏi nhưng tôi đã không tìm thấy bài đăng bạn đã đề cập. Cảm ơn bạn –

+0

Có vẻ như nhiều người đang gặp phải rất nhiều vấn đề khi cố gắng làm cho các hoạt động của cha mẹ con hoạt động trong EF như chèn, cập nhật và xóa mỗi khi tôi đọc thêm về EF, tôi càng thất vọng. –

1

Tôi đã thử một cách tiếp cận hơi khác và, kỳ lạ, nó hoạt động. Nếu tôi thay thế mã này:

using (var context = new TestContext()) 
{ 
    context.Entry(account).State = EntityState.Deleted; 
    context.SaveChanges(); 
} 

Bằng cách này:

using (var context = new TestContext()) 
{ 
    context.Entry(account).State = EntityState.Unchanged; 
    context.Accounts.Remove(account); 
    context.SaveChanges(); 
} 

Nó hoạt động với không có vấn đề hơn nữa. Không chắc chắn nếu đây là một lỗi hoặc nếu tôi đang thiếu một cái gì đó. Tôi thực sự sẽ đánh giá cao một số ánh sáng về vấn đề này, bởi vì tôi đã khá chắc chắn rằng cách đầu tiên (EntityState.Deleted) là một trong những recommeded.

Các vấn đề liên quan