Home > Net >  Save complex relational data in CoreData
Save complex relational data in CoreData

Time:11-06

I'm back on a learning course in SwiftUI using CoreData. I have three entities:


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

When user first installs the app after they logged in, I fetch some initial data to be saved (the above: customers, part exchnages etc...):


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 is only used when user first logs in or reinstalls the app to get the initial data from our 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()
    }
    
}

Last, the 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....
    }  
}

The issue I'm having with this approach is when saving; it's saving the last customer in the loop.

Xcode generated some extensions for User, Customer and PartExchnage and inside User, I see a function: @NSManaged public func addToCustomers(_ values: NSSet):


[..]

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

My User entity saves correctly. Customer only has the last data from the api array. How to correctly save the user with many customers, where the each customer has many part exchanges?

CodePudding user response:

You need to create a new object for each iteration in each of your loops since each object created will be stored as a separate item in Core Data

So change createUser like this

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)")
    }
    
}
  • Related