繁体   English   中英

核心数据查找或创建最有效的方式

[英]Core data find-or-create most efficient way

我有大约10000个实体' Message '的对象。 当我添加一个新的“ Message ”时,我想首先看它是否存在 - 如果它只是更新它的数据,但是如果它不创建它。

现在,“查找或创建”算法可以将所有Message对象的objectID '保存在一个数组中,然后通过它们进行过滤并使用existingObjectWithID:error:获取消息existingObjectWithID:error:

这很好但在我的情况下,当我使用existingObjectWithID获取'Message'时,然后尝试通过设置'Message'对象的属性并调用save:来设置和保存属性,在它的上下文中它不能正确保存它。 有没有人遇到这样的问题?

是否有更有效的方法来制作查找或创建算法?

首先, Message是CoreData实体的“坏”名称,因为苹果在内部使用它,它会在以后的开发中引起问题。
你可以在这里阅读更多相关信息

我注意到这里所有建议的解决方案都使用数组或获取请求。
您可能想要考虑基于字典的解决方案......

在单线程/上下文应用程序中,通过向新插入的对象(类型为Message )添加缓存(字典)并使用现有对象ID和键映射预填充缓存,可以在不增加太多负担的情况下完成。

考虑这个界面:

@interface UniquenessEnforcer : NSObject

@property (readonly,nonatomic,strong) NSPersistentStoreCoordinator* coordinator;
@property (readonly,nonatomic,strong) NSEntityDescription* entity;
@property (readonly,nonatomic,strong) NSString* keyProperty;
@property (nonatomic,readonly,strong) NSError* error;

- (instancetype) initWithEntity:(NSEntityDescription *)entity
                    keyProperty:(NSString*)keyProperty
                    coordinator:(NSPersistentStoreCoordinator*)coordinator;

- (NSArray*) existingObjectIDsForKeys:(NSArray*)keys;
- (void) unregisterKeys:(NSArray*)keys;
- (void) registerObjects:(NSArray*)objects;//objects must have permanent objectIDs
- (NSArray*) findOrCreate:(NSArray*)keys
                  context:(NSManagedObjectContext*)context
                    error:(NSError* __autoreleasing*)error;
@end

流:

1)在应用程序启动时,分配“唯一性强制执行器”并填充缓存:

//private method of uniqueness enforcer
- (void) populateCache
{
    NSManagedObjectContext* context = [[NSManagedObjectContext alloc] init];
    context.persistentStoreCoordinator = self.coordinator;

    NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:self.entity.name];
    [r setResultType:NSDictionaryResultType];

    NSExpressionDescription* objectIdDesc = [NSExpressionDescription new];
    objectIdDesc.name = @"objectID";
    objectIdDesc.expression = [NSExpression expressionForEvaluatedObject];
    objectIdDesc.expressionResultType = NSObjectIDAttributeType;

    r.propertiesToFetch = @[self.keyProperty,objectIdDesc];

    NSError* error = nil;

    NSArray* results = [context executeFetchRequest:r error:&error];
    self.error = error;
    if (results) {
        for (NSDictionary* dict in results) {
            _cache[dict[self.keyProperty]] = dict[@"objectID"];
        }
    } else {
        _cache = nil;
    }
}

2)当你需要测试存在时,只需使用:

- (NSArray*) existingObjectIDsForKeys:(NSArray *)keys
{
    return [_cache objectsForKeys:keys notFoundMarker:[NSNull null]];
}

3)当你想要实际获取对象并创建缺失对象时:

- (NSArray*) findOrCreate:(NSArray*)keys
                  context:(NSManagedObjectContext*)context
                    error:(NSError* __autoreleasing*)error
{
    NSMutableArray* fullList = [[NSMutableArray alloc] initWithCapacity:[keys count]];
    NSMutableArray* needFetch = [[NSMutableArray alloc] initWithCapacity:[keys count]];

    NSManagedObject* object = nil;
    for (id<NSCopying> key in keys) {
        NSManagedObjectID* oID = _cache[key];
        if (oID) {
            object = [context objectWithID:oID];
            if ([object isFault]) {
                [needFetch addObject:oID];
            }
        } else {
            object = [NSEntityDescription insertNewObjectForEntityForName:self.entity.name
                                                   inManagedObjectContext:context];
            [object setValue:key forKey:self.keyProperty];
        }
        [fullList addObject:object];
    }

    if ([needFetch count]) {
        NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:self.entity.name];
        r.predicate = [NSPredicate predicateWithFormat:@"SELF IN %@",needFetch];
        if([context executeFetchRequest:r error:error] == nil) {//load the missing faults from store
            fullList = nil;
        }
    }

    return fullList;
}

在此实现中,您需要自己跟踪对象的删除/创建。
成功保存后,您可以使用注册/取消注册方法(简单实现)。
您可以通过挂钩上下文“保存”通知并使用相关更改更新缓存来使其更加自动化。

多线程案例要复杂得多(相同的界面,但考虑到性能时,完全不同的实现)。
例如,您必须让强制执行者在将它们返回到请求上下文之前保存新项目(到商店),否则它们没有永久ID,即使您调用“获取永久ID”,请求上下文也可能最终无法保存。
您还需要使用某种调度队列(并行或串行)来访问缓存字典。

一些数学:

鉴于:
10K(10 * 1024)个唯一密钥对象
平均密钥长度为256 [字节]
objectID长度为128 [byte]
我们正在寻找:
10K *(256 + 128)=〜4 [MB]的内存

这可能是一个很高的估计,但你应该考虑到这一点......

好吧,很多事情都可能出错,这是如何:

  1. 创建NSManagedObjectContext - > MOC
  2. 使用正确的实体创建NSFetchRequest
  3. 创建NSPredicate并将其附加到获取请求
  4. 对新创建的上下文执行获取请求
  5. 获取请求将返回与谓词匹配的对象数组(如果您的ID不同,则该数组中应该只有一个对象)
  6. 将数组的第一个元素强制转换为NSManagedObject
  7. 改变它的财产
  8. 保存上下文

最重要的是你使用相同的上下文来获取和保存,你必须在同一个线程中执行它,因为MOC不是线程安全的,这是人们做的最常见的错误

目前你说你维护一个`objectID'的数组。 当您需要时:

过滤它们并使用existingObjectWithID:error:获取消息existingObjectWithID:error:

在此之后你需要检查你收到的消息:

  1. 存在
  2. 匹配你想要的那个

这是非常低效的。 这是低效的,因为您始终将对象从数据存储中取回到内存中。 您也是单独进行(不进行批处理)。 这基本上是你可能做到的最慢的方式。

为什么未正确保存对该对象的更改尚不清楚。 你应该得到某种错误。 但是,你应该真正改变你的搜索方法:

而不是循环和加载,使用带谓词的单个提取请求:

NSFetchRequest *request = ...;
NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"XXX == %@", YYY];

[request setPredicate:filterPredicate];
[request setFetchLimit:1];

其中XXX是要测试的消息中的属性的名称, YYY是要对其进行测试的值。

当您在MOC上执行此提取时,您应该得到一个或零响应。 如果您得到零,则创建+插入新消息并保存MOC。 如果你得到一个,更新它并保存MOC。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM