2012-05-31 30 views
13

Đối với một ứng dụng sử dụng Mã Đầu tiên EF 5 beta Tôi có:EF có thể tự động xóa dữ liệu bị mồ côi, nơi cha mẹ không bị xóa không?

public class ParentObject 
{ 
    public int Id {get; set;} 
    public virtual List<ChildObject> ChildObjects {get; set;} 
    //Other members 
} 

public class ChildObject 
{ 
    public int Id {get; set;} 
    public int ParentObjectId {get; set;} 
    //Other members 
} 

Các hoạt động CRUD liên quan được thực hiện bởi các kho, khi cần thiết.

Trong

OnModelCreating(DbModelBuilder modelBuilder) 

tôi đã thiết lập chúng:

modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects) 
      .WithOptional() 
      .HasForeignKey(c => c.ParentObjectId) 
      .WillCascadeOnDelete(); 

Vì vậy, nếu một ParentObject bị xóa, ChildObjects của nó quá.

Tuy nhiên, nếu tôi chạy:

parentObject.ChildObjects.Clear(); 
_parentObjectRepository.SaveChanges(); //this repository uses the context 

tôi nhận được ngoại lệ:

Thao tác thất bại: Mối quan hệ không thể được thay đổi bởi vì một hoặc nhiều trong những tài sản nước ngoài quan trọng là không -nullable. 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ột 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 một giá trị không null khác, hoặc đối tượng không liên quan phải bị xóa.

Điều này có ý nghĩa vì định nghĩa của các thực thể bao gồm ràng buộc khóa ngoài bị hỏng.

Tôi có thể định cấu hình thực thể để "tự xóa" khi bị mồ côi hoặc tôi phải xóa thủ công các ChildObject từ ngữ cảnh này (trong trường hợp này sử dụng ChildObjectRepository).

+0

Rất may, nhóm EF [biết về điều này] (http://blog.oneunicorn.com/2012/06/02/deleting-orphans-with-entity-framework/) và có thể sẽ xuất hiện một giải pháp tích hợp mà không yêu cầu sửa đổi nội bộ – PinnyM

Trả lời

25

Nó thực sự được hỗ trợ nhưng chỉ khi bạn sử dụng Identifying relation. Nó hoạt động với mã đầu tiên là tốt. Bạn chỉ cần xác định trọng điểm phức tạp cho ChildObject của bạn có chứa cả IdParentObjectId:

modelBuilder.Entity<ChildObject>() 
      .HasKey(c => new {c.Id, c.ParentObjectId}); 

Bởi vì việc xác định trọng điểm như vậy sẽ loại bỏ ước mặc định để tự động tăng lên Id bạn phải xác định lại nó bằng tay:

modelBuilder.Entity<ChildObject>() 
      .Property(c => c.Id) 
      .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 

Bây giờ gọi để parentObject.ChildObjects.Clear() xóa các đối tượng phụ thuộc.

Btw.lập bản đồ liên quan bạn nên sử dụng WithRequired làm theo các lớp học thực sự của bạn bởi vì nếu FK là không nullable, nó không phải là bắt buộc:

modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects) 
      .WithRequired() 
      .HasForeignKey(c => c.ParentObjectId) 
      .WillCascadeOnDelete(); 
+0

Câu trả lời hay. Có vẻ như tôi có rất nhiều điều để tìm hiểu về EF. – StuperUser

+1

@Ladislav Mmka những gì về trường hợp một chiều? Khi ChildObject không biết về cha mẹ? – Davita

+0

@Ladislav Tôi đang sử dụng EF6 và cố gắng làm như bạn đã làm trong các ví dụ. Tuy nhiên gọi Collection.Clear() và AddOrUpdate (cha mẹ) dường như không hoạt động. Có điều gì tôi phải làm trong bối cảnh không? – Jamez

-1

Đây không phải là nội dung được EF hỗ trợ tự động ngay bây giờ. Bạn có thể làm điều đó bằng cách ghi đè các SaveChang trong ngữ cảnh của bạn và xóa thủ công một đối tượng con không còn có cha/mẹ. Mã sẽ giống như sau:

public override int SaveChanges() 
{ 
    foreach (var bar in Bars.Local.ToList()) 
    { 
     if (bar.Foo == null) 
     { 
      Bars.Remove(bar); 
     } 
    } 

    return base.SaveChanges(); 
} 
+3

Ý tưởng hay, ngoại trừ điều này phải cho bạn hiệu suất khủng khiếp –

+3

Mã này không được thực thi cho mỗi SaveChanges()? – Elisabeth

4

Để giải quyết vấn đề này mà không thành lập một chính phức tạp, bạn có thể ghi đè lên SaveChanges của DbContext của bạn, nhưng sau đó sử dụng ChangeTracker để tránh truy cập cơ sở dữ liệu để tìm các đối tượng mồ côi.

Đầu tiên thêm một tài sản chuyển hướng đến ChildObject (bạn có thể giữ int ParentObjectId thuộc tính nếu bạn muốn, nó hoạt động một trong hai cách):

public class ParentObject 
{ 
    public int Id { get; set; } 
    public virtual List<ChildObject> ChildObjects { get; set; } 
} 

public class ChildObject 
{ 
    public int Id { get; set; } 
    public virtual ParentObject ParentObject { get; set; } 
} 

Sau đó tìm kiếm đối tượng trẻ mồ côi bằng ChangeTracker:

public class MyContext : DbContext 
{ 
    //... 
    public override int SaveChanges() 
    { 
     HandleOrphans(); 
     return base.SaveChanges(); 
    } 

    private void HandleOrphans() 
    { 
     var orphanedEntities = 
      ChangeTracker.Entries() 
      .Where(x => x.Entity.GetType().BaseType == typeof(ChildObject)) 
      .Select(x => ((ChildObject)x.Entity)) 
      .Where(x => x.ParentObject == null) 
      .ToList(); 

     Set<ChildObject>().RemoveRange(orphanedEntities); 
    } 
} 

Cấu hình của bạn sẽ trở thành:

modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects) 
      .WithRequired(c => c.ParentObject) 
      .WillCascadeOnDelete(); 

Tôi đã thực hiện một thử nghiệm tốc độ đơn giản lặp lại 10.000 lần. Với HandleOrphans() cho phép mất 1: 01.443 phút để hoàn thành, với nó bị vô hiệu hóa nó là 0: 59.326 phút (cả hai đều là trung bình của ba lần chạy). Kiểm tra mã dưới đây.

using (var context = new MyContext()) 
{ 
    var parentObject = context.ParentObject.Find(1); 
    parentObject.ChildObjects.Add(new ChildObject()); 
    context.SaveChanges(); 
} 

using (var context = new MyContext()) 
{ 
    var parentObject = context.ParentObject.Find(1); 
    parentObject.ChildObjects.Clear(); 
    context.SaveChanges(); 
} 
Các vấn đề liên quan