How to add unique constraints for some fields in Core Data - ios

I use Xcode for iOS development. I have some entity (for example, User), and I need to set unique constraint for his name, but I can't find how I can do it through visual editor. Is it possible to do it through GUI? Or it's possible through code only? I will be glad to get some screenshot.

There's a new section in the sidebar when selecting an entity in the editor for Core Data. You can set what constraint(s) you want to be unique across all instances of an entity
For automatic conflict resolution during saves, you'll need to make sure you've got a merge policy set for your managed object context or else you'll just get errors when saving (which might actually be what you want)
[managedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
The "Swift version" is exactly the same
managedObjectContext.mergePolicy = .mergeByPropertyObjectTrumpMergePolicyType
Keep in mind conflict resolution only happens during saves, and not inserts. So if you're making use of a NSFetchedResultsController you will see entities with non-unique constraints as they're inserted.
If you want to sure you have no entities with non-unique constraints in your managed object context without saving (if you're making use of a FRC), this answer is still probably the best way to go. Although, keep in mind, it's expensive if you're doing a lot of inserts, since NSFetchRequests are expensive operations.
Sample code for this demo can be found here

Swift solution:
As noted in the other answer, you can have unique constraints in Core Data for iOS9 onwards.
To do this, first add constraints to the Entity from the Core Data Editor (explaination in Zachary's answer).
Then add this line in code:
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
Note: The uniqueness is checked when you do managedObjectContext.save() not while you're just adding objects to the managed object.
NSMergeByPropertyObjectTrumpMergePolicy is just one of the merge policies, which overwrites the old value with the new one. You might want to check the documentation for other options.

swift Version Is Easy
Disclaimer : If You Have Conflicting information please delete piror to implementation. Else App will not run.
Solution Delete from device and start again
steps are:
Open Core Data File (projectname.xcdatamodeld)
Click on the entity name (needs to be highlighted)
Right Side of screen (in class section) find constraints (hit the plus button)
Right click to edit info rename to Attribute.
// now to add the code in you core data container
open AppDelegate.swift file and scroll into the coredata stack (" // MARK: - Core Data Saving support ")
update the code for the static func saveContext() { let variable = persistentContainer.viewContext "
//now make this simple call that manages update process
variable".mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy"
// clear understanding
static func saveContext () {
let context = persistentContainer.viewContext
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
// you need that line
if context.hasChanges {
do {
try context.save()
} catch {

Related

Want to save Core Data but "Extensions must not contain stored properties"

I have a Core Data class called "CloudPage", and I've added an extension for it in my code:
extension CloudPage:GalleryItem {
// bunch of methods defined here
func someFunc() -> Bool {
return true
}
It made sense to me to extend this class to have all model information reside here. I also thought it made sense for this class to know how to save an instance of itself, so I added my viewcontext to it:
extension CloudPage:GalleryItem {
#Environment(\.managedObjectContext) private var viewContext
// ...some code...
But now swift says "Extensions must not contain stored properties". Is there a better way to do this? I would think all saving logic should reside on this model.
As the compiler already say, it is not allow to add stored properties in Extension. CloudPage is an NSManagedObject which means it is already managed by CoreData.
Anyway this is not how you work with CoreData Objects. I'll clarify some stuff
These objects are managed/loaded in a context. There are 2 types of contexts
ViewContext (should be used for view purposes)
BackgroundContext (should be used for loading stuff from e.g. APIs in the background)
If you update objects or add new ones, these already live in said context. To persist these objects you don't have to "push" them in there anymore like you'd do it with plain SQL (UPDATE table...). You simply have to save the context like
// context is the one you loaded the objects from and worked with
context.save()
I hope this helps, i just wanted to sum it up roughly for you to get a better understanding. Don't become desperate, CoreData has its quirks and you'll get used to it. For more details read Apples Documentation of working with Core Data.

CoreStore create object in context without saving to database

I want to solve next problem:
I would like to work with some NSManagedObject in context and change some properties in runtime, but without telling SQLite about any changes in it.
I just want to save NSManagedObject to database when I hit save button or similar.
As I found out from source code demo we need to use beginUnsafe for this purposes (maybe I am wrong)
func unstoredWorkout() -> WorkoutEntity {
let transaction = CoreStore.beginUnsafe()
let workout = transaction.create(Into<WorkoutEntity>())
return workout
}
let workout = unstoredWorkout()
workout.muscles = []
Now when I try to update workout.muscles = [] app crashes with error:
error: Mutating a managed object 0x600003f68b60 <x-coredata://C00A3E74-AC3F-47FD-B656-CA0ECA02832F/WorkoutEntity/tC3921DAE-BA43-45CB-8271-079CC0E4821D82> (0x600001c2da90) after it has been removed from its context.
My question how we can create object without saving it and how we can save it then when we modify some properties and avoid this crash as well.
The reason for the crash is that your transaction only lives in your unstoredWorkout() method, so it calles deinit, which resets the context (and deletes all unsaved objects).
You have to retain that unsafe transaction somewhere to keep your object alive - such as in the viewcontroller that will eventually save the changes.
But I would rather encourage you to think about that if you really want to do that. You might run into other synchronization issues with various context or other async transactions alive, like when API calls are involved.

best way to rename a class in Realm swift

I use a "common" library in my iOS project. This library creates a Realm database. So far, I've been using this library on only iOS projects. I want to now use that same library with a macOS project. It's Foundation based, and doesn't use UIKit, so why not?
Here's the problem: I have a Realm class named Collection
Collection is also the name of a standard Swift protocol.
While I've been able to get away with this name collision on my iOS project, for some reason, I can't do the same on my MacOS project -- it creates a name-collection.
I read about this notation that can be used like this:
#objc(SpecialCollection)
class Collection: Realm.Object {
let items: List<ItemObject>
let name: String
let url: String
....
}
So, this solves the name-collision problem. In ObjC, the name will be something different, but in Swift, I don't need to change anything.
This is all well and good except for my local Realm database. I have a lot of Collection objects that should be renamed to SpecialCollection (since Realm uses ObjC underneath Swift). I'd like to perform a migration to do this, but apparently there isn't a supported way to do this yet? I noticed tickets on github about this issue being "watched", but unfortunately, there still exists no published solution to fix this problem.
All of my Collection objects contain List objects (hence the name). So, I tried to run an enumeration on all of the Collection objects in a migration... I would just take the older object, and create a new object with the new name, like this:
migration.enumerateObjects(ofType: "Collection", { (oldObject, _) in
migration.create("SpecialCollection", value: oldObject)
}
But since oldObject has a list of other objects, Realm's migration will try and create all the items in any List objects... which can't be done, because it creates objects with the same primaryKey value (causing a crash).
So, I can't keep the old name (Collection), and I can't convert to the new name, and I can't just trash the user's data. So, I'm truly at an impasse.
Blockquote
I tried to modify oldObject before creating the new object, but you can't change oldObject in a migration.
The only rule is that the old data has to be preserved, I can't just destroy the user's realm here.
Thanks for any help in this. It is greatly appreciated.
I had a very similar problem last night. I had a couple Realm classes I wanted to rename, and where one of them had a List property referring to the second class. So the only difference compared to your problem is I was renaming ItemObject class as well.
So here's how I did it:
Migrate your Collection class first, creating SpecialCollection.
While migrating, walk the Collection's list and create new
SpecialItemObject for each ItemObject and append it to the new list.
Delete each ItemObject.
Now enumerate all ItemObject remaining
in the realm and create a new SpecialItemObject and map its values
over. The reason is there may be other ItemObject floating around
in your realm, not tied to the list.
Delete all remaining ItemObject.
migration.enumerateObjects(ofType: "Collection")
{ (oldObject, newObject) in
let specialCollection = migration.create(SpecialCollection.className())
specialCollection["name"] = oldObject!["name"]
specialCollection["url"] = oldObject!["url"]
if let oldItems = oldObject!["items"] as? List<MigrationObject>,
let newItems = specialCollection["items"] as? List<MigrationObject>
{
for oldItem in oldItems
{
let newItem = migration.create(SpecialItemObject.className())
newItem["name"] = oldItem["name"] // You didn't specify what was in your ItemObject so this example just assumes a name property.
newItems.append(newItem)
migration.delete(oldItem)
}
}
}
migration.deleteData(forType: "Collection")
// Now migrate any remaining ItemObject objects that were not part of a Collection.
migration.enumerateObjects(ofType: "ItemObject")
{ (oldObject, newObject) in
let newItem = migration.create(SpecialItemObject.className())
newItem["name"] = oldItem["name"]
}
// Now let's be sure we'll have no further ItemObject in our entire Realm.
migration.deleteData(forType: "ItemObject")
So this is how I solved it for myself last night, after finding next to nothing about most of this in cocoa-realm in GitHub or on SO or elsewhere. The above example only differs from what you asked in that you weren't asking to rename your ItemObject class. You could try just creating new ItemObject objects and mapping the properties across in the same way I show in my example. I don't see why it wouldn't work. I've provided my example exactly how I solved my own problem, since I tested some migrations last night to prove it was solid.
Since your question is almost 5 months old, I'm really just posting this answer for posterity. Hope this helps someone!
Tested with Realm 3.3.2 on iOS 11.3 sim / Xcode 9.3 / Swift 4.1

CoreData - creating new entity - Why don't you need to save this?

I figured you can create new entities (in swift 3) like this:
let person = Person(context: persistentContainer.viewContext)
person.name = "Some Name"
This seems to be it. It saves the new person permanently (I think so, at least).
Why don't you need to call saveContext()of AppDelegate. swift (or persistentContainer.viewContext.save() which is basically the same, right?)?
Every time you change some entity, you need to save it. Why isn't this the case when creating new entities?
Thanks in Advance !!!
According to your comments on your question, you ARE calling saveContext().
Go into your AppDelegate and check out applicationWillTerminate, saveContext() is called there.
In short, if you want to persist the data then yes, you need to call saveContext()
for your anser you have to understand the Core Data stack
https://developer.apple.com/library/ios/documentation/DataManagement/Devpedia-CoreData/coreDataStack.html#//apple_ref/doc/uid/TP40010398-CH25-SW1
Changes that you make to your managed objects are not committed to the parent store until you save the context.

How to implement the new Core Data model builder 'unique' property in iOS 9.0 Beta

In the WWDC15 video session, 'What's New in Core Data' at 10:45 mins (into the presentation) the Apple engineer describes a new feature of the model builder that allows you to specify unique properties. Once you set the those unique properties, Core Data will not create a duplicate object with that property. This is suppose to eliminate the need to check if an identical object before you create a new object.
I have been experimenting with this but have no luck preventing the creation of new objects with identical 'unique' properties (duplicate objects). Other than the 5 minute video explanation, I have not found any other information describing how to use this feature.
Does anyone have any experience implementing the 'unique' property attribute in the Core Data Model?
Short answer:
You'll need to add this line to your Core Data stack setup code:
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
Long answer: I struggled with this for some time, but I think I have figured it out now:
Unique Constraints (UC) do not prevent creating duplicates in a context. Only when you try to save that context, Core Data checks for the uniqueness of the UCs.
If it finds more than one object with the same value for a UC, the default behaviour is to throw an error because the default merge policy for conflicts is NSErrorMergePolicyType. The error contains the conflicting objects in its userInfo.conflictList, so you could manually resolve the conflict.
But most of the time you probably want to use one of the other merge policies instead and let Core Data merge the conflicts automatically. These merge policies did exist before, they are used for conflicts between objects in different contexts. Maybe that's why they were not mentioned in the context of the UC feature at WWDC Session 220. Usually the right choice is NSMergeByPropertyObjectTrumpMergePolicy. It basically says "new data trumps old data", which is what you want in the common scenario when you import new data from external sources.
(Tip: First I had problems verifying this behaviour, because the duplicate objects seem to remain in the context until the save operation is finished - which in my case happened asynchronously in a background queue. So if you fetch/count your objects right after hitting the save button, you might still see the duplicates.)
I don't know the right answer, as this is a beta version, but after playing with it for a minute I found a way to make it work:
Tell the model which attributes form the unique constraint, exactly as shown in the image you have in your question.
Add a new record:
let newTag = NSEntityDescription.insertNewObjectForEntityForName("Tag", inManagedObjectContext: context) as! Tag
Assign the values to the attribues.
Save your changes:
do {
try context.save()
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
context.reset()
}
The key is in the catch block. If an error happens, reset the context to the previous state. As the save operation failed, the duplicate records won't be there.
Please notice that you should check the error to see if it was caused by a duplicated record.
I hope this helps.

Resources