Лучшая практика для предварительной загрузки основных данных с отношением «многие ко многим»

Я разрабатываю приложение IOS, которое имеет основные данные, и мне нужно предварительно загружать данные, многие ко многим относятся к основным данным. У меня есть три таблицы с супермаркетом, продуктом и промежуточной таблицей, называемой супермаркетомТопродукт, в которой хранится связь между супермаркетами и продуктами. Как я уже говорил, между супермаркетом и товарными объектами существует связь «многие ко многим», поэтому мне нужно было создать промежуточную таблицу, чтобы указать отношения. Мой вопрос заключается в том, что лучше всего предварительно загружать данные в формате JSON в мои основные данные с отношением «многие ко многим». В справочных документах Apple указано, что нет необходимости создавать промежуточную таблицу, так как coredata создает ее для нас. Однако как я могу определить отношения «многие ко многим» при предварительной загрузке даты без промежуточной таблицы? Кстати, все данные статичны, поэтому нет необходимости вставлять новые данные в таблицы, мне нужно только получить данные и связанные с ними супермаркет или продукт. Обратите внимание, что объединенная таблица имеет два независимых атрибута, называемых priceofProductforGivenMarket и primaryProductforGivenMarket. Вот JSON-представление моих данных, а также модель, которую я бы создал в основных данных:

Supermarket:
name
location
phone number

Product:
name
price
company

supermarketToproduct:
 nameofsupermarket
 nameofproduct 
 priceofProductforGivenMarket
 primaryProductforGivenMarket

person emkay    schedule 21.09.2014    source источник


Ответы (2)


Я бы создал три сущности: 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 укажите, что «назначением» для отношения productDetails в сущности Supermarket является сущность SupermarketProductDetails с обратным супермаркетом, аналогичным образом установите сущность SupermarketProductDetails в качестве места назначения для supermarketDetails в сущности Product с обратным продуктом.

Затем я бы сначала проанализировал ваши данные JSON для супермаркетов, создав объекты Supermarket и установив имя, местоположение и номер телефона, но ничего не вводя для отношения продуктов. Аналогичным образом проанализируйте продукты JSON и создайте все объекты продуктов. Затем я анализировал таблицу соединений и создавал объекты SupermarketProductDetails. Установите атрибуты на основе ваших данных JSON и выполните выборку, чтобы получить сущность 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 к набору supermarketDetails для myProduct и т. д.). Также обратите внимание, что «значение» для отношения (к одному) (например, супермаркет) — это сам целевой объект (например, мойСупермаркет); вы не используете имя или любой другой ключ. Coredata (скрытый в базовых таблицах sql) использует уникальные идентификаторы объектов для связывания.

(Возможно, есть более эффективные способы сделать это, чем делать две выборки для каждой записи 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];
person pbasdf    schedule 21.09.2014
comment
Это очень хороший момент. Я забыл упомянуть об этом. Да, у меня есть два других атрибута для совместной таблицы. Итак, вы говорите, что мне нужно построить таблицу соединений, поэтому не могли бы вы дать мне некоторую информацию о том, как это сделать? Еще раз спасибо за очень важное замечание? - person emkay; 21.09.2014
comment
Итак, ваша структура таблицы должна быть немного другой - я отредактирую свой ответ. - person pbasdf; 21.09.2014
comment
Спасибо, жду вашего подхода - person emkay; 21.09.2014
comment
Большое спасибо за Ваш ответ. Решение кажется мне ясным, кроме одного момента. Как вы можете видеть из моего вопроса, SupermarketProductDetails имеет 4 атрибута, 2 из которых — это nameOfSupermaerket и nameofProduct, которые создают связь между двумя таблицами, такими как Tesco и CocaCola. В вашем подходе я не мог понять, как использовать эти атрибуты для создания отношений с использованием вашего кода: [mySupermarketProductDetails setValue:fetchedSupermarket forKey:@supermarket]; [mySupermarketProductDetails setValue:fetchedProduct forKey:@product] - person emkay; 22.09.2014
comment
Вы должны использовать имя как часть выборки - я отредактирую свой ответ, чтобы расширить это. - person pbasdf; 22.09.2014
comment
Здравствуйте, Большое спасибо за ваше редактирование и ответ еще раз. Но все еще есть некоторые проблемы. В своем ответе вы убрали имя супермаркета, название продукта из таблицы соединений, которые создают отношения? Я прав или вы просто забыли включить эти атрибуты в свой ответ? Более того, во второй строке кода у вас есть NSPredicate *predicate = [NSPredicate predicateWithFormat:@name == %@,nameofsupermarket]; так откуда взялось имя супермаркета? - person emkay; 23.09.2014
comment
Вы правы — Coredata не использует имя в качестве связи между таблицей соединения и другими таблицами. Что касается Coredata, супермаркет и продукт являются свойствами сущностей SupermarketProductDetails. Но чтобы заполнить таблицу соединений из ваших данных JSON, нам нужно использовать имя, чтобы найти правильные объекты Supermarket и Product для назначения свойствам SupermarketProductDetails. Таким образом, в приведенном выше коде nameofsupermarket, nameofproduct, primaryProductForGivenMarket и priceOfProductForGivenMarket — это все значения, которые вам нужно получить из ваших данных JSON. - person pbasdf; 23.09.2014
comment
Спасибо! Попробую реализовать, как вы говорите. - person emkay; 23.09.2014
comment
Здравствуйте, я реализовал ваше решение, и оно действительно работает хорошо, за исключением одного момента. Когда я просматриваю объединенную таблицу основных данных, создается, но названия продуктов неверны. Например, в супермаркете есть 7 продуктов, я вижу, что для одного супермаркета есть 7 продуктов, но названия продуктов неверны. У вас есть идеи, почему это происходит? Небольшое примечание в вашем ответе: у вас есть строка NSManagedObject *mySupermarketProductDetails = [NSEntityDescription insertNewObjectForEntityForName:@SupermarketProductDetails inManagedObjectContext:context]; - person emkay; 23.09.2014
comment
Небольшое примечание в вашем ответе: у вас есть строка NSManagedObject *mySupermarketProductDetails = [NSEntityDescription insertNewObjectForEntityForName:@SupermarketProductDetails inManagedObjectContext:context]; но я считаю, что это должен быть SupermarketProductDetails вместо NSManagedObject. Если вы считаете, что это правильно, не могли бы вы обновить свой ответ, чтобы другие люди также могли извлечь пользу из вашего ответа. - person emkay; 23.09.2014
comment
Я не вижу никаких причин, по которым имена были бы неправильными, но я предполагаю, что предикаты для выборки возвращают неправильные результаты. Я исправлю ответ, включив в него несколько NSLogs, чтобы проверить это. Что касается класса для mySupermarketProductDetails, я для простоты предположил, что вы работаете с собственными NSManagedObjects, но если вы реализовали определенные подклассы для каждой сущности, то вы правы — класс будет SupermarketProductDetails. Это также позволяет использовать запись через точку для значений свойств; Я обновлю ответ соответственно. - person pbasdf; 23.09.2014
comment
Я думаю, что неправильные имена могут быть связаны с использованием == в предикате, а не как. Я тоже поправлю. - person pbasdf; 23.09.2014
comment
Это был удивительный ответ и помощь. Большое Вам спасибо. Я считаю, что этот ответ поможет не только мне, но и многим другим. Я заметил, что в Интернете не хватает ресурсов для реализации такого подхода «многие ко многим», что может быть большой проблемой для новичков в основных данных. Большое спасибо. Небольшой вопрос: допустим, я хочу получить продукты для выбранного супермаркета. Поскольку есть 2 многих из супермаркета, чтобы присоединиться к таблице, и многие к одному продукту, я не могу понять, как получить несколько продуктов для данного супермаркета в этом отношении. Любые рекомендации или коды для него? - person emkay; 23.09.2014
comment
Вы можете использовать кодировку значения ключа: [mySupermarket valueForKeyPath:@"productDetails.product"] должен предоставить вам NSSet продуктов для mySupermarket. - person pbasdf; 24.09.2014
comment
Да. С KVC все в порядке, но предположим, что я также хочу получить значения priceofProductforGivenMarket, primaryProductforGivenMarket для выбранного супермаркета для всех сопутствующих товаров. Возможно ли это также с KVC? Например, когда я выбираю супермаркет, я хочу показать его наборы продуктов с названиями, компаниями и нормальной ценой, а также цену продуктов и основной продукт для данного рынка? У вас есть рекомендации для него? Должен ли я вернуться к таблице соединений для каждого продукта и получить? - person emkay; 25.09.2014
comment
В чем причина придерживаться промежуточной таблицы для отношений? Во всяком случае, в настоящее время я работаю над очень похожей проблемой, но вместо этого использовал отношения «многие ко многим». Моя модель не касается продуктов и супермаркетов, но если использовать эту аналогию, отношения будут прямыми между этими двумя объектами, и никакого третьего объекта не существует. Ну, у меня есть пара сотен тысяч отношений, и оказалось, что связывать все это друг с другом было мучительно медленно. Я думаю, что мне следует игнорировать документы Core Data и использовать обычную промежуточную таблицу, как в этом ответе. - person Jonny; 30.07.2015
comment
Просто предположение, причина в том, чтобы ускорить первоначальный процесс вставки/миграции? Я просто чувствую, что с Core Data что-то не так. - person Jonny; 30.07.2015
comment
@Jonny В этом случае объект промежуточного соединения был необходим, потому что отношения между супермаркетом и продуктом имеют атрибуты, например. цена продукта для заданного рынка. Если вы просто используете отношение «многие-многие» между «Супермаркетом» и «Продуктом», вам негде будет смоделировать эти атрибуты. Я не уверен, что использование промежуточного объекта значительно ускорит процесс импорта. - person pbasdf; 30.07.2015
comment
@Jonny Вы пробовали методы, описанные в Руководстве Apple по программированию основных данных, эффективно импортируете данные? - person pbasdf; 30.07.2015
comment
Хорошо, это имеет смысл. И нет, это то, что я обязательно прочитаю. Спасибо. - person Jonny; 30.07.2015

Я думаю, что описанный выше подход будет затруднен для предварительной выборки и будет выполнять предварительную выборку почти всех совпадающих записей, и реальный мотив предварительной выборки исчезнет.

также я бы предложил добавить 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.

Для получения дополнительной информации см.: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/Articles/cdRelationships.html#//apple_ref/doc/uid/TP40001857-SW10

Также следуйте этому: Core Data sectionNameKeyPath с проблемой производительности атрибута отношения

person bllakjakk    schedule 21.09.2014
comment
Большое спасибо за быстрый ответ. Я уже читал документ яблок, но не смог найти соответствующее решение. Что вы имеете в виду, добавляя topProduct? Вы имеете в виду, что я должен создать атрибут в таблице супермаркетов с именем topProduct, который показывает взаимосвязь между супермаркетом и продуктом? - person emkay; 21.09.2014
comment
Но как это создает отношения между продуктами и супермаркетом? Поскольку это логически отношения «многие ко многим», мне это тоже нужно. Не могли бы вы дать более подробную информацию? - person emkay; 21.09.2014
comment
Кстати, все данные статичны, поэтому нет необходимости вставлять данные в таблицы, мне нужно только получить данные и связанные с ними супермаркет или продукт. - person emkay; 21.09.2014
comment
Обновлено об отношениях и извлечении выше. - person bllakjakk; 21.09.2014