Продолжаем изучение CoreData. Видео к уроку 43 находится
здесь.
В этом уроке мы разберемся как извлекать данные из базы, применяя условия, сортировки и т.д.
В прошлом уроке мы добавили сущность TACourse которая соответсвует предметам в университете.
Для вывода названия курса и студентов, добавим метод:
- (void)printCourse:(TACourse *) courseForPrint
{
NSString *strReturn = [NSString stringWithFormat:@"TACourse: %@. Students: ",
courseForPrint.nameCourse];
for (TAStudent *student in courseForPrint.relationStudent) {
strReturn = [NSString stringWithFormat:@"%@ %@", strReturn, student.lastName];
}
NSLog(@"%@", strReturn);
}
Так же добавим информацию о курсе в функции вывода студента и университета.
Создадим университет и добавим в него 5 курсов.
Затем для 100 студентов добавим курсы (рандомно до 5 курсов для каждого студента).
TAUniversity *university = [self addUniversity];
NSArray *arrayCourses = @[
[self addCourseWithName:courseName[0]],
[self addCourseWithName:courseName[1]],
[self addCourseWithName:courseName[2]],
[self addCourseWithName:courseName[3]],
[self addCourseWithName:courseName[4]]
];
[university addRelationCourse:[NSSet setWithArray:arrayCourses]];
for (int i = 0; i < 100; i++) {
TAStudent *student = [self addRandomStudent];
TACar *car = [self addRandomCar];
student.carRelation = car;
[university addRelationStudentObject:student];
NSInteger numberCourses = arc4random_uniform(5);
while (student.relationCourse.count < numberCourses) {
TACourse *courseForStudent = arrayCourses[arc4random_uniform(5)];
if (![student.relationCourse containsObject:courseForStudent]) {
[student addRelationCourseObject:courseForStudent];
}
}
}
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(@"%@",[error localizedDescription]);
};
[self printAllObjects];
Рассмотрим запрос студентов из базы. Нам редко нужны сразу все студенты содержащиеся в базе. К примеру для первичного вывода в таблицу нужны 20 студентов, а остальные записи нужно подгружать по мере скрола таблицы.
Для реализации этой функции в запрос нужно прописать размер пачки, по которой будет браться студенты из базы:
[request setFetchBatchSize:20];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSEntityDescription *description = [NSEntityDescription entityForName:@"TAStudent" inManagedObjectContext:self.managedObjectContext];
[request setEntity:description];
[request setFetchBatchSize:20];
NSError *requestError = nil;
NSArray *arrayResult = [self.managedObjectContext executeFetchRequest:request error:&requestError];
[self printObjects:arrayResult];
Если например взять первые 50 записей в массиве, то видно, что запрос сделан 3 раза по 20 записей:
arrayResult = [arrayResult subarrayWithRange:NSMakeRange(0, 50)];
Для того, чтобы взять первые 35 записей из базы, нужно установить лимит:
[request setFetchLimit:35];
Для того, чтобы установить смещение от начала базы устанавливаем оффсет:
[request setFetchOffset:10];
Теперь по запросу будут выведены студенты с 10 по 45 по 20 штук в пачке.
Если мы выводе студента используем информаци о курсах, то каждый раз делается дополнительный запрос в базу на загрузку курсов связанных с текущим студентом.
Это замедляет работу.
Чтобы данные с нужными связями подгрузились сразу, нужно указать какие связи подгружать:
[request setRelationshipKeyPathsForPrefetching:@[@"relationCourse"]];
Таким образом, если свойство не будет обязательно использоваться для вывода, то оставляем загрузку по умолчанию, а если свойство 100% нам понадобится, делаем предазгрузку.
Сортировку получаемых данных лучше делать средствами CoreData, а не сортировать массивы после получения, т.к. сортировка через CoreData занимает меньше времени и ресурсов.
Для сортировки студентов по фамилии, а при одинаковой фамилии по имени задаем два дескриптора в котором указываем по какому полю сортировать и вставляем их в запрос в нужном порядке (сначала сортировка фамилии, затем имени):
NSSortDescriptor *firstNameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"firstName" ascending:YES];
NSSortDescriptor *lastNameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastName" ascending:YES];
[request setSortDescriptors:@[lastNameDescriptor, firstNameDescriptor]];
Для того, чтобы брать из базы студентов, удовлетворяющих определенным условиям, нужно использовать NSPredicate:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"score > %@", @3];
[request setPredicate:predicate];
Мы создали предикат и указали, что нам нужны студенты с оценкой больше 3. Добавили предикат к запросу.
Если нам нужно устновить несколько условий (например вывести score >3 и <=3.5):
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"score > 3 AND score <= 3.5"];
или
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"score > %@ && score <= %@", @3, @3.5];
Для того, чтобы вывести студентов с оценкой 3 < score <=3.5 и количеством курсов больше 3, нужно использовать оператор коллекции @count:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"score > %@ && score <= %@ && relationCourse.@count >= %@", @3, @3.5, @3];
Для того, чтобы к этим условиям добавить новое: имена студентов должны быть в массиве, строка предиката будет выглядеть так:
NSArray *arrayValideName = @[@"Vanetta",@"Tran",@"Tandra"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"score > %@ && score <= %@ && relationCourse.@count >= %@ && firstName IN %@", @3, @3.5, @3, arrayValideName];
Сделаем запрос на вывод курсов, у которых средний бал студентов больше 2.9
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSEntityDescription *description = [NSEntityDescription entityForName:@"TACourse" inManagedObjectContext:self.managedObjectContext];
[request setEntity:description];
[request setRelationshipKeyPathsForPrefetching:@[@"relationStudent"]];
NSSortDescriptor *courseNameSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"nameCourse" ascending:YES];
[request setSortDescriptors:@[courseNameSortDescriptor]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"@avg.relationStudent.score > %@", @2.9];
[request setPredicate:predicate];
NSError *requestError = nil;
NSArray *arrayResult = [self.managedObjectContext executeFetchRequest:request error:&requestError];
[self printObjects:arrayResult];
Чтобы вывести курсы с суммой оценок студентов 160
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"@sum.relationStudent.score > %@", @160];
Чтобы вывести курсы, которые имеют студентов с оценкой больше 3.9
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"@max.relationStudent.score > %@", @3.9];
Чтобы сделать подзапрос для определения курсов у которых студенты ездят более, чем на 3 BMW, предикат выглядит так:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SUBQUERY(relationStudent, $student, $student.carRelation.model == %@).@count >= %@", @"BMW", 3];
Но у меня этот запрос вываливается с EXC_BAD_ACCESS (я к сожаению не разобрался, почему).
Альтернативным способом для выборки из базы сущностей с определенными критериями является добавление в файле модели нового Fetch Request.
Назовем наш запрос FetchStudents.
В списке Fetch All выбираем TAStudent.
Добавляем условие отбора - жмем плюсик и выбираем score -> is greater than -> 3.0.
Еще одно условие score -> is less than or equal to -> 3.5.
Если поменять режим страницы (правая кнопка), то мы увидим, как эти условия будут выглядеть в предикате: score > 3 AND score <= 3.5
Для того, чтобы воспользоваться интерактивно созданным запросом, нужно указать его имя:
NSFetchRequest *request = [self.managedObjectModel fetchRequestTemplateForName:@"FetchStudents"];
NSError *requestError = nil;
NSArray *arrayResult = [self.managedObjectContext executeFetchRequest:request error:&requestError];
[self printObjects:arrayResult];
Это удобно, когда нужно много раз использовать один и тот же запрос, т.е. вызывать запрос по имени. Если изменить этот запрос, то он изменится во всем приложении, что так же удобно.
Можно в этот запрос добавить сортировочные дескрипторы (в старом xCode для этого надо было сделать копию запроса (сейчас этого делать не нужно):
request = [request copy];
NSSortDescriptor *firstNameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"firstName" ascending:YES];
NSSortDescriptor *lastNameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastName" ascending:YES];
[request setSortDescriptors:@[lastNameDescriptor, firstNameDescriptor]];
Последнее понятие рассмотренное в этом уроке это Fetched Properties.
Это проперти возвращает всегда массив.
В файле модели выберем сущность TACourse и нажмем правую кнопку с плюсиком - Add Fetched Property.
Назовем новую Property - bestStudents.
Destination выбираем TAStudent.
В Predicate прописываем условие score > 3.5, т.е. в это проперти будет возвращены все студенты курса, у которых оценка больше 3.5 баллов (по аналогии с SUBQUERY).
Чтобы обращаться через точку к новому проперти, добавим в файл с пропертями TACourse (TACourse+CoreDataProperties.h):
@property (nullable, nonatomic, retain) NSArray *bestStudents;
И объявим в файле TACourse+CoreDataProperties.m
@dynamic bestStudents;
Пересоздадим базу.
Выведем для каждого курса массив с лучшими студентами
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription * description = [NSEntityDescription entityForName:@"TACourse" inManagedObjectContext:self.managedObjectContext];
[request setEntity:description];
[request setRelationshipKeyPathsForPrefetching:@[@"relationStudent"]];
NSArray *arrayResult = [self.managedObjectContext executeFetchRequest:request error:nil];
for (TACourse *course in arrayResult) {
NSLog(@"Course: %@. BestStudents:", course.nameCourse);
[self printObjects:course.bestStudents];
}
Аналогично создаем и выводим еще одно Fetched Property для студентов подписанных на много курсов: studentsWithManyCourses
Destination: TAStudent
Predicate: relationCourse.@count >= 4
Когда нужно обратиться к проперти в предикате, то используется выражение:
$FETCH_SOURCE.имя проперти
Файл с исходным кодом по уроку можно скачать
здесь.