在 CoreData 中保存复杂的关系数据

Posted

技术标签:

【中文标题】在 CoreData 中保存复杂的关系数据【英文标题】:Save complex relational data in CoreData 【发布时间】:2021-11-05 09:46:41 【问题描述】:

我回到了使用 CoreData 的 SwiftUI 学习课程。我有三个实体:


User // Has many Customers
Customer // Belongs to User and has many PartExchanges
PartExchange // Belongs to customer

当用户在登录后首次安装应用程序时,我会获取一些要保存的初始数据(以上:客户、零件交换等...):


struct AuthResponse: Decodable 
    let error: String?
    let token: String?
    let userData: UserObject?
    let customers: [Customers]?

    struct UserObject: Decodable 
        let FirstName: String?
        let Surname: String?
        let EmailAddress: String?
        let UserID: String
    
    
    struct Customers: Decodable 
        let FirstName: String?
        let Surname: String?
        let EmailAddress: String?
        let Customer_ID: String
        let PartExchanges: [PartExchangeData]?
    


// In another file and not inside AuthResponse
struct PartExchangeData: Decodable 
    let Registration: String?
    let Customer_ID: String?
    let PartExchange_ID: String?
    let Variant: String?
    let Colour: String?


AuthResponse 仅在用户首次登录或重新安装应用以从我们的 API 获取初始数据时使用:


// The exact data I have

import SwiftUI


class AuthController 
    
    var emailUsername: String = ""
    var password: String = ""
    
    
    func login() -> Void 
        
        guard let url = URL(string: "http://localhost:4000/api/auth") else 
            print("Invalid URL")
            return
            
        
        
        var request = URLRequest(url: url)
        
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        let body: [String: AnyHashable] = [
            "emailUsername": emailUsername,
            "password": password
        ]
        request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
        
        // Make the request
        URLSession.shared.dataTask(with: request)  data, response, error in
            
            if let data = data 
                
                let decoder = JSONDecoder()
                
                decoder.dateDecodingStrategy = .iso8601
                
                if let decodedResponse = try?
                    decoder.decode(AuthResponse.self, from: data) 
                    DispatchQueue.main.async 
                        
                        if decodedResponse.error != nil 
                            // Tell user?
                            return
                        
                        
                        let userObject = UserModel()
                        userObject.createUser(authObject: decodedResponse)
                        
                    
                    
                    return
                
                
            
            print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
            
        .resume()
    
    


最后,UserModel

class UserModel: ObservableObject 
    private let fetchRequest: NSFetchRequest<User> = User.fetchRequest()
    private let viewContext = PersistenceController.shared.container.viewContext
    
    @Published var saved: Bool = false
    
    var firstName: String = ""
    var surname: String = ""
    var emailAddress: String = ""
    var token: String = ""
    var userId: String = ""
    
    init() ...
    
    public func createUser(authObject: AuthResponse) -> Void 
        
        do 
            // Create a user on first login
            let user = User(context: viewContext)
            let customer = Customer(context: viewContext)
            let partExchange = PartExchange(context: viewContext)
            //let userCustomers: [AuthResponse.Customers]
            
            user.firstName = authObject.userData!.FirstName
            user.surname = authObject.userData!.Surname
            user.emailAddress = authObject.userData!.EmailAddress
            user.token = authObject.token!
            user.userId = authObject.userData!.UserID
            
            
            
            // Save customers
            for cus in authObject.customers! 
                customer.firstName = cus.FirstName
                customer.surname = cus.Surname
                
                user.addToCustomers(customer)
                
                // save part exchanges
                for px in cus.PartExchanges! 
                    
                    partExchange.registration = px.Registration
                    partExchange.partExchangeId = px.PartExchange_ID
                    partExchange.variant = px.Variant
                    
                    customer.addToPartExchanges(partExchange)
                    
                
                
            

            
            try viewContext.save()
            
            saved = true
            print("ALL SAVED!!")
        
         catch 
            let error = error as NSError
            // If any issues, rollback? viewContext.rollback()
            fatalError("Could not save user: \(error)")
        
        
    
    
    public func logOut() 
        // Only remove the token....
      


我在使用这种方法时遇到的问题是保存时;它正在保存循环中的最后一个客户。

Xcode 为UserCustomerPartExchnage 生成了一些扩展,在User 内部,我看到了一个函数:@NSManaged public func addToCustomers(_ values: NSSet)


[..]

user.addToCustomers(<what-goes-here>)

我的用户实体正确保存。客户只有 api 数组中的最后一个数据。如何正确保存客户多的用户,每个客户有很多零件交换?

【问题讨论】:

您需要为每个循环中的每次迭代创建一个新对象,您不能重用同一个对象,因为这意味着您只是在更新它,并且只存储上次迭代的数据。所以将let customer = Customer(…let partExchange = PartExchange(… 移动到相应循环的开头 啊,好吧...所以我应该在每个实体的循环中都有try viewContext.save() 不需要,它会保存所有插入的对象。 我想我明白了。让我重构然后再回复你。谢谢 @JoakimDanielson 你是明星!!您可以创建一个答案,我会接受它。只取决于你。再次感谢您! 【参考方案1】:

您需要为每个循环中的每次迭代创建一个新对象,因为创建的每个对象都将作为单独的项目存储在 Core Data 中

所以把createUser改成这样

public func createUser(authObject: AuthResponse) -> Void         
    do 
        let user = User(context: viewContext)                       
        user.firstName = authObject.userData!.FirstName
        // more properties ...          
        
        for cus in authObject.customers! 
            let customer = Customer(context: viewContext)
            customer.firstName = cus.FirstName
            customer.surname = cus.Surname
            
            user.addToCustomers(customer)
            
            for px in cus.PartExchanges! 
                let partExchange = PartExchange(context: viewContext)
                partExchange.registration = px.Registration
                partExchange.partExchangeId = px.PartExchange_ID
                partExchange.variant = px.Variant
                
                customer.addToPartExchanges(partExchange)                    
                            
        
        
        try viewContext.save()
        
        saved = true
        print("ALL SAVED!!")
    
     catch let error = error as NSError 
        //Either log the error and return some status or throw it
        //FatalError is a bit to much in this situation          
        fatalError("Could not save user: \(error)")
    
    

【讨论】:

以上是关于在 CoreData 中保存复杂的关系数据的主要内容,如果未能解决你的问题,请参考以下文章

Swift 3 Core Data - 无法保存关系值

如何在复杂的 Core Data 模型中高效访问数据

Core Data 复杂关系

Swift 2.1 Core Data - 保存具有一对多关系的数据,但如果已经存在则不要添加异构数据

Core Data 对象有时不会保存到永久存储中

将 Core Data 数据库保存到 sqlite 文件会丢失一些递归/循环关系信息