【问题标题】:MagicalRecord findByAttribute returning inverse relationship documents issueMagicalRecord findByAttribute 返回反向关系文档问题
【发布时间】:2015-06-27 20:29:15
【问题描述】:

我在弄清楚如何使用 Core Data 和 MagicalRecord 建模/获取项目时遇到问题。我有两个实体,UserMessage,它们彼此之间存在反向关系:

用户实体

Relationship    Destination    Inverse    Type
-------------------------------------------------
messages        Message        user       To Many

消息实体

Relationship    Destination    Inverse    Type
-------------------------------------------------
user            User           messages   To One

问题

当获取给定用户的消息时,MagicalRecord 返回 2 倍的记录数,因为它在 useruser.messages.user 键路径上匹配属性:

- (NSArray *)fetchMessagesByUser:(NSString *)identifier {

    // returning 2x the records it should
    return [Message MR_findByAttribute:@"user.identifier" withValue:identifier];
}

我应该改变我使用 MagicalRecord 的方式还是我的核心数据模型存在根本问题?

【问题讨论】:

    标签: ios cocoa core-data magicalrecord


    【解决方案1】:

    当我使用 to many 关系设置一个带有 MagicalRecord 的应用程序,然后运行该应用程序几次时,用户一次又一次地被保存,所以我看到消息的数量增加了一倍(和三倍),因为同一个用户已保存多次。

    如果您尝试在代码中更改用户的属性值——以便在下次运行程序时创建具有唯一属性的用户——我想你会看到消息的数量将是你期待。

    您可以通过添加此代码查看所有用户:

    NSArray* users = [User MR_findAll];
    
    for(User* user in users) {
        NSLog(@"%@", [user name]);  //Log whatever attributes you want
    }
    

    如果我是对的,那将表明您有多个具有相同属性的用户。

    ========

    由于有关 MagicalRecord 的信息很少,我将发布我为设置应用程序所做的事情。我使用了 Xcode 6.3.2。

    1) 我创建了一个 OSX Cocoa 应用程序,我将其命名为 MagicalRecord2。我做了NOT 检查使用核心数据。

    2) 在终端窗口中,我执行了命令:

    ~$ gem install cocoapods  //However, you probably need to use sudo(see below)
    

    在我的系统上,我安装了多个版本的 ruby​​(使用 rvm 来管理它们),你不应该将sudo 与 rvm 一起使用,但如果你只有 ruby​​ 的系统安装,那么你将需要使用sudo 安装 gem:$ sudo gem install cocoapods

    3)在终端中,我将目录(cd)更改为我的项目目录:

    ~$ cd ~/xcode_projects/MagicalRecord2/
    

    4) 在项目目录中,我用这个创建了一个名为Podfile 的文件 内容:

    pod "MagicalRecord"
    

    然后我保存了文件。

    5) 然后我运行命令

    ~/xcode_projects/MagicalRecord2$ pod update
    

    该命令完成后,它会打印出消息:

    [!] 请关闭所有当前的 Xcode 会话并使用 从现在开始MagicalRecord2.xcworkspace 这个项目。

    所以我退出了 Xcode,并使用 Finder 导航到我的项目目录,然后单击 MagicalRecord2.xcworkspace 打开 Xcode。如果您在 Project Navigator 中展开一些组,您很快就会看到普通的 AppDelegate.h/.m 文件等。

    6) 在 AppDelegate 中(或者您可以创建一个 NSWindowController 子类),我添加了以下导入:

    #import <MagicalRecord/MagicalRecord.h>
    

    7) 然后我创建了模型:File&gt;New&gt;OSX:CoreData&gt;Data Model

    我将文件命名为 MyModels.xcdatamodeld 并接受默认值。然后在 Project Navigator 中,我单击了 MyModels.xcdatamodeld 文件,它打开了实体编辑器。在实体编辑器的底部,我单击了添加实体。然后我双击“实体”并将“实体”更改为“用户”。在属性下,我单击了+,并添加了一个字符串类型的name 属性。以类似的方式,我添加了一个带有字符串类型“文本”属性的消息实体。

    然后我再次点击User实体,在Relationships部分我点击+,在Relationship列下我输入messages(小写, 复数),对于Destination,我选择了Message。接下来,在 Xcode 窗口的最右侧,顶部有一组三个图标,我选择了第三个图标——Data Model Inspector。突出显示messages 关系后,如果您查看数据模型检查器,您将看到一个标题为Relationship,在该部分中您可以看到Type 的值为To One。把Type改成To Many

    接下来,在实体编辑器的左侧,点击Message 实体,然后在关系部分点击“+”,并将关系命名为“用户”(单数,小写),对于 Inverse 选择 messages

    如果您返回 User 实体,您会看到在 Relationships>Inverse 下它现在显示为 user

    数据模型检查器中还有其他值得设置的设置,但您必须阅读这些设置。对于这个演示,不需要其他任何东西。

    8) MyModels.xcdatamodeld 在项目导航器中仍然突出显示,向上看屏幕顶部,然后在 Xcode 菜单栏中选择 Editor&gt;Create NSManagedObject Subclass。在弹出窗口中,我检查了MyModels,然后单击下一步,然后检查了UserMessage,然后再次单击下一步。下一个屏幕真的把我搞砸了:Xcode 询问您要将要创建的新文件保存在哪里。默认情况下,Xcode 会将文件添加到项目目录之外,但它们会出现在 Project Navigator 中,因此它们看起来就像在项目中一样。但后来,当我尝试导入这些文件时,我收到一条错误消息,提示找不到文件。我的回答是,“什么~!@#~!@#!@你的意思是什么??!文件就在项目导航器中!!”

    因此,请确保您仔细选择将文件添加到项目内的组(即文件夹)中,例如我的 MagicalRecord2 组(文件夹)。这有点令人困惑,因为有两三个名为“YourProjectName”的东西,当您选择保存新文件的位置时,您必须导航到名为“YourProjectName”的组(文件夹)。不要接受默认位置。

    如果操作正确,您将在“YourProjectName”组(文件夹)中看到新文件 User.h/.m 和 Message.h/.m。如果你做错了,它会是什么样子:

    查看顶部的文件如何不在 MagicalRecord2 组(文件夹)中?

    9) 现在设置 MagicalRecord:

    //
    //  AppDelegate.m
    //  MagicalRecord2
    //
    
    
    #import "AppDelegate.h"
    #import <MagicalRecord/MagicalRecord.h>
    
    @interface AppDelegate ()
    
    @property (weak) IBOutlet NSWindow *window;
    @end
    
    @implementation AppDelegate
    
    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
        // Insert code here to initialize your application
    
        [MagicalRecord setupCoreDataStackWithStoreNamed:@"MyDatabase.sqlite"];
    }
    
    - (void)applicationWillTerminate:(NSNotification *)aNotification {
        // Insert code here to tear down your application
    
        [MagicalRecord cleanUp];
    }
    
    @end
    

    如您所见,您只需要两行代码来设置 Magical Record,然后由它负责为您设置 Core Data。

    10) 接下来,编写一个方便的方法,您可以调用它来创建和保存新用户:

    #import "AppDelegate.h"
    #import <MagicalRecord/MagicalRecord.h>
    #import "User.h"
    #import "Message.h"
    
    @interface AppDelegate ()
    ...
    ...
    
    - (void)persistNewUserWithName:(NSString*) name {
    
        NSManagedObjectContext* defaultContext =
        [NSManagedObjectContext MR_defaultContext]; //[NSManagedObjectContext MR_contextForCurrentThread] => deprecated
    
        User* myUser = [User MR_createEntityInContext:defaultContext]; //Requires that you import User.h.
        [myUser setName: name];
    
        Message* msg1 = [Message MR_createEntityInContext:defaultContext];  //Requires that you import Message.h
        [msg1 setText:@"Hello"];
        [msg1 setUser:myUser];
    
        Message* msg2 = [Message MR_createEntityInContext:defaultContext];
        [msg2 setText:@"Goodbye"];
        [msg2 setUser:myUser];
    
        //I commented out the following line so that the database starts out  
        //empty every time I run the project:
    
        //[defaultContext MR_saveToPersistentStoreAndWait];  //[defaultContext MR_save] => deprecated
    
    }
    
    @end
    

    11) 使用 MagicalRecord 查询 Core Data:

    @implementation AppDelegate
    
    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
        // Insert code here to initialize your application
    
        [MagicalRecord setupCoreDataStackWithStoreNamed:@"MyDatabase.sqlite"];
    
    
        [self persistNewUserWithName:@"Rokkor"];
        NSArray* users = [User MR_findAll];
        NSLog(@"User count: %lu", (unsigned long)[users count]);
    
        for(User* user in users) {
            NSLog(@"%@", [user name]);
        }
    
        NSArray* matchingMessages =
        [Message MR_findByAttribute:@"user.name" withValue:@"Rokkor"];
        //When you search in the Message entity, as above, you can write "user.name", 
        //but if you search in the User entity, you cannot write "messages"
    
        for (Message* msg in matchingMessages) {
            NSLog(@"%@", [msg text]);
        }
    
    
    
    }
    

    我收到一条错误消息,提示找不到文件 NSManagedObject.h。在 User.m 中,有一行:

    #import NSManagedObject.h
    

    我注释掉了那行,一切似乎都奏效了。 Xcode 创建了那个文件,我不知道为什么它会为一个不存在的文件创建一个带有 import 语句的文件。

    这是控制台输出:

    2015-06-28 19:42:24.845 MagicalRecord2[2924:73871] Created new private queue context: <NSManagedObjectContext: 0x6000001e7100>
    2015-06-28 19:42:24.845 MagicalRecord2[2924:73871] Set root saving context: <NSManagedObjectContext: 0x6000001e7100>
    2015-06-28 19:42:24.845 MagicalRecord2[2924:73871] Created new main queue context: <NSManagedObjectContext: 0x6080001e4900>
    2015-06-28 19:42:24.846 MagicalRecord2[2924:73871] Set default context: <NSManagedObjectContext: 0x6080001e4900>
    2015-06-28 19:42:24.847 MagicalRecord2[2924:73871] User count: 1
    2015-06-28 19:42:24.847 MagicalRecord2[2924:73871] Rokkor
    2015-06-28 19:42:24.848 MagicalRecord2[2924:73871] Hello
    2015-06-28 19:42:24.848 MagicalRecord2[2924:73871] Goodbye
    

    这里有几个其他选项:

    1) 可以使用block来获取defaultContext并保存数据:

     [MagicalRecord saveWithBlock:^(NSManagedObjectContext* defaultContext) {
    
         User* myUser = [User MR_createEntityInContext:defaultContext];
         [myUser setName: name];
    
    
         Message* m1 = [Message MR_createEntityInContext:defaultContext];
         [m1 setText:@"Hello"];
         [m1 setUser:myUser];
    
         Message* m2 = [Message MR_createEntityInContext:defaultContext];
         [m2 setText:@"Goodbye"];
         [m2 setUser:myUser];
    
     }];
    

    大概,saveWithBlock() 是这样定义的:

    (void)saveWithBlock:(void(^)(NSManagedObjectContext*))yourBlock {
    
        NSManagedObjectContext* context =
            [NSManagedObjectContext MR_defaultContext]; 
    
        yourBlock(context)
    
        [context MR_saveToPersistentStoreAndWait];
    }
    

    因此,saveWithBlock() 会自动保存您添加到上下文中的任何内容。

    2) 查询数据时可以使用过滤器:

    NSPredicate* messagesFilter =
    [NSPredicate predicateWithFormat:@"user.name == %@ && text == %@",
     @"Rokkor", @"Hello"];
    
    NSArray* matchingMessages = [Message MR_findAllWithPredicate:messagesFilter];
    
    for (Message* msg in matchingMessages) {
        NSLog(@"%@", [msg text]);
    }
    

    【讨论】:

    • 这是一个非常彻底的答案。我敢肯定,偶然发现它的几个人会发现它很有用。谢谢。
    猜你喜欢
    • 1970-01-01
    • 2019-01-07
    • 1970-01-01
    • 2018-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多