2011-08-19 23 views
9

Tôi có một vấn đề rất lạ với quan hệ nghịch đảo trong Dữ liệu lõi và tôi đã giảm thiểu vấn đề của mình xuống một ví dụ tối thiểu, bắt đầu từ một dự án mới trong xcode dựa trên mẫu cửa sổ với sự hỗ trợ cho Core Data (ví dụ, có rất ít ở đó).Quan hệ nghịch đảo không được thiết lập (trong bộ xử lý KVO)

Giả sử chúng tôi có mô hình Dữ liệu chính với ba thực thể: Bộ phận, Nhân viên và Tóm tắt bộ phận (một số loại thực thể đại diện cho một số thống kê về bộ phận). Để đơn giản, chúng tôi chỉ có quan hệ một-một:

DepartmentSummary Department  Employee 
--------------------------------------------------------- 
        employee <----> department 
department <----> summary 

Đây là tất cả trong mô hình. Trong application:didFinishLaunchingWithOptions: chúng ta tạo ra một nhân viên và một bộ phận và thiết lập KVO:

NSManagedObject* employee = 
[NSEntityDescription 
    insertNewObjectForEntityForName:@"Employee" 
    inManagedObjectContext:[self managedObjectContext]]; 
[employee addObserver:self forKeyPath:@"department" options:0 context:nil]; 

NSManagedObject* department = 
    [NSEntityDescription 
    insertNewObjectForEntityForName:@"Department" 
    inManagedObjectContext:[self managedObjectContext]]; 
[department setValue:employee forKey:@"employee"]; 

Mục đích của việc xử lý KVO là tạo ra một bản tóm tắt cho bộ phận này càng sớm càng phận của người lao động được thiết lập:

- (void) observeValueForKeyPath:(NSString *)keyPath 
         ofObject:(id)object 
         change:(NSDictionary *)change 
         context:(void *)context 
{ 
    [self createSummary:object]; 
} 

createSummary là đơn giản: nó tạo ra một đối tượng tóm tắt mới và liên kết nó với bộ phận, và sau đó kiểm tra xem mối quan hệ nghịch đảo của bộ phận với đối tượng tóm tắt cũng được đặt là:

- (void) createSummary:(NSManagedObject*)employee 
{ 
    NSManagedObject* department = [employee valueForKey:@"department"]; 
    NSManagedObject* summary = 
    [NSEntityDescription 
     insertNewObjectForEntityForName:@"DepartmentSummary" 
     inManagedObjectContext:[self managedObjectContext]]; 

    [summary setValue:department forKey:@"department"]; 

    NSAssert([department valueForKey:@"summary"] == summary, 
      @"Inverse relation not set"); 
} 

Xác nhận này không thành công. Thật vậy, nếu chúng ta in các đối tượng bách hóa và tóm tắt sau khi bộ phận của bản tóm tắt đã được thiết lập, chúng tôi nhận

entity: DepartmentSummary; 
    id: ..DepartmentSummary/..AA14> ; 
    data: { 
    department = "..Department/..AA13>"; 
    } 

cho Nói tóm lại, như mong đợi, nhưng

entity: Department; 
    id: ..Department/..AA13> ; 
    data: { 
    employee = "..Employee/..AA12>"; 
    summary = nil; 
    } 

cho bộ phận (với một nil tóm lược). Tuy nhiên, nếu chúng ta trì hoãn việc gọi đến createSummary để nó không chạy cho đến khi phiên bản kế tiếp của runloop:

- (void) observeValueForKeyPath:(NSString *)keyPath 
         ofObject:(id)object 
         change:(NSDictionary *)change 
         context:(void *)context 
{ 
    [self performSelector:@selector(createSummary:) 
       withObject:object 
       afterDelay:0]; 
} 

sau đó mọi thứ hoạt động như mong đợi.

Trì hoãn việc khẳng định thay vì làm không giúp đỡ: mối quan hệ nghịch đảo thực sự không nhận được thiết trong đồ thị đối tượng, mặc dù nó được đặt trong cơ sở dữ liệu (nếu bạn đã lưu cơ sở dữ liệu, và khởi động lại các ứng dụng, bây giờ tất cả của một đột ngột các mối quan hệ nghịch đảo xuất hiện).

Đây có phải là lỗi trong Dữ liệu chính không? Đây có phải là hành vi được ghi lại mà tôi đã bỏ lỡ không? Tôi có đang sử dụng Dữ liệu cốt lõi theo cách mà nó không được dự định không?

Lưu ý rằng các handler KVO được gọi trong khi Core Data là (tự động) thiết lập (other) nghịch đảo: chúng ta tự đặt lĩnh vực employee của bộ phận, Core Data tự động cài đặt lĩnh vực department của nhân viên, và đó lần lượt kích hoạt Trình xử lý KVO. Có lẽ đó chỉ là quá nhiều cho Core Data để xử lý :) Thật vậy, khi chúng tôi đặt

[employee setValue:department forKey:@"department"]; 

thay vào đó, mọi thứ lại hoạt động như mong đợi.

Mọi con trỏ sẽ được đánh giá cao.

+0

Điều gì sẽ xảy ra nếu bạn đặt tóm tắt ngay lập tức, nhưng trì hoãn xác nhận * của bạn * cho đến lần chạy tiếp theo? – jtbandes

+0

Câu hỏi hay. Tôi sẽ chỉnh sửa câu hỏi để trả lời câu hỏi - về cơ bản, trì hoãn việc xác nhận không giúp được gì. – edsko

+0

hi, tôi nhận thấy tương tự, nó đã hoạt động trước ... – RolandasR

Trả lời

4

Đây là vấn đề về Dữ liệu cốt lõi cổ điển. Các tài liệu cụ thể nêu rõ:

Vì dữ liệu chính chăm sóc bảo trì đồ thị đối tượng cho bạn, bạn chỉ cần thay đổi một đầu mối quan hệ và tất cả các khía cạnh khác được quản lý cho bạn.

Tuy nhiên, trong thực tế đây là một lời nói dối hói phải đối mặt, ở chỗ nó không đáng tin cậy.

câu trả lời của tôi cho câu hỏi của bạn là như sau:

Đây có phải là một lỗi trong lõi dữ liệu?

CÓ.

Hành vi được ghi lại mà tôi đã bỏ lỡ?

NO.

Tôi có đang sử dụng Dữ liệu cốt lõi theo cách không được dự định không?

NO.

Bạn đã cung cấp giải pháp "chính xác" cho vấn đề của mình, cùng một giải pháp mà tôi sử dụng mỗi lần tôi thay đổi giá trị mối quan hệ trong mọi ứng dụng Dữ liệu cốt lõi mà tôi thực hiện. Đối với hàng trăm trường hợp theo nghĩa đen, mẫu được đề xuất là:

[department setValue:employee forKey:@"employee"]; 
[employee setValue:department forKey:@"department"]; 

tức là, tự đặt mối quan hệ nghịch đảo bất cứ khi nào bạn thay đổi mối quan hệ.

Một người nào đó có thể có nhiều ánh sáng hơn về chủ đề này, hoặc một biểu mẫu chính để giải quyết vấn đề của bạn, nhưng theo kinh nghiệm của tôi thì không có cách nào đảm bảo mối quan hệ khả dụng trừ khi nó được thiết lập theo cách thủ công.). Quan trọng hơn, giải pháp này cũng có hai lợi ích khác:

  1. Nó hoạt động 100% thời gian.
  2. Nó làm cho mã dễ đọc hơn.

Điểm cuối cùng là phản trực giác. Một mặt, nó có vẻ phức tạp mã và làm cho nó dài hơn bằng cách thêm dòng vào những gì có thể được, theo các tài liệu, một cuộc gọi ngắn, một dòng. Nhưng theo kinh nghiệm của tôi, điều đó là lưu một chuyến đi của người lập trình tới trình soạn thảo Dữ liệu cốt lõi để tìm kiếm trực quan và xác nhận các mối quan hệ mô hình, có giá trị hơn về thời gian. Tốt hơn là phải rõ ràng và rõ ràng khi có mô hình tâm thần về những gì được cho là xảy ra khi thay đổi mối quan hệ.

Tôi cũng xin đề nghị thêm một loại đơn giản để NSManagedObject:

@interface NSManagedObject (inverse) 

- (void)setValue:(id)value forKey:(NSString *)key inverseKey:(NSString *)inverse; 

@end 

@implementation NSManagedObject (inverse) 

- (void)setValue:(id)value forKey:(NSString *)key inverseKey:(NSString *)inverse { 
    [self setValue:value forKey:key]; 
    [value setValue:self forKey:inverse]; 
} 

@end 

như trong:

[department setValue:employee forKey:@"employee" inverse:@"department"]; 

có một vài trường hợp để mở rộng sau khi loại đó nhưng tôi sẽ xử lý, ví dụ, xóa hoàn toàn trong một thời trang khác.

Tóm lại:xử lý mọi mối quan hệ của riêng bạn một cách rõ ràng mọi lúc. Dữ liệu cốt lõi không đáng tin cậy trong vấn đề này.

+0

Hmmm. Hấp dẫn. Tôi đã tìm thấy nó nhiều hơn chỉ khi thiết lập quan hệ mặc dù. Một vấn đề tương tự xảy ra khi xóa một bản ghi; mối quan hệ nghịch đảo có thể không được đặt thành nil. Bạn có đối phó với điều đó theo cách thủ công không? Tôi sẽ lo lắng rằng tôi sẽ bắt đầu bỏ lỡ các trường hợp. Bây giờ, tôi đã chắc chắn rằng tôi không bao giờ thay đổi đồ thị đối tượng trong một trình xử lý KVO; Tôi tính toán những gì cần phải được thực hiện và sau đó lên lịch nó cho lần lặp tiếp theo của runloop. Điều này làm việc, nhưng tôi phải thừa nhận việc thực hiện out-of-order này làm cho mã phức tạp hơn. – edsko

+0

Vâng, khi xóa, tôi luôn đặt các mối quan hệ thành nil theo cách thủ công. Theo kinh nghiệm của tôi, Core Data sẽ lên lịch các đối tượng để xóa một cách đáng tin cậy theo các quy tắc được nêu trong tài liệu, vì vậy đó không phải là vấn đề. Để làm rõ, Core Data trong thực tế làm tất cả mọi thứ nó nói, chỉ rằng nó hoạt động nhất định "khi thuận tiện" hơn là "ngay bây giờ", như bạn phát hiện ra trong mã của bạn. Nếu bạn cần một mối quan hệ có sẵn càng sớm càng tốt, bạn nên tự mình đặt nó. Nếu bạn chỉ cần nó vào lần sau, runloop thiết lập giao diện người dùng của bạn, hãy để hệ thống tự động xử lý nó. – SG1

+0

Ngoài ra, tôi sẽ, nếu tôi là bạn, quay trở lại để thay đổi mối quan hệ trong trình xử lý KVO. Điều này hoàn toàn bình thường. Tôi sẽ * không * đi về việc phát minh ra thời gian chạy của riêng tôi để truyền bá thay đổi Dữ liệu cốt lõi. Cái nhìn sâu sắc thực sự ở đây là hiểu rằng bạn cần quản lý các mối quan hệ theo cách thủ công, không phải là bạn cần phải "chờ" và sau đó xử lý các thay đổi sau này. – SG1

0

Làm thế nào để lưu ManagedObjectContext ngay sau khi chèn?

+0

Không lưu MOC hoặc gọi processPendingChanges: giúp. – edsko

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