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

Save complex relational data in CoreData

我回到了使用 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 数组中的最后一个数据。如何正确保存客户多的用户,每个客户有很多零件交换?

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

所以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)")
    }
    
}