预加载具有多对多关系的核心数据的最佳实践

Posted

技术标签:

【中文标题】预加载具有多对多关系的核心数据的最佳实践【英文标题】:Best practice for preloading core data with many to many relationship 【发布时间】:2014-09-21 15:45:54 【问题描述】:

我正在开发一个具有核心数据的 ios 应用程序,并且我必须预加载与核心数据的多对多关系的数据。我有三个表,分别是超市、产品和中间表,称为 supermarketToproduct 存储超市与产品之间的关系。正如我所说,超市和产品实体之间存在多对多关系,因此我需要创建一个中间表来指示这些关系。 我的问题是,将 JSON 格式的数据预加载到具有多对多关系的核心数据中的最佳做法是什么。在 Apple 的参考文档中,声明不需要创建中间表,因为 coredata 为我们创建了一个。但是,如何在没有中间表的情况下预加载日期时定义多对多关系? 顺便说一句,所有数据都是静态的,所以不需要向表中插入新数据,我只需要获取数据及其相关的超市或产品。 请注意,连接表有两个独立的属性,称为 priceofProductforGivenMarket 和 primaryProductforGivenMarket 这是我的数据的 JSON 表示,也是我将在核心数据中创建的模型:

Supermarket:
name
location
phone number

Product:
name
price
company

supermarketToproduct:
 nameofsupermarket
 nameofproduct 
 priceofProductforGivenMarket
 primaryProductforGivenMarket

【问题讨论】:

【参考方案1】:

我将创建三个实体,Supermarket、Product 和 SupermaketProductDetails,其属性和关系如下:

Supermarket:
Attributes: name, location, phone number
Relationship (to many): productDetails

Product:
Attributes: name, price, company
Relationship (to many): supermarketDetails

SupermarketProductDetails:
Attributes:  priceofProductforGivenMarket, primaryProductforGivenMarket
Relationships: (to one) supermarket, (to one) product

在Xcode中,指明Supermarket实体中productDetails关系的“目的地”是SupermarketProductDetails实体,逆超市,同样将SupermarketProductDetails实体设置为Product实体中supermarketDetails的目的地,逆产品。

然后,我将首先解析超市的 JSON 数据,创建 Supermarket 对象并设置名称、位置和电话号码,但不为产品关系输入任何内容。同样,解析产品 JSON 并创建所有 Products 对象。然后我会解析连接表,并创建 SupermarketProductDetails 对象。根据你的 JSON 数据设置属性,并执行一次 fetch 以获取具有正确名称的 Supermarket 实体,同样获取具有正确名称的 Product 实体,然后直接设置这些关系。

编辑: 假设您将连接表中的每一行解析为四个 NSString:nameofsupermarket、nameofproduct、priceofProductforGivenMarket 和 primaryProductforGivenMarket。然后对于每一行...

// First fetch the correct Supermarket...
NSFetchRequest *supermarketFetch = [NSFetchRequest fetchRequestWithEntityName:@"Supermarket"]
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like %@",nameofsupermarket];
supermarketFetch.predicate = predicate;
NSError *error;
NSArray *results = [context executeFetchRequest:supermarketFetch error:&error];
// should really check for errors, and that we get one and only one supermarket
NSLog(@"Supermarket fetch returned %i results",[results count]);  // should be only 1!
mySupermarket = (NSManagedObject *)[results firstObject];
if (![[mySupermarket valueForKey:@"name"] isEqualToString:nameofsupermarket]) 
    NSLog(@"Wrong supermarket fetched");
    

// Now fetch the product...
NSFetchRequest *productFetch = [NSFetchRequest fetchRequestWithEntityName:@"Product"]
predicate = [NSPredicate predicateWithFormat:@"name like %@",nameofproduct];
productFetch.predicate = predicate;
results = [context executeFetchRequest:productFetch error:&error];
// should really check for errors, and that we get one and only one product
NSLog(@"Product fetch returned %i results",[results count]);  // should be only 1!
myProduct = (NSManagedObject *)[results firstObject];
if (![[myProduct valueForKey:@"name"] isEqualToString:nameofproduct]) 
    NSLog(@"Wrong product fetched");
    

// Now create the SupermarketProductDetails entity:
NSManagedObject *mySupermarketProductDetails = [NSEntityDescription insertNewObjectForEntityForName:@"SupermarketProductDetails" inManagedObjectContext:context];
// set attributes...
[mySupermarketProductDetails setValue:primaryProductForGivenMarket forKey:@"primaryProductForGivenMarket"];
[mySupermarketProductDetails setValue:priceOfProductForGivenMarket forKey:@"priceOfProductForGivenMarket"];
// set relationships...
[mySupermarketProductDetails setValue:mySupermarket forKey:@"supermarket"];
[mySupermarketProductDetails setValue:myProduct forKey:@"product"];
[context save:&error];
// should check for errors...

请注意,您只需要设置这些关系的一侧 - CoreData 会为您更新另一侧(即,它将 mySupermarketDetails 添加到 myProduct 的 supermarketDetails 集合中,等等)。另请注意,(一对一)关系(例如超市)的“价值”是目标对象本身(例如 mySupermarket);您不使用名称或任何其他键。 Coredata(隐藏在底层 sql 表中)使用唯一的 objectID 进行链接。

(可能有更有效的方法来执行此操作,而不是为每个 SupermarketProductDetails 条目进行两次提取,但这会起作用。)

EDIT2:请注意,以上假设您的实体都实现为 NSManagedObjects。如果您为每个实体创建了单独的子类,那么您可以简化上面的一些代码。例如,valueForKey: 和 setValue:forKey: 可以使用点表示法替换为等效的属性访问器方法,例如:

[mySupermarketProductDetails setValue:primaryProductForGivenMarket forKey:@"primaryProductForGivenMarket"];

会变成:

mySupermarketProductDetails.primaryProductForGivenMarket = primaryProductForGivenMarket;

[mySupermarket valueForKey:@"name"]

可以替换为:

mySupermarket.name

同样,应该使用适当的子类而不是 NSManagedObject 创建对象。例如。

NSManagedObject *mySupermarketProductDetails = [NSEntityDescription insertNewObjectForEntityForName:@"SupermarketProductDetails" inManagedObjectContext:context];

会变成

SupermarketProductDetails *mySupermarketProductDetails = [NSEntityDescription insertNewObjectForEntityForName:@"SupermarketProductDetails" inManagedObjectContext:context];

【讨论】:

这是非常非常好的一点。我忘了提这件事。是的,我还有其他两个用于联合表的属性。所以你说我需要建立连接表所以你能给我一些关于如何做到这一点的信息吗?再次感谢您的重要评论? 因此,您的表结构需要稍有不同 - 我将编辑我的回复。 谢谢,期待看到你的做法 非常感谢您的回复。解决方案对我来说似乎很清楚,除了一点。从我的问题中可以看出,SupermarketProductDetails 有 4 个属性,其中 2 个是 nameOfSupermaerket 和 nameofProduct,它们创建了两个表(例如 Tesco 和 CocaCola)之间的关系。在您的方法中,我无法理解如何使用这些属性来使用您的代码创建关系: [mySupermarketProductDetails setValue:fetchedSupermarket forKey:@"supermarket"]; [mySupermarketProductDetails setValue:fetchedProduct forKey:@"product"] 您将使用该名称作为提取的一部分 - 我将编辑我的回复以对此进行扩展。【参考方案2】:

我认为上述方法很难预取,并且会预取几乎所有匹配的条目,并且预取的真正动机将不复存在。

我也建议在 Supermarket 表中添加 topProduct。

您必须在两个方向上定义多对多关系——也就是说,您必须指定两个关系,每个关系都是另一个关系的倒数

编辑 01:

Product (Attributes):
name
price
company

Supermarket (Attributes):
name
location
phone number
supermarket top Product

一旦你像上面一样创建属性并添加多对多关系。

Coredata 将在下面添加关系的相应实体。

**NSSet products, [supermarket table] and NSSet supermarkets [product table]**

所以现在您可以在插入数据后实际更新此关系,如下所示: 即

插入

    // Create Product
    NSManagedObject *product = [[NSManagedObject alloc] initWithEntity:@"Product" insertIntoManagedObjectContext:self.managedObjectContext];

    // Set details
    [product setValue:@"xxx" forKey:@"name"];
    ...

// Create Supermarket

    NSManagedObject *supermarket = [[NSManagedObject alloc] initWithEntity:@"SuperMarket" insertIntoManagedObjectContext:self.managedObjectContext];

    // Set First and Last Name
    [supermarket setValue:@"Main Street" forKey:@"name"];
    ...

// Create Relationship

    [supermarket setValue:[NSSet setWithObject:product] forKey:@"products"];

    // Save Managed Object Context
    NSError *error = nil;
    if (![supermarket.managedObjectContext save:&error]) 
        NSLog(@"Unable to save managed object context.");
        NSLog(@"%@, %@", error, error.localizedDescription);
    

//And similarly inverse relationship.

正在抓取

现在,由于您在 Supermarket 表中拥有有关产品的相关信息,反之亦然,因此提取将变得平滑,并且如前所述,不会预先提取所有数据,否则会预先提取所有数据。因为无需通过 coreData fetch 创建进一步的关系。

更多详情请关注:https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/Articles/cdRelationships.html#//apple_ref/doc/uid/TP40001857-SW10

另外请遵循: Core Data sectionNameKeyPath with Relationship Attribute Performance Issue

【讨论】:

非常感谢您的快速回答。我已经阅读了苹果文档,但找不到相关的解决方案。添加 topProduct 是什么意思?你的意思是我应该在超市表中创建一个名为 topProduct 的属性来显示超市和产品之间的关系? 但是它是如何建立从产品到超市的关系的呢?因为它在逻辑上是多对多的关系,所以我也需要它。能否请您提供更多详细信息? 顺便说一下,所有数据都是静态的,所以不需要往表中插入数据,我只需要获取数据及其相关的超市或产品。 更新了上面的关系和获取。

以上是关于预加载具有多对多关系的核心数据的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

获取多对象的核心数据最佳实践

在 Doctrine 中创建多对多关系的最佳实践。

RESTful API 设计更新 1/多对多关系的最佳实践?

Laravel 中批量更新关系的最佳实践

多对多表 id 的最佳实践

Automapper - 将多对多关联映射到平面对象的最佳实践