【问题标题】:Paging results from Core Data requests来自 Core Data 请求的分页结果
【发布时间】:2011-12-18 19:43:06
【问题描述】:

我有一个比较简单的核心数据 sqlite 数据库。我正在尝试一次从 DB 获取结果。


    NSFetchRequest* request = [[[NSFetchRequest alloc] init] autorelease];
    [request setEntity:[...]];

    [request setPredicate:[NSPredicate predicateWithFormat:@"flaggedTime != nil"]];

    NSSortDescriptor* sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"flaggedTime" ascending:NO];
    [request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];

    [request setFetchLimit:pageSize];
    [request setFetchOffset:((pageIndex - 1) * pageSize)];    

    NSArray* results = [self.context executeFetchRequest:request error:NULL];

pageSize 为 30,测试数据上的 pageIndex 为 1、2、3 或 4(DB 中大约有 80 个项目,因此 pageIndex = 4 应该不返回任何项目)。 谓词和排序工作正常,结果成功返回。获取限制也可以正常工作。不返回任何错误。

问题:我总是从第一页得到结果,好像 fetchOffset 没有设置。我试图删除谓词和排序但无济于事。 唯一可以让 fetchOffset 工作的情况是当我使用小于 30 的值时。当然,这对于分页毫无意义......

有人知道为什么吗?我会非常感谢每一个答案。

更新:我说的是 iOS。在 4.2 和 5.0 上测试。

更新 2:简化问题。


    NSFetchRequest* request = [[[NSFetchRequest alloc] init] autorelease];
    [request setEntity:[...];

    NSError* error = nil;

    NSManagedObjectContext* context = [...];

    NSUInteger count = [context countForFetchRequest:request error:&error];
    assert(error == nil);

    NSLog(@"Total count: %u", count);

    request.fetchOffset = 0;    
    request.fetchLimit = 30;

    NSLog(@"Fetch offset: %u, limit: %u", request.fetchOffset, request.fetchLimit);

    NSArray* page1 = [context executeFetchRequest:request error:&error];
    assert(error == nil);

    NSLog(@"Page 1 count: %u", page1.count);

    request.fetchOffset = 30;    
    request.fetchLimit = 30;

    NSLog(@"Fetch offset: %u, limit: %u", request.fetchOffset, request.fetchLimit);

    NSArray* page2 = [context executeFetchRequest:request error:&error];
    assert(error == nil);

    NSLog(@"Page 2 count: %u", page2.count);

给予:

总数:34 获取偏移量:0,限制:30 第 1 页计数:30 获取偏移量:30,限制:30 第 2 页计数:30(错误:应该给 4)

【问题讨论】:

  • 我们能看到你设置pageSize和pageIndex的代码吗?还有,你显示结果的代码?
  • 那不相关。问题就在这里。我知道您不相信我,并且您希望在其他地方找到一个简单的解决方案,但是进入该方法的参数就是我上面写的。但我很好奇 - 你真的有一个同时使用 fetchLimit 和 fetchOffset 的工作代码吗?
  • 我有使用 fetchLimit、fetchOffset 和 sortDescriptors 的工作代码 - 就像你一样。我的代码工作正常。
  • 打开数据库或请求设置有区别吗?我知道文档状态“在嵌套请求(例如子查询)中忽略偏移量”。但我觉得我的要求很简单。
  • @Sulthan 您可能需要共享您的 momd 文件以帮助重现错误。

标签: objective-c ios core-data


【解决方案1】:

我刚刚创建了一个演示项目,它试图以最简单的形式重新创建您的场景。我创建了一个空白项目,添加了 34 个对象,然后使用您上面列出的完全相同的代码对其进行查询。下面是例子:

CDAppDelegate * delegate = (CDAppDelegate*)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext * context = [delegate managedObjectContext];
for(int i = 0; i < 34;i++){
    CDObject * object = [NSEntityDescription insertNewObjectForEntityForName:@"CDObject"
                                                      inManagedObjectContext:context];
    [object setValue:i];
}
[delegate saveContext];

NSFetchRequest* request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[NSEntityDescription entityForName:@"CDObject"
                               inManagedObjectContext:context]];

 NSError* error = nil;

 NSUInteger count = [context countForFetchRequest:request error:&error];
 assert(error == nil);

 NSLog(@"Total count: %u", count);

 request.fetchOffset = 0;    
 request.fetchLimit = 30;

 NSLog(@"Fetch offset: %u, limit: %u", request.fetchOffset, request.fetchLimit);

 NSArray* page1 = [context executeFetchRequest:request error:&error];
 assert(error == nil);

 NSLog(@"Page 1 count: %u", page1.count);

 request.fetchOffset = 30;    
 request.fetchLimit = 30;

 NSLog(@"Fetch offset: %u, limit: %u", request.fetchOffset, request.fetchLimit);

 NSArray* page2 = [context executeFetchRequest:request error:&error];
 assert(error == nil);

 NSLog(@"Page 2 count: %u", page2.count);

[request release];

日志如图:

2011-11-04 14:53:04.530 CDCoreDataTest[77964:207] Total count: 34
2011-11-04 14:53:04.531 CDCoreDataTest[77964:207] Fetch offset: 0, limit: 30
2011-11-04 14:53:04.532 CDCoreDataTest[77964:207] Page 1 count: 30
2011-11-04 14:53:04.533 CDCoreDataTest[77964:207] Fetch offset: 30, limit: 30
2011-11-04 14:53:04.533 CDCoreDataTest[77964:207] Page 2 count: 4

使用您的代码,我可以毫无问题地启动并运行它。这是通过在模拟器上运行的 iOS 5.0 完成的。您的代码在我看来是正确的,因为您正在尝试完成,因此获取请求或上下文本身一定有问题......

【讨论】:

  • 我认为 OP 的问题是与“页面”和“分页”的概念混淆。在这个隐含的数据模型或显式代码中,没有任何本质上类似于页面的东西。他不会像编程中通常使用的术语那样“分页”任何东西。
  • 仍在尝试在新创建的模型上重现该问题。请耐心等待。
【解决方案2】:

当您针对未保存的上下文运行获取请求时,NSFetchRequestpageOffset 属性(有时?)会被忽略。请注意如何在 OP 上下文的代码中永远不会保存,而在 @kcharwood 附加到答案的代码 sn-p 中它实际上是保存的。这是一个例子:

- (void) printArrayOfTestEntities:(NSArray *)array{
    NSMutableString * s = [NSMutableString new];
    NSArray * numbers = [array valueForKey:@"someField"];
    for (NSNumber * number in numbers){
        [s appendString:[NSString stringWithFormat:@"%d ", [number intValue]]];
    }

    NSLog(@"fetched objects: %@ \rcount: %d", s, array.count);
}

/* example itself */

NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[context setPersistentStoreCoordinator:persistentStoreCoordinator];

for(int i = 0; i < 34;i++){
    NSManagedObject * object = [NSEntityDescription insertNewObjectForEntityForName:@"TestEntity"
                                                      inManagedObjectContext:context];
    [object setValue:@(i) forKey:@"someField"];
}

NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"TestEntity"];
request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"someField" ascending:YES]];
request.fetchLimit = 30;

NSArray * result = [context executeFetchRequest:request error:nil];
[self printArrayOfTestEntities:result];

request.fetchOffset = 30;
result = [context executeFetchRequest:request error:nil];
[self printArrayOfTestEntities:result];

[context save:&error];

request.fetchOffset = 0;
result = [context executeFetchRequest:request error:nil];
[self printArrayOfTestEntities:result];

request.fetchOffset = 30;
result = [context executeFetchRequest:request error:nil];
[self printArrayOfTestEntities:result];

日志:

2014-03-01 11:30:06.986 coredatatest[19771:70b] fetched objects: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29  
count: 30
2014-03-01 11:30:06.990 coredatatest[19771:70b] fetched objects: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29  
count: 30
2014-03-01 11:30:06.995 coredatatest[19771:70b] fetched objects: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29  
count: 30
2014-03-01 11:30:06.997 coredatatest[19771:70b] fetched objects: 30 31 32 33   
count: 4

【讨论】:

  • 这个问题早就该了,我相信这是当时的实际问题。未保存的上下文。
  • 感谢这个答案,我可以在我的程序中找到一个错误。我只想补充一点,获取的对象是未保存的对象。我不敢相信 Apple 没有记录这个问题。
  • 有一点需要注意——这个例子是有效的,因为你使用的上下文是基本上下文而不是它的子上下文。即使在将更改保存到持久存储之前保存,在子上下文中运行相同类型的场景仍然会产生不好的结果。这是因为 fetchOffset 属性依赖于 sqlite 和磁盘上的数据库来运行。这确实应该在 Objective-c 中引起警告,但它们只会返回糟糕的结果。
【解决方案3】:

Core Data 内置了分页功能,非常简单,只需在获取请求上设置 fetchBatchSize。当设置 fetch 时,只查询所有对象作为故障,基本上只是一个指针及其行索引,它是一种特殊类型的数组的最小内存,称为批处理故障数组。我想这在大多数情况下都很好,尽管我还没有研究过它将用于大量行的确切内存量。然后,当您在访问错误记录的属性时循环此数组时,它会在数据库中查询该记录以及下一条记录,直到您为批量大小设置的数量。这使您可以像往常一样逐个循环您的结果,但在后台它会分批加载数据。从 iOS 9.2 开始,最小批量大小为 4,因此没有必要设置低于此的数字。

【讨论】:

    猜你喜欢
    • 2018-08-18
    • 2014-08-17
    • 2021-06-08
    • 1970-01-01
    • 2021-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-16
    相关资源
    最近更新 更多