SwiftUI入门 – Core Data实体的关联关系(一对一、一对多、多对多)

置顶

菜鸟入门笔记,如有谬误之处还请大佬指出

深耕细作 笃行致远

相关文章

如需获取其他SwiftUI知识点,请移步至SwiftUI入门专栏

前言

在之前的 Core Data 相关章节中,我们学习了模型创建、添加实体和属性、数据排序、数据筛选、数据分页与分组,基本拉通 SwiftUI 入门阶段所必须掌握的 Core Data知识点。但截止目前我们已学习的知识点都是围绕单个实体对象在做一些操作,并未涉及到关联关系部分,而仅仅是在《SwiftUI入门 – Core Data初探与实践》一文中提到了这个概念。在常见的应用场景中关联关系被广泛的使用,例如用户与用户主页(一对一的关系,一个用户仅能拥有一个主页),用户与用户订单(一对多的关系,一个用户可以下多个订单),用户与用户角色(多对多的关系,一个用户可以兼任多个角色,一个角色可以赋予多个用户)。本章我们将创建一些实体与他们之间的关联关系并完成基本的查询与展示,亦作为 SwiftUI 入门中学习 Core Data 基础知识的终章。

概述

数据库的关联关系是指在关系型数据库中,通过一个或多个共同的数据字段将两个或多个数据表连接在一起的方式。这种连接允许我们在多个表中存储相关数据,并通过执行联接操作来检索、组合和分析这些数据。关联关系是关系型数据库的核心概念之一,能够有效地组织和管理大量复杂数据。

关联关系通常通过使用外键(Foreign Key)来实现。外键是一个表中的字段,它指向另一个表中的主键(Primary Key)。主键是唯一标识表中每一行的字段,而外键则用于建立跨表之间的联系。

这些主键和外键在Core Data中已帮助我们自动创建和完成关联,所以我们无需再设置他们的关联键和中间表,仅配置必要的关联关系即可。

以下是几种常见的数据库关联关系:

一对一(One-to-One)关联:

在一对一关联中,一个表的一条记录只与另一个表的一条记录关联。这种关联适用于将两个独立的实体信息分开存储,但有时需要将它们联系在一起。如前言所述,一个“用户”表可以与一个“用户主页”表关联,使每个用户记录关联一个唯一的用户主页记录。

一对多(One-to-Many)关联:

在一对多关联中,一个表的一条记录可以与另一个表的多条记录关联。这是最常见的关联类型之一。如前言所述,一个“用户”表可以与一个“订单”表关联,一个用户可以下多个订单。

多对多(Many-to-Many)关联:

多对多关联表示两个表中的多个记录可以相互关联。为了实现这种关联,通常需要一个中间表来连接这两个表。如前言所述,一个“用户”表和一个“角色”表可以通过一个“用户角色”表来实现多对多关联,表示一个用户可以兼任多个角色,一个角色可以赋予多个用户。

关联关系使数据库能够更好地管理数据之间的复杂关系,同时也能够提高查询的效率和数据的一致性。在设计数据库时,正确使用关联关系是十分重要的,这有助于保持数据的完整性和减少数据冗余。

一对一

如概要描述,一对一的关联关系需要两个实体

创建实体

创建 User 用户实体,再随意添加一些属性
SwiftUI入门 - Core Data实体的关联关系(一对一、一对多、多对多)

创建 Page 主页实体,添加一些属性

SwiftUI入门 - Core Data实体的关联关系(一对一、一对多、多对多)

然后回到 User 实体配置面板为其添加一个关联关系

SwiftUI入门 - Core Data实体的关联关系(一对一、一对多、多对多)
配置说明:

Relationshippage,表示指定关联关系的名称为 page,这个关系名称可以随意取,但最好得有意义,一眼能看出这个关系代表什么,这里用page表示用户的主页

DestinationPage,表示 User 实体的关联对方为 Page 实体

右侧面板中的 Type 则很重要,决定着实体的关联关系,这里我们选择的是(To One)即一对一

一对一的数据操作


import SwiftUI
import CoreData

struct ToOneView: View {
    // 获取 BestBeforeApp 传入的环境变量并赋值给 viewContext
    @Environment(\.managedObjectContext) var viewContext
    // 用户列表
    @FetchRequest(
        sortDescriptors: [
            NSSortDescriptor(keyPath: \User.timestamp, ascending: true)
        ]
    )
    var users: FetchedResults<User>
    
    var body: some View {
        VStack{
            ForEach(users){user in
                VStack{
                    Text(user.name!)
                    Divider()
                    // 判断关联 Page 主页实体是否存在
                    if(user.page != nil){
                        Text(user.page!.title ?? "")
                        Text(user.page!.content ?? "")
                    }
                }
            }
            Spacer()
            HStack{
                Button {
                    addUser()
                } label: {
                    Text("新增用户")
                        .frame(height: 50)
                        .frame(maxWidth: .infinity)
                        .foregroundColor(Color.white)
                        .background(Color.green)
                        .cornerRadius(8)
                }
            }
        }.padding()
    }
    
    func addUser(){
        let user = User(context: viewContext)
        let page = Page(context: viewContext)
        user.name = "张三"
        user.score = 50
        user.timestamp = Date()
        
        page.title = "张三的主页"
        page.content = "主页展示的内容"
        
        user.page = page
        
        do{
            try viewContext.save()
        }catch{
            fatalError("error \(error)")
        }
    }
}

执行逻辑:

我们创建 addUser 函数来新增一个用户,实例化 User 对象并赋值基础属性,然后实例化 Page 对象并将此对象赋值给 User.page,完成关联对象的新增,这里的 User.page 即我们在 Core Data模型文件中为 User 定义的关联关系名称 page。新增完成后使用 ForEach 来渲染数据,这里要注意去判断下 page 是否存在if(user.page != nil),值不存在会报错

展示结果如下:

SwiftUI入门 - Core Data实体的关联关系(一对一、一对多、多对多)

反向一对一

一对一和反向一对一仅仅是站在不同的角度上看待事物的关系,如果我们以 User 为主,正向一对一即 User 关联 Page,在这个关系中我们说 Page 则是 User的反向一对一关系。

我们进入Core Data模型文件中,配置反向关联关系

SwiftUI入门 - Core Data实体的关联关系(一对一、一对多、多对多)
这里我们是以Page实体为主题,要反向关联的是用户,等于是我们需要在Page的实例中,添加一个用户的实例信息,而第二步关联关系中的 Inverse 则要指定 User关联关系的 page 来建立反向关联关系

import SwiftUI
import CoreData

struct BelongToView: View {
    // 获取 BestBeforeApp 传入的环境变量并赋值给 viewContext
    @Environment(\.managedObjectContext) var viewContext
    // 主页列表
    @FetchRequest(
        sortDescriptors: [
            NSSortDescriptor(keyPath: \Page.title, ascending: true)
        ]
    )
    var pages: FetchedResults<Page>
    
    var body: some View {
        VStack{
            ForEach(pages){page in
                VStack{
                    Divider()
                    Text("标题:\(page.title!)")
                    Divider()
                    // 判断反向关联 User 用户实体是否存在
                    if(page.user != nil){
                        Text("用户名:\(page.user!.name ?? "")")
                        Text("用户积分:\(page.user!.score)")
                    }
                }
            }
            Spacer()
        }
        .padding()
    }
}

SwiftUI入门 - Core Data实体的关联关系(一对一、一对多、多对多)

一对多

创建 Order 实体
SwiftUI入门 - Core Data实体的关联关系(一对一、一对多、多对多)
配置 User 实体与 Order 实体的关联关系

SwiftUI入门 - Core Data实体的关联关系(一对一、一对多、多对多)
上图步骤一中,我们配置Relationship中的关系名称为 orders,名称采用复数寓意这个关系属性下有多个订单。步骤二Type类型则也要选择 To Many

一对多数据的操作

import SwiftUI
import CoreData

struct ToManyView: View {
    // 获取 BestBeforeApp 传入的环境变量并赋值给 viewContext
    @Environment(\.managedObjectContext) var viewContext
    // 用户列表
    @FetchRequest(
        sortDescriptors: [
            NSSortDescriptor(keyPath: \User.timestamp, ascending: true)
        ]
    )
    var users: FetchedResults<User>
    
    var body: some View {
        VStack{
            ForEach(users){user in
                VStack{
                    Text(user.name ?? "")
                    
                    Divider()
                    
                    if let orders = user.orders?.allObjects as? [Order]{
                        ForEach(orders){order in
                            VStack{
                                Text("订单号:\(order.no?.uuidString ?? "")")
                                Text("金 额:\(order.total ?? 0)")
                            }.padding(.vertical,10)
                        }
                    }
                }
            }
            
            Spacer()
            
            HStack{
                Button {
                    addUser()
                } label: {
                    Text("新增用户与订单")
                        .frame(height: 50)
                        .frame(maxWidth: .infinity)
                        .foregroundColor(Color.white)
                        .background(Color.blue)
                        .cornerRadius(8)
                }
            }
        }.padding()
    }
    
    func addUser(){
        let user = User(context: viewContext)
        user.name = "张三"
        user.score = 50
        user.timestamp = Date()
        
        let order1 = Order(context: viewContext)
        order1.no = UUID()
        order1.total = 100
        
        let order2 = Order(context: viewContext)
        order2.no = UUID()
        order2.total = 200
        user.orders = [order1,order2];
        
        do{
            try viewContext.save()
        }catch{
            fatalError("error \(error)")
        }
    }
}

新增逻辑与一对一唯一不同点在于user.page接受一个Page对象,而 user.orders 接受的是一个数组,我们只需要实例多个Order并拼装成一个数组传入即可。渲染逻辑则是通过User实例的关联关系属性user.orders?.allObjects拿到其所有关联对象再循环渲染

展示结果如下:

SwiftUI入门 - Core Data实体的关联关系(一对一、一对多、多对多)

多对多

如前言所述,用户与用户角色我们认为可以是多对多的关系,一个用户可以兼任多个角色,一个角色可以赋予多个用户

创建实体与关系:

SwiftUI入门 - Core Data实体的关联关系(一对一、一对多、多对多)
User 用户实体中配置与Role角色实体的关系
SwiftUI入门 - Core Data实体的关联关系(一对一、一对多、多对多)
Role 角色实体中配置与User用户实体的关系
SwiftUI入门 - Core Data实体的关联关系(一对一、一对多、多对多)
这里或许就有道友要提问了,不是应该是多对多吗?怎么还是一对多呢?这不合理

实则不然,多对多的概念是站在上帝视角去看用户和角色的关系,但站在其中任意一方的视角上双方都互为对方的一对多。在实际的应用中,同样我们只会以一方为主体去关联查询另一方的数据,所以配置时在双方的Type都是选择To Many(一对多)也是这个原因

当维度降到一对多时,代码和数据操作就跟上一小节的一对多毫无差别,这里就再重复实现,仅需改变代码中的实体即可

总结

  1. 我们了解了关联关系的基本概念和几种常见的关系
  2. 学习了Core Data中一对一、一对多和多对多实体和关联关系的创建,关联数据的操作
  3. 通过学习Core Data的相关章节,我相信已经能满足中小型App的本地化数据库应用

别错过精彩内容,快来关注我们的微信公众号【寻桃】!

原文链接:https://juejin.cn/post/7261064585007972411 作者:寻桃

(0)
上一篇 2023年7月30日 上午10:25
下一篇 2023年7月30日 上午10:36

相关推荐

发表回复

登录后才能评论