Attaching Core Data Entities using Relationships - ios

I have two Core Data entities, Car and Services. I want a car to have multiple services so I made the car relationship to Services a to-many Type.
If I look at my database, I have a Car table with its attributes and a Services table with its attributes, both saving to their respective tables.
The problem is that I can not figure out to get a service to attach to a car. When looking at the Services table there is a column for Car. I know that there are some methods in the Car+CoreDataProperties.swift file such as addToServices(_ value: Service) & addToServices(_ value: NSSet) but when I try to call one of those methods and pass in my service data I get this error:
CoreData: error: +[Services entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
Here's the code that invokes that error:
func saveToCoreData() {
let car = Car(context: self.moc)
let service = Services(context: self.moc)
service.date = date
service.serviceType = self.serviceType
service.serviceCost = self.serviceCost
service.serviceComplete = self.servicecComplete
let serviceData: NSSet = [service.date, service.serviceType, service.serviceCost, service.serviceComplete]
car.addToServices(serviceData)
try? self.moc.save()
}
To possibly make all of this more difficult, the data is being entered via a form I made using SwiftUI.

Check self.moc.save(). If it failed there would be no changes visible in the database.
addToServices(_ value: Service) and addToServices(_ value: NSSet) are overloaded methods. The first one adds a (single) Service object to a Car object and the second adds a set of Service objects to a Car object.
In order to set a car's service(s) you have alternative ways to do that:
A) (as mentioned by #nighttalker) car.addToServices(service)
B) car.addToServices([service])
C) A) and B) add new service(s), but if you want to replace the currently existing services, or set service(s) of a car for the first time, use: car.services = [service].
About the error: car.addToServices(serviceData) failed because serviceData is neither of type Set<Service> nor Service.

Related

Fetching the latest entity from a relationship in Swift Core Data

Im trying to find the best way to get the latest entity from a relationship with swift and core data.
For example I have a Conversation with many Messages:
Conversation Entity:
id = 1, name = "Test"
Message Entity:
id = 1, body = "Test", conversation = 1
id = 2, body = "Test", conversation = 1
In my SwiftUI View I have this FetchRequest:
#FetchRequest(sortDescriptors: []) var conversations: FetchedResults<Conversation>
What would be the best way to access the relation and get the latest result while looping over the conversations in View like this:
List(conversations) { conversation in
Text(conversation.name ?? "")
Text(conversation.messages.latest.body ?? "") // <--- Trying to do something like this
}
The best way is to store the data you need in the entity you are fetching. So in your case make a latestMessageBody property on the conversation entity. Apple provide derived attributes to automate this.
It's a common mistake to think of Core Data as a relational database, instead just think of it as a way to persist what you see on screen and design your entities based on how you want to fetch and display them. If you are making too many fetches on one screen then its time to redesign the entities.
I.e. you wouldn't want to fetch the latest message for all the converstations you want to show that is too many fetches. And you don't want to load in all the messages for every conversation just to find the latest, that has too high memory overhead. This is why Core Data offers denormalisation via the derived attributes feature.
Another point is #FetchRequest (which is powered by NSFetchedResultsController by the way) only tracks changes to the entity that is currently fetched. So if you reach into relations to show data, that view won't update when the related object changes. You can work around this by passing the object into another View marked with #ObservedObject however that doesn't work for a 1-to-many relation.
There is a neat trick using entity inheritance to allow a NSFetchedResultsController to work on multiple entities however I don't think that is suitable for this use case.

Is there a way to access properties of an x-coredata:// object returned from an NSFetchRequest?

TL;DR: Is there a way to programmatically read/recall (NOT write!) an instance of a Core Data entity using the p-numbered "serial number" that's tacked on to the instance's x-coredata:// identifier? Is this a good/bad idea?
I'm using a method similar to the following to retrieve the instances of an Entity called from a Core Data data store:
var managedContext: NSManagedObjectContext!
let fetchRequest : NSFetchRequest<TrackInfo> = TrackInfo.fetchRequest()
fetchResults = try! managedContext.fetch(fetchRequest)
for (i, _) in Global.Vars.numberOfTrackButtons! {
let workingTrackInfo = fetchResults.randomElement()!
print("current track is: \(workingTrackInfo)")
The list of tracks comes back in fetchResults as an array, and I can select one of them at random (fetchResults.randomElement()). From there, I can examine the details of that one item by coercing it to a string and displaying it in the console (the print statement). I don't list the code below, but using workingTrackInfo I am able to see that instance, read its properties into other variables, etc.
In the console, iOS/Xcode lists the selected item as follows:
current track is: <MyProjectName.TrackInfo: 0x60000374c2d0> (entity:
TrackInfo; id: 0xa7dc809ab862d89d
<x-coredata://2B5DDCDB-0F2C-4CDF-A7B9-D4C43785FDE7/TrackInfo/p22>;
data: <fault>)
The line beginning with x-coredata: got my attention. It's formatted like a URL, consisting of what I assume is a UUID for the specific Core Data store associated with the current build of the app (i.e. not a stable address that you could hardcode; you'd need to programmatically look up the Core Data store, similar to the functions we use for programmatically locating the Documents Folder, App Bundle, etc.) The third item is the name of the Entity in my Core Data model -- easy enough.
But that last number is what I'm curious about. From examining the SQLite database associated with this data store, it appears to be a sort of "instance serial number" associated with the Z_PK field in the data model.
I AM NOT interested in trying to circumvent Core Data's normal mechanisms to modify the contents of a managed object. Apple is very clear about that being a bad idea.
What I AM interested in is whether it's possible to address a particular Core Data instance using this "serial number".**
In my application, where I'm randomly selecting one track out of what might be hundreds or even thousands of tracks, I'd be interested in, among other things, the ability to select a single track on the basis of that p-number serial, where I simply ask for an individual instance by generating a random p-number, tack it on to a x-coredata:// statement formatted like the one listed above, and loading the result (on a read-only basis!) into a variable for further use elsewhere in the app.
For testing purposes, I've tried simply hardcoding x-coredata://2B5DDCDB-0F2C-4CDF-A7B9-D4C43785FDE7/TrackInfo/p22 as a URL, but XCode doesn't seem to like it. Is there some other data Type (e.g. an NSManagedObject?) that allows you to set an x-coredata:// "URL" as its contents?
QUESTIONS: Has anyone done anything like this; are there any memory/threading considerations why grabbing instance names in this manner is a bad idea (I'm an iOS/Core Data noob, so I don't know what I don't know; please humor me!); what would the syntax/method for these types of statements be?
Thanks!
You are quite close.
x-coredata://2B5DDCDB-0F2C-4CDF-A7B9-D4C43785FDE7/TrackInfo/p22
is the uriRepresentation() of the NSManagedObjectID of the record.
You get this URL from an NSManagedObject with
let workingTrackInfo = fetchResults.randomElement()!
let objectIDURL = workingTrackInfo.objectID.uriRepresentation()
With this URL you can get the managed Object ID from the NSPersistentStoreCoordinator and the coordinator from the managed object context.
Then call object(with: on the context to get the object.
let persistentStoreCoordinator = managedContext.persistentStoreCoordinator!
if let objectID = persistentStoreCoordinator.managedObjectID(forURIRepresentation: objectIDURL) {
let object = managedContext.object(with: objectID) as! TrackInfo
print(object)
}

What is the way to persist a trie data tree with 27000 nodes in iOS using Swift?

I am building a Trie tree that will have about 27000 of the nodes below. Instead of recreating it every time on app start, I would like to persist. Because the child property is a dictionary to another node, I'm having trouble using NSCoding to archive and store it in the core data entity. Is there a way to store this node in Core Data? Or should I be using a different type of persistence?
class TrieNode {
var letter:Character
var fullWord:Bool
var leadingLetters:String
var child = [Character:TrieNode]()
init (letter:Character, leadingLetters:String, fullWord:Bool) {
self.letter = letter
self.fullWord = fullWord
self.leadingLetters = leadingLetters
}
}
The main problem I had in trying to use Core Data is how to convert var child = [Character:TrieNode]() into NSData or another useable type that CD can store in an entity. Examples on how to do that would be appreciated.
It's a little awkward in Core Data. I think what I'd do is:
Create a new entity called something like TrieNodeLink. It has one property, a string called something like childString and one relationship, called node of type TrieNode. Each instance of this entity represents one single sub-node of a trie node.
Add a new to-many relationship from your existing TrieNode to the new TrieNodeLink entity.
Keep your existing child dictionary. At a convenient time, initialize this dictionary by scanning the new to-many relationship from step 2. A convenient time might be in awakeFromFetch, or else you could make it a Swift lazy property. Or if you want to pre-load data for faster performance at the cost of higher memory use, you might write some code to recursively load child nodes a few levels deep before they're needed.
The effect of this would be that you'd load portions of the trie on demand, when needed. Once loaded you'd be able to use your child dictionary to quickly look up child nodes.

Cross-Store weak relationship with Fetched Properties?

I would like to separate my reference data from my user data in my Core Data model to simplify future updates of my app (and because, I plan to store the database on the cloud and there is no need to store reference data on the cloud as this is part of my application). Therefore, I've been looking for a while for a way to code a cross-store relationship using fetched properties. I have not found any example implementations of this.
I have a Core Data model using 2 configurations :
data model config 1 : UserData (entities relative to user)
data model config 2 : ReferenceData (entities relative to application itself)
I set up 2 different SQLite persistent stores for both config.
UserData config (and store) contains entity "User"
ReferenceData config (and store) contains entities "Type" and "Item".
I would like to create two single-way weak relationships as below :
A "User" has a unique "Type"
A "User" has many "Items"
Here are my questions :
How do I set up my properties?
Do I need 2 properties for each relation (one for storing Unique ID and another to access my fetched results)?
Could this weak relationship be ordered?
Could someone give me an example implementation of this?
As a follow-on to Marcus' answer:
Looking through the forums and docs, I read that I should use the URI Representation of my entity instance instead of objectID. What is the reason behind this?
// Get the URI of my object to reference
NSURL * uriObjectB [[myObjectB objectID] URIRepresentation];
Next, I wonder, how do I store my object B URI (NSURL) in my parent object A as a weak relationship? What attribute type should I use? How do I convert this? I heard about archive... ?
Then, later I should retrieve the managed object the same way (by unconvert/unarchive the URIRepresentation) and get Object from URI
// Get the Object ID from the URI
NSManagedObjectID* idObjectB = [storeCoordinator managedObjectIDForURIRepresentation:[[myManagedObject objectID] URIRepresentation]];
// Get the Managed Object for the idOjectB ...
And last but not least, shouId I declare two properties in my entity A, one for persisting of URI needs and another for retrieving direclty object B?
NSURL * uriObjectB [objectA uriObjectB];
ObjectB * myObjectB = [objectA objectB];
As you can read, I really miss some simple example to implement thes weak relationships ! I would really appreciate some help.
Splitting the data is the right answer by far. Reference data should not be synced with the cloud, especially since iCloud has soft caps on what it will allow an application to sync and store in documents.
To create soft references across to stores (they do not need to be SQLite but it is a good idea for general app performance) you will need to have some kind of unique key that can be referenced from the other side; a good old fashioned foreign key.
From there you can create a fetched property in the model to reference the entity.
While this relationship cannot be ordered directly you can create order via a sort index or if it has a logical sort then you can sort it once you retrieve the data (I use convenience methods for this that return a sorted array instead of a set).
I can build up an example but you really are on the right track. The only fun part is migration. When you detect a migration situation you will need to migrate each store independently before you build up your core data stack. It sounds tricky but it really is not that hard to accomplish.
Example
Imagine you have a UserBar entity in the user store and a RefBar entity in the reference store. The RefBar will then have a fetchedProperty "relationship" with a UserBar thereby creating a ToOne relationship.
UserBar
----------
refBarID : NSInteger
RefBar
--------
identifier : NSInteger
You can then create a fetched property on the RefBar entity in the modeler with a predicate of:
$FETCHED_PROPERTY.refBarID == identifier
Lets name that predicate "userBarFetched"
Now that will return an array so we want to add a convenience method to the RefBar
#class UserBar;
#interface RefBar : NSManagedObject
- (UserBar*)userBar;
#end
#implementation RefBar
- (UserBar*)userBar
{
NSArray *fetched = [self valueForKey:#"userBarFetched"];
return [fetched lastObject];
}
#end
To create a ToMany is the same except your convenience method would return an array and you would sort the array before returning it.
As Heath Borders mentioned, it is possible to add a sort to the NSFetchedProperty if you want but you must do it in code. Personally I have always found it wasteful and don't use that feature. It might be more useful if I could set the sort in the modeler.
Using the ObjectID
I do not recommend using the ObjectID or the URIRepresentation. The ObjectID (and therefore the URIRepresentation of that ObjectID) can and will change. Whenever you migrate a database that value will change. You are far better off creating a non-changing GUID.
The weak relationship
You only need a single value on the M side of the relationship and that stores the foreign identifier. In your object subclass you only need to implement accessors that retrieve the object (or objects).
I would go with just one store.
For storing stuff in the cloud, you will anyway have to serialize the data, either as JSON or SQL statements, or whatever scheme you prefer.
You will need a local copy of the data on the user's device, so he can access it quickly and offline. The cloud store can have only the user entity, while the local store (part of the app) can also have the reference entity.
I have a similar project with a huge reference store (20000 records) with geographic information, and user generated content ("posts"). I use a single store. When I ship the app, the "posts" entity is also defined but empty. When I update the data model I simply re-generate the whole reference store before shipping.
I see absolutely no reason to go for a cross store solution here.

Lazy fetching of objects using FindAllBy , for the first time

When I use criteria queries, the result contains array list of lazy initialized objects. that is, the list has values with handler org.codehaus.groovy.grails.orm.hibernate.proxy.GroovyAwareJavassistLazyInitializer.
This prevent me from doing any array operation (minus, remove etc) in it. When I use, GORM methods, I get array list of actual object types. How can I get the actual objects in criteria query?
The code is listed below.
availableTypes = Type.withCriteria() {
'in'("roleFrom", from)
'in'("roleTo", to)
}
availableTypes (an array list) has one value , but not actual object but value with a handler of GroovyAwareJavassistLazyInitializer
availableTypes (an array list) has values with type Type
availableTypes = Type.findByRoleFrom(from)
---------- Update ----------
I did further troubleshooting, and this is what I found. Probably the above description might be misleading, but I kept it in case it helps.
When using findAllBy for the first time, I get proxy objects rather than the actual instance. Then, I invoke the method through an ajax call, the actual instance is loaded (anything to do with cache loading??). When I refresh the page, it again loads the proxy
def typeFrom = Type.findAllByParty(partyFrom)
there is another use of findAllBy in the same method, which always returns actual instances.
def relFrom = Relation.findAllByParty(partyFrom)
When compared the two classes, the attribute 'party' of class Roles is part of a 1-m relation. like
class Role {
RoleType roleType
LocalDate validFrom
LocalDate validTo
static belongsTo = [party : Party ]
...
}
I know if I do statement like Party.findAll(), the role instances would be proxy till they access. But, when using gorm directly on the class (Role), why I am getting the proxy objects ???
thanks for the help.
thanks.
Turns out are a couple of possible solutions which I came across but didn't try, such as
Overloading the equals method so that the proxy and the domain
object use a primary key instead of the hashCode for equality
Using a join query so that you get actual instances back and not proxies
GrailsHibernateUtil.unwrapProxy(o)
HibernateProxyHelper.getClassWithoutInitializingProxy(object)
One solution that worked for me was to specify lazy loading to be false in the domain object mapping.
History of this problem seems to be discussed here: GRAILS-4614
See also: eager load

Resources