Swift Core Data case insensitive request

I'm trying to prevent a user from adding a duplicate name into a Core Data entity. I have a constraint setup for the name but I still need to do the duplicate validation before saving the data. I have a record with the name of 'Test' but I'm still allowed to add a new record with the name of 'test', the only difference being the case sensitivity between the two names is different.


When I perform a fetch request before saving to determine if the record already exists, I'm adding a case insensitive sort descriptor:


let fetchRequest: NSFetchRequest<ItemStore> = ItemStore.fetchRequest()

let storeName: String = storeNameText.text!

fetchRequest.predicate = NSPredicate(format: "%K = %@", argumentArray: [#keyPath(ItemStore.name), storeName])

let sortDescriptor = NSSortDescriptor(key: #keyPath(ItemStore.name), ascending: true, selector: #selector(NSString.caseInsensitiveCompare))

fetchRequest.sortDescriptors = [sortDescriptor]


But the fetch request doesn't appear to recognize that selector. If I pass 'test' as the storeName into the fetch request, the record for 'Test' isn't being returned.


Is there something simple I'm overlooking here, or is there a better way to check for duplicate records?

Accepted Reply

You are using `caseInsensitiveCompare:` for `NSSortDescriptor`, which is used just for sorting.

It does not affect the condition represented by `NSPredicate`.


If you want to make a condition which makes case-insensitive comparison, you need to specify that in your `NSPredicate`.


let fetchRequest: NSFetchRequest<ItemStore> = ItemStore.fetchRequest()
let storeName: String = storeNameText.text!
fetchRequest.predicate = NSPredicate(format: "%K =[c] %@", argumentArray: [#keyPath(ItemStore.name), storeName])
//Please do not miss `[c]` after `=`.


You have no need to use `NSSortDescriptor` for duplicate checking.

Replies

You could lowercased the record name to perform the fetch request.


let storeNameLowercased = storeName.lowercased()
fetchRequest.predicate = NSPredicate(format: "%K = %@", argumentArray: [#keyPath(ItemStore.name), storeNameLowercased])
let sortDescriptor = NSSortDescriptor(key: #keyPath(ItemStore.name), ascending: true)


Also if you are doing simple insertions, you could not use any fetch request and configure the NSPersistentContainer about how to deal when a value exist in the database previously.


mergePolicy:


A policy that merges conflicts between the persistent store's version of the object and the current in-memory version by individual property, with the external changes trumping in-memory changes.


NSMergeByPropertyObjectTrumpMergePolicy:

For properties that have been changed in both the external source and in memory, the in-memory changes trump the external ones.


container.loadPersistentStores(completionHandler: { (storeDescription, error) in
 container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

Thease are all the availables mergePolicy:


// Default policy for all managed object contexts - save returns with an error that contains the object IDs of the objects that had


NSErrorMergePolicy:


// This singleton policy merges conflicts between the persistent store's version of the object and the current in memory version. The merge occurs by individual property. For properties which have been changed in both the external source and in memory, the external changes trump the in memory ones.


NSMergeByPropertyStoreTrumpMergePolicy


// This singleton policy merges conflicts between the persistent store's version of the object and the current in memory version. The merge occurs by individual property. For properties which have been changed in both the external source and in memory, the in memory changes trump the external ones.


NSMergeByPropertyObjectTrumpMergePolicy


// This singleton policy overwrites all state for the changed objects in conflict The current object's state is pushed upon the persistent store.


NSOverwriteMergePolicy


// This singleton policy discards all state for the changed objects in conflict. The persistent store's version of the object is used.


NSRollbackMergePolicy: AnyObject

Simply lower casing the record name doesn’t work because the record could be stored in the database as Banana, so the case insensitive sort descriptor would still be required. According to the documentation, the caseInsensitiveCompare flag SHOULD work.


As a work around, I just added a new field used just for querying. This field is the lower case version of the name. It’s a hokey temporary fix for now. I will look into whether or not a merge policy would work, but that still doesn’t explain why the sort descriptor flag doesn’t work. I will submit a bug report, just wanted to see if there was something syntactic I wasn’t doing right.

If you do it from the beginning it will work, if the database contains previous data not.

You are using `caseInsensitiveCompare:` for `NSSortDescriptor`, which is used just for sorting.

It does not affect the condition represented by `NSPredicate`.


If you want to make a condition which makes case-insensitive comparison, you need to specify that in your `NSPredicate`.


let fetchRequest: NSFetchRequest<ItemStore> = ItemStore.fetchRequest()
let storeName: String = storeNameText.text!
fetchRequest.predicate = NSPredicate(format: "%K =[c] %@", argumentArray: [#keyPath(ItemStore.name), storeName])
//Please do not miss `[c]` after `=`.


You have no need to use `NSSortDescriptor` for duplicate checking.

OK, that makes sense. I will try that and see if it works.


[Edit] - That did work. I did some research on the NSPredicate format strings but obviously didn't find anything that specifically mentioned that this was how to perform a case insensitive search.


Thanks

I did some research on the NSPredicate format strings but obviously didn't find anything that specifically mentioned that this was how to perform a case insensitive search.


In fact, it is very hard find it from Apple's documentations. You need to dig deeply into some documentaions and find a description which does not have enough examples.


String Comparisons (Predicate Programming Guide)


String comparisons are, by default, case and diacritic sensitive. You can modify an operator using the key characters

c
and
d
within square braces to specify case and diacritic insensitivity respectively, for example
firstName BEGINSWITH[cd] $FIRST_NAME
.


I have never thought that this applies also to `==` (or `=`) until I found some examples in other sites than Apple's.

(And the BNF rule in the document does not match `=[c]` as an `operator`.)