NSFetchRequest 的意外行为
Posted
技术标签:
【中文标题】NSFetchRequest 的意外行为【英文标题】:Unexpected behavior of NSFetchRequest 【发布时间】:2012-04-17 20:47:23 【问题描述】:我在上下文中创建了一些实体,以便使用
将其保存在数据库中AppCalendarEntity *appCalendar = [AppCalendarEntity getInstanceWithManagedDocument:manageDocument];
添加一些实体后,我执行流式提取请求
NSFetchRequest *requestToSeeIfCalendarWithIdExist = [NSFetchRequest fetchRequestWithEntityName:@"AppCalendarEntity"];
NSArray *result = [managedDocument.managedObjectContext executeFetchRequest:requestToSeeIfCalendarWithIdExist error:&InternalError] ;
它返回的结果只包括我使用第一个命令在上下文中添加的实体,而不是已经保存在数据库中的条目。我已经确定在这个阶段文档状态是 UIDocumentStateNormal。
当我将此行添加到已打开的文档 (UIDocumentStateNormal) 时,它会返回我预期的结果,即它从 db 以及尚未保存到 db 的内存上下文中获取结果。
[managedDocument openWithCompletionHandler:^(BOOL success)
NSFetchRequest *requestToSeeIfCalendarWithIdExist = [NSFetchRequest fetchRequestWithEntityName:@"AppCalendarEntity"];
NSArray *result = [managedDocument.managedObjectContext executeFetchRequest:requestToSeeIfCalendarWithIdExist error:&InternalError] ;
我的问题是 1-我希望查询的结果在这两种情况下应该是相同的。为什么在上述情况下不是这样。 2-对我来说,如果文档状态是 UIDocumentStateNormal,我不应该在上下文中调用“openWithCompletionHandler”来打开文档。在这种特殊情况下,它在 NSFetchRequest 中产生了什么不同,这在添加后给了我想要的结果。
如果我错了,请告诉我
这是完整的代码
这是函数的完整代码
+ (void ) saveCalendarArrayInDbIfItAlreadyDoesNotExist : (NSArray*) appCalendarArray managedDocument: (UIManagedDocument*) managedDocument completionBlock : ( void(^) (NSArray* ObjectSavedSuccesfully, NSError *InternalError)) handler
// i dont know why i have to do it :( if i dont add openWithCompletionHandler my query doesnt fetch result from db rather just do query in-memory context and not db
[managedDocument openWithCompletionHandler:^(BOOL success)
void (^completionHandler)(NSArray* , NSError* );
completionHandler = [handler copy ];
NSError *error = nil;
NSMutableArray *array = [[NSMutableArray alloc] init];
for (id appCalendar in appCalendarArray)
if([appCalendar isKindOfClass:[AppCalendarEntity class]])
AppCalendarEntity *appCalendarEntity = (AppCalendarEntity*) appCalendar;
NSFetchRequest *requestToSeeIfCalendarWithIdExist = [NSFetchRequest fetchRequestWithEntityName:@"MyEntity"];
requestToSeeIfCalendarWithIdExist.predicate = [NSPredicate predicateWithFormat:@"identifier = %@", appCalendarEntity.identifier];
NSError *InternalError = nil;
[requestToSeeIfCalendarWithIdExist setShouldRefreshRefetchedObjects:YES];
NSArray *result = [managedDocument.managedObjectContext executeFetchRequest:requestToSeeIfCalendarWithIdExist error:&InternalError] ;
// "result" is different when we encapsulate it in openWithCompletionHandler and when we don't…….MY PROBLEM
if(result == nil)
// return error
// 1 object always return that depict the in memory(context) object we created but not saved. I expect it should be zero because no object has yet been saved to database..
else if(result.count > 1)
[managedDocument.managedObjectContext deleteObject:appCalendar];
else
[array addObject:appCalendarEntity];
else
// error handling
if (error != nil)
completionHandler (nil, error);
return;
// saving all the objects
[ managedDocument updateChangeCount:UIDocumentChangeDone ];
【问题讨论】:
你确定这就是你所做的一切......只是将对象插入 MOC 并获取?我敢打赌,你在这之间还有其他事情……比如在 MOC 上调用 save,也许? 是的,因为我只在同一个函数中进行了这个更改,并且该函数开始工作。 抱歉,您说的是什么变化?你能发布插入然后查询的整个代码sn-p吗? 我正在使用 UIManagedDocument 类来管理和初始化文档,并且我正在使用 sqllite 数据库 是的,我已经在 [uimangeddocument openwithcompletionhandler...] 中封装了相同的函数,并且我从数据库中获取了结果。如果不使用它,它就不会从数据库中检索结果。我可以制作小样本,但您也可以从那里获得所有信息。 【参考方案1】:使用 UIManagedDocument 时,不要在 MOC 上调用 save,因为它实现了自动保存。但是,需要告知自动保存应该在未来的某个时间点进行。
去掉那个函数中对 openWithCompletionHandler 的调用(我知道它只是为了调试这个问题)。
替换
[managedDocument.managedObjectContext 保存:&InternalError]
与
[managedDocument updateChangeCount:UIDocumentChangeDone];
这将通知文档现在可以保存。
编辑
首先,我认为您应该摆脱调试技巧。您可以添加 NSLog 或 NSAssert,但其他内容只会让您很难说出您想要的原因,并且会混淆真正的问题。
其次,您在这里的真正目标是什么?我可以看到方法的名称,我可以看到代码,但它们不匹配。
这里有太多的“琐碎”,很难理解你的问题。我将重新发布您的代码,并进行编辑以删除“打开”的内容,并将问题注释为代码 cmets。
希望此更改能帮助您解决问题。
// First, the method name seems to indicate that some objects will be added
// to the database. however, the only database work in this method is removal.
// I don't get it.
+ (void ) saveCalendarArrayInDbIfItAlreadyDoesNotExist : (NSArray*) appCalendarArray managedDocument: (UIManagedDocument*) managedDocument
NSError *error = nil;
NSMutableArray *array = [[NSMutableArray alloc] init];
for (id appCalendar in appCalendarArray)
if([appCalendar isKindOfClass:[AppCalendarEntity class]])
// OK, we are filtering the array of objects. We are only interested in
// objects of type AppCalendarEntity, and are going to use its identity
// property to look for objects of type MyEntity.
// What is the relationship between AppCalendarEntity and MyEntity?
AppCalendarEntity *appCalendarEntity = (AppCalendarEntity*) appCalendar;
NSFetchRequest *requestToSeeIfCalendarWithIdExist = [NSFetchRequest fetchRequestWithEntityName:@"MyEntity"];
requestToSeeIfCalendarWithIdExist.predicate = [NSPredicate predicateWithFormat:@"identifier = %@", appCalendarEntity.identifier];
NSError *InternalError = nil;
[requestToSeeIfCalendarWithIdExist setShouldRefreshRefetchedObjects:YES];
NSArray *result = [managedDocument.managedObjectContext executeFetchRequest:requestToSeeIfCalendarWithIdExist error:&InternalError];
// OK, now we just got a result from searching for a MyEntity, where
// its identifier is the same as the appCalendarEntity.
if(result == nil)
// return error
// 1 object always return that depict the in memory(context) object we created but not saved. I expect it should be zero because no object has yet been saved to database..
else if(result.count > 1)
// I am extremely confused by this code. First, why are you
// checking for more than 1 object? The method name indicates
// you are going to insert something. Furthermore, you are only
// deleting one object. How many do you expect? Also, why are
// you deleting an appCalendar? You were searching for a MyEntity.
// If an appCalendar is a MyEntity, then that's terrible naming.
// Furthermore, it would explain why you are finding it...
// because you create entities by inserting them in a MOC to
// begin with!
[managedDocument.managedObjectContext deleteObject:appCalendar];
else
// Even more confusion. You are adding this object to an internal
// array, not the database. Furthermore, you are doing it if there
// are either 0 or 1 MyEntity objects in the database with matching
// identifier.
[array addObject:appCalendarEntity];
// saving all the objects
// OK - but the only thing being saved are the ones you deleted...
[ managedDocument updateChangeCount:UIDocumentChangeDone ];
最后,如果我的预感是正确的,并且日历对象实际上是 MyEntity 对象,那么它们已经在 MOC 中——因为它们就是这样创建的。当您执行 fetch 时,您可以强制搜索忽略待处理的更改(如我之前的一个 cmets 中所述)并且只接受已保存的更改。
如果您想忽略挂起的更改,
fetchRequest.includesPendingChanges = NO;
【讨论】:
感谢您纠正我的错误。但我的问题是 fetch query 的不同结果。您对这段代码有什么想法吗?还是我需要提供一些额外的细节? 我认为这是你的问题。您的保存不会保存到磁盘,因此当您稍后查找它们时,它们不存在。当你运行它时它做了什么? 我的数据正在被保存。因为即使在重新启动应用程序之后,我也会在添加“openwithcompletionhandlerfunction”后检索它。但是我无法在不使用此功能的情况下检索它,尽管文档状态是正常的。 使用您发布的代码,文档没有正确处理您的数据。您是否尝试过我建议的解决方案,还是只是假设它没有解决您的问题? 所以无法摆脱这个调用:(我没有尝试你的建议,因为在调用保存函数之前就出现了问题。【参考方案2】:@Jody 问题已解决,感谢您抽出时间来回答这个问题。
首先让我解决您的困惑
1- Yes 函数旨在保存在数据库中,它是一个帮助函数。传递给此函数的参数“appCalendarArray”由已在上下文中创建的实体组成。我故意消除了逻辑,因为它涉及与外部 api 通信、解析 json 等。在上下文中插入实体所需的代码已经包含在问题的第一部分中。
AppCalendarEntity *appCalendar = [AppCalendarEntity getInstanceWithManagedDocument:manageDocument];
2- 根据数据库中应该唯一的列,我从上下文中删除了已构建但尚未从上下文中保存的实体。如果我们已经在数据库中有对象的标识符,我们不想重新保存它。所以,我只是从上下文中删除它。此功能按预期工作,实体不会重新保存在数据库中。最后一行确实保存了留在上下文中的对象(如果有的话)。大多数时候有很多。
3- 抱歉,AppCalendarEntity 和 MyEntity 是一样的。
解决方案
我已经添加了这个标志 fetchRequest.includesPendingChanges = NO; ,删除数据库,重新启动 Xcode 并开始工作。谢谢你的坚持
【讨论】:
以上是关于NSFetchRequest 的意外行为的主要内容,如果未能解决你的问题,请参考以下文章