我的小应用程序中有一个数据模型,有两个小表:Test和subTest,其中Test可以有很多子测试。
在Test表中我们可以添加任意数量的项目,对于Test表中的每个项目,subTest表中都有一堆项目。
我可以通过使用CoreData谓词然后使用valueForKeyPath轻松地从与Test1,Test2等关联的subTest表中请求数据。但是,我有将数据保存到subTest表的问题。我无法管理,如何以某种方式写入数据,以后我可以通过传递测试名称(例如Test1)然后使用valueForKeyPath获取此数据。
这是一个澄清我的意思的例子。
在表中,Test是与一组子测试(st1,st2)相关联的Test1。现在必须将subtest st3添加到subTest表中。稍后我们想要获得与Test1相关的一系列子测试:
Test test = call here the method which returns NSManagedObject;
NSMutableSet setOfSubTests = [test valueForKeyPath testTosubtests.toTest]; /* Returns st1 st2 and st3 */
是否只是使用旧学校索引将数据写入subTest表? (Test1有索引1,在subTest表中与之关联的所有子测试都有Test1索引)?在iOS中使用CoreData是正确的吗?我可以应用与常见SQL DB相同的原则吗?
所有这些东西(CoreData和NSManagedObject)对我来说都是新手。我希望更好地理解它们。
答案 0 :(得分:1)
您需要停止将核心数据视为数据库表。核心数据旨在管理相关对象的集合。它的底层实现可能是SQL数据库,平面二进制文件或某些自定义存储实现。 不要将其视为包含表格的数据库。
现在,至于你的例子......
我的小应用程序中有一个数据模型,有两个小表: 测试和子测试,其中测试可以有许多子测试。
您将拥有一个Test实体和一个Subtest实体。测试实体与Subtest有多对多的关系,因为一个测试可以“持有”许多Subtest实体。
Subtest实体与Test有一对一的关系,因为Subtest只能属于一个Test。
我在代码中理解的东西更好,因此在代码中描述时,模型可能看起来像这样。
- (NSManagedObjectModel*)modelForTestsAndSubtests {
NSEntityDescription *testEntity = [[NSEntityDescription alloc] init];
testEntity.name = @"Test";
NSAttributeDescription *testName = [[NSAttributeDescription alloc] init];
testName.name = @"name";
testName.attributeType = NSStringAttributeType;
NSEntityDescription *subtestEntity = [[NSEntityDescription alloc] init];
subtestEntity.name = @"Subtest";
NSAttributeDescription *subtestName = [[NSAttributeDescription alloc] init];
subtestName.name = @"name";
subtestName.attributeType = NSStringAttributeType;
// A Test can have many Subtest objects in its relationship
NSRelationshipDescription *testToSubtests = [[NSRelationshipDescription alloc] init];
testToSubtests.optional = YES;
testToSubtests.name = @"subtests";
testToSubtests.destinationEntity = subtestEntity;
testToSubtests.deleteRule = NSCascadeDeleteRule;
testToSubtests.minCount = testToSubtests.maxCount = 0;
testToSubtests.ordered = NO;
// A Subtest can (and must) reference exactly one Test
NSRelationshipDescription *subtestToTest = [[NSRelationshipDescription alloc] init];
subtestToTest.optional = NO;
subtestToTest.name = @"test";
subtestToTest.destinationEntity = testEntity;
subtestToTest.inverseRelationship = testToSubtests;
subtestToTest.deleteRule = NSNullifyDeleteRule;
subtestToTest.minCount = subtestToTest.maxCount = 1;
testToSubtests.inverseRelationship = subtestToTest;
testEntity.properties = @[testName, testToSubtests];
subtestEntity.properties = @[subtestName, subtestToTest];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] init];
model.entities = @[testEntity, subtestEntity];
return model;
}
但是,我遇到了将数据保存到subTest表中的问题。
同样,不要将其视为将数据保存到表中。可以把它想象成“我要在”Test1“中添加subtest”st1“
你会这样做......
NSManagedObject *subtest = [NSEntityDescription
insertNewObjectForEntityForName:@"Subtest"
inManagedObjectContext:test.managedObjectContext];
[subtest setValue:test forKey:@"test"];
请注意,当您将Subtest实体中的一对一关系分配给Test实体时,核心数据将自动设置反向关系,因此您不必将子测试添加到Test中的to-many关系实体。
现在,假设您插入10个子测试(这仅用于测试)......
for (int i = 0; i < 10; ++i) {
NSManagedObject *subtest = [NSEntityDescription
insertNewObjectForEntityForName:@"Subtest"
inManagedObjectContext:moc];
[subtest setValue:[NSString stringWithFormat:@"st%02d", i] forKey:@"name"];
[subtest setValue:test forKey:@"test"];
}
[moc save:&error]; // Handle failure and error appropriately...
我无法管理,如何以某种方式写入数据,以后我可以 通过传递测试名称(例如Test1)然后使用来获取此数据 valueForKeyPath。
所以,如果你想获得名为“Test1”的测试,你可以写这样的东西......
- (NSManagedObject*)existingTestWithName:(NSString*)name
inMOC:(NSManagedObjectContext*)moc
error:(NSError**)error {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Test"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name = %@", name];
fetchRequest.fetchLimit = 1;
NSManagedObject *result = nil;
NSArray *fetched = [moc executeFetchRequest:fetchRequest error:error];
if (fetched) {
if (error) *error = nil;
result = [fetched firstObject];
}
return result;
}
然后,您可以像这样抓住'Test1'......
NSManagedObject *test = [self existingTestWithName:@"Test1" inMOC:moc error:&error];
if (test) {
// Do something with the Test entity that has name "Test1"
}
然后,一旦有了测试对象,就可以通过“子测试”关系访问该测试实例的所有Subtest对象。
NSSet *subtests = [test valueForKey:@"subtests"];
因此,您只需搜索集合即可找到特定的子测试...
- (NSManagedObject*)findExistingSubtestWithName:(NSString*)name
forTest:(NSManagedObject*)test {
// This is "simple" but could yield less than optimal performance
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@", name];
NSSet *subtests = [test valueForKey:@"subtests"];
return [[subtests filteredSetUsingPredicate:predicate] anyObject];
}
但是,这会导致所有子测试都加载到内存中以执行迭代搜索。如果物体相对较小,并且有少量物体,则可以。但
或者,您实际上可以执行获取...
- (NSManagedObject*)fetchExistingSubtestWithName:(NSString*)name
forTest:(NSManagedObject*)test
error:(NSError**)error {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Subtest"];
fetchRequest.predicate = [NSPredicate
predicateWithFormat:@"test = %@ AND name = %@", test, name];
fetchRequest.fetchLimit = 1;
NSManagedObject *result = nil;
NSArray *fetched = [test.managedObjectContext executeFetchRequest:fetchRequest
error:error];
if (fetched) {
if (error) *error = nil;
result = [fetched firstObject];
}
return result;
}
由于您似乎熟悉SQL,因此以下是核心数据为上面的fetch生成的SQL。
SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZTEST
FROM ZSUBTEST t0 JOIN ZTEST t1 ON t0.ZTEST = t1.Z_PK
WHERE ( t0.ZNAME = ? AND t1.ZNAME = ?) LIMIT 1
但是,这只是为了向您展示如何进行提取。您仍然应该将所有内容都视为互连对象,并且在您需要关注性能问题之前不要担心存储是SQL的实现。
您可以在Xcode和第三方工具(如mogenerator)中生成子类代码,这些工具可以极大地增强您使用托管对象的体验。但是,知道这一切是如何运作的还是很好的。