Core data fetch request not working - swift

I have ShopItem and Product tables. Product has relation to ShopItem. I want to get all products from ShopItem object. I have created a method at ShopItem class
class ShopItem: NSManagedObject {
func fetchProducts(q: String) {
// some code ...
let fetchRequest = NSFetchRequest(entityName: "Product")
fetchRequest.predicate = NSPredicate(
format: "shopitem == %# AND keyword == %#",
self.objectID, String(jsonObj["keyword"])
)
fetchRequest.fetchLimit = 1;
do {
fetchResults = try self.managedObjectContext!.executeFetchRequest(fetchRequest) as! [Product]
} catch {
fatalError("Fetching from the store failed")
}
}
}
At logs it generates me the following sql:
SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZPRODUCT_TITLE, t0.ZPRODUCT_URL,
t0.ZSHOPITEM FROM ZPRODUCT t0 WHERE ( t0.ZSHOPITEM = ? AND
t0.ZKEYWORD = ?) LIMIT 1
And results is always empty.

If you have a relationship, why the fetch request? If everything is set up correctly, you just filter your products by keyword.
// filter by kw
let filteredProducts = shopItem.products.filter { $0.keyword == kw }

Your fetch request looks correct (although you don't need to pass self.objectID in your predicate, self is sufficient). When I see mysteriously empty relationships it's typically one of two things:
You aren't setting the relationship at creation like you think you are (or whatever even relates the two entities).
You are setting the relationship, but you have a to-one relationship configured in your data model instead of a to-many, which means only the last relationship you create will be present.

jsonObj["keyword"] returns an optional.
The String representation will look like Optional("keywordValue")
Unwrap the optional or use optional bindings if the value can be nil.

Related

Fetch Request Predicate to filter a Core Data entity where a small NSSet is contained within larger NSSet

I'm writing a 100% SwiftUI app with iOS and macOS targets, using Core Data and NSPersistentCloudKitContainer to backup to iCloud and sync between devices signed into the same AppleID.
The three entities involved are: Meals, Portions, Foods
Each Meal has:
a many-to-many relationship with Portions
a many-to-many relationship with Foods
Each Portion has:
a many-to-many relationship with Foods
I'm attempting to prepare a predicate to filter meals where each meal portion contains a certain food OR the meal contains a certain food directly.
So I'll provide a practical example...
Meal 1
consists of...
Portions
Banana Smoothie
Egg Sandwich
Foods
Apple
The Portion with the name Banana Smoothie contains the following Foods:
Banana
Cows Milk
Honey
Meal 2
consists of...
Portions
Blueberry Smoothie
Ham Sandwich
Foods
Banana
For the macOS target, I'm using the relatively new Table structure to present a table that lists all Meal entities for a certain Food entity, including those Meal entities where one or more of the Portion entities contains that certain Food entity.
If I refer back to the above example, for the Food entity named "Banana", I'd want my predicate to filter my FetchRequest such that Meal entities with names "Meal 1" & "Meal 2" are in the results.
#FetchRequest var meals: FetchedResults<Meal>
Here is the current predicate for this FetchRequest...
let portions = NSSet(object: food.foodPortions as Any)
let predicatePartA = NSPredicate(format: "%# IN mealFoods", food)
let predicatePartB = NSPredicate(format: "ANY %# IN mealsPortions", portions)
let predicate = NSCompoundPredicate(orPredicateWithSubpredicates: [predicatePartA, predicatePartB])
where food is #ObservedObject var food: Food
and mealFoods and mealsPortions are NSSet many-to-many relationships to from every Meal object.
predicatePartA works fine, I suspect because it is one single Object IN an NSSet of objects.
predicatePartB doesn't crash, but it also doesn't resolve any meals, I suspect because I'm providing a set instead of a single object.
I've attempted to research for some time now how this might be achieved and the best I can come up with are the operators...
#distinctUnionOfSets
#"SUBQUERY()
...but apart from this website there is little documentation I can find on how to implement them.
UPDATE
With help from #JoakimDanielson I've attempted to use SUBQUERY...
let predicatePartB = NSPredicate(format: "SUBQUERY(mealsPortions, $portion, $portion IN %#).#count > 0", portions)
AND
let predicatePartB = NSPredicate(format: "SUBQUERY(mealsPortions, $portion, $portion IN %#).#count != 0", portions)
Again this does not crash, but it does not provide the expected results for the fetch request.
Any suggestions please?
Also worth noting that I've found some better documentation by Apple that supports this syntax although, because the predicate isn't working, I still not sure it is correct.
init(forSubquery:usingIteratorVariable:predicate:)
with the syntax
SUBQUERY(collection_expression, variable_expression, predicate);
Short answer...
let portions = food.foodPortions
let predicatePartB = NSPredicate(format: "(SUBQUERY(mealsPortions, $p, $p IN %#).#count != 0)", portions!)
OR, if I prepare a computed property for portions...
var portions: NSSet {
if let p = food.foodPortions { return p }
return NSSet()
}
then in the creation of the predicate I'm not required to force unwrap the optional NSSet...
let predicatePartB = NSPredicate(format: "(SUBQUERY(mealsPortions, $p, $p IN %#).#count != 0)", portions)
Most people reading this probably won't want to know the detail but nonetheless I feel compelled to write this down, so the long answer is...
... in two parts, or at least recognises two contributors who helped me solve it.
Part 1
Primarily #JoakimDanielson for confirming that SUBQUERY was the right path to a solution and for taking the time to work out the syntax for my case and also for questioning what eventually turned out to be a very basic error, the problem was not my SUBQUERY syntax but in fact the manner in which I was preparing the NSSet that I used in the predicate string.
All I needed to do was change...
let portions = NSSet(object: food.foodPortions as Any)
to...
let portions = food.foodPortions
after which I could either force unwrap it in the creation of the predicate, or otherwise prepare a computed property (the solution I chose) - as detailed above in the short answer.
This was simply an error as a result of my inadequate understanding of the collections NSSet and Set. A refresher of the swift.org docs helped me.
Part 2
Secondly this SO Q&A "How to create a CoreData SUBQUERY with BETWEEN clause?" and the reference to this clever article titled "SUBQUERY Is Not That Scary" by #MaciekCzarnik.
I went through the process of reducing the necessary iteration until I could line for line compare the SUBQUERY syntax. While it didn't actually solve my problem, this did encourage me to try numerous predicate syntax alternatives until I returned with an understanding of SUBQUERY and was able to confirm the original syntax was correct. It provided me with the type of example my brain can comprehend and work through to develop an understanding of how SUBQUERY actually works.
Because you have nothing better to read at the current moment in time...
var iterationOne: [Meal] {
let meals: [Meal] = []//all meals
var results = [Meal]()
for meal in meals {
var portionsMatchingQuery = Set<Portion>()
if let mealPortionsToCheck = meal.mealsPortions {
for case let portion as Portion in mealPortionsToCheck {
if portions.contains(portion) == true {
portionsMatchingQuery.insert(portion)
}
}
if portionsMatchingQuery.count > 0 { results.append(meal) }
}
}
return results
}
can be simplified to _
var iterationTwo: [Meal] {
let meals: [Meal] = []//all meals
let results = meals.filter { meal in
var portionsMatchingQuery = Set<Portion>()
if let mealPortionsToCheck = meal.mealsPortions {
for case let portion as Portion in mealPortionsToCheck {
if portions.contains(portion) == true {
portionsMatchingQuery.insert(portion)
}
}
return portionsMatchingQuery.count > 0
}
return false
}
return results
}
can be simplified to _
var iterationThree: [Meal] {
let meals: [Meal] = []//all meals
let results = meals.filter { meal in
let portionsMatchingQuery = meal.mealsPortions?.filter { portion in
for case let portion as Portion in meal.mealsPortions! {
return portions.contains(portion) == true
}
return false
}
return portionsMatchingQuery?.count ?? 0 > 0
}
return results
}
can be simplified to _
var iterationFour: [Meal] {
let meals: [Meal] = []//all meals
let results = meals.filter { meal in
meal.mealsPortions?.filter { portion in
for case let portion as Portion in meal.mealsPortions {
return portions.contains(portion) == true
}
return false
}.count ?? 0 > 0
}
return results
}
iterationFour == predicatePartB

how to save mixed sort?

From the list in which the words are located, I need to display them in a mixed state.
To do this, I added a shuffle sort field to the entity
func setShuffleWords(id: UUID?) -> [CoreWord]? {
guard let id = id else { return nil }
let requestWords = NSFetchRequest<NSFetchRequestResult>(entityName: "CoreWord")
requestWords.predicate = NSPredicate(format: "ANY dictionary.id == %#", id as CVarArg)
var coreWords: [CoreWord]?
if let result = try? managedContext.fetch(requestWords) as? [CoreWord] {
let words = result.shuffled()
for (index, value) in words.enumerated() {
if value.value(forKey: "shuffle") != nil {
value.setValue(index, forKey: "shuffle")
}
}
coreWords = words
}
save()
return coreWords
}
func getShuffleWords(id: UUID?) -> [CoreWord]? {
guard let id = id else { return nil }
let requestWords = NSFetchRequest<NSFetchRequestResult>(entityName: "CoreWord")
let sort = NSSortDescriptor(key: "shuffle", ascending: true)
requestWords.predicate = NSPredicate(format: "ANY dictionary.id == %#", id as CVarArg)
requestWords.sortDescriptors = [sort]
do {
let result = try managedContext.fetch(requestWords)
return result as? [CoreWord]
} catch let error {
fatalError(error.localizedDescription)
}
}
But, the problem is that if I mix up another list where this word is, then the value is rewritten and it will change its position everywhere.
How to keep mixed order in a specific list?
In this situation you need to replace your many-many relationship with an intermediate entity. Let’s call that entity a Listing. It will represent the listing of a particular word in a particular group, so each Listing should have a to-one relationship to CoreWord and a to-one relationship to CoreGroup. In each case the inverse (which you might call “listings”) should be to-many. The Listing entity should have (at least) one attribute, shuffle, which represents the sequence of a particular word on a particular group.
Life generally gets more complicated, because you have to create and manage these intermediate objects rather than just working the Word and Group objects and their relationships.

Filter querying multiple objects from Realm using List of Primary Keys

I'm trying to query multiple objects from Realm using a List of Primary Key Strings. I know I can do this using a for..in loop but I'd rather use a filter if possible.
primaryKeyArray contains a number of Strings
class Item : Object {
#objc dynamic var itemKey = NSUUID().uuidString
}
var primaryKeyArray : List<String>?
//Assume Realm DB already contains multiple Item Objects
//primaryKeyArray contains "key1", "key2", "key3", etc..
let predicate = NSPredicate(format: "itemKey == %#", primaryKeyArray)
let items = realm.objects(Item.self).filter(predicate)
I know the problem is with my predicate format. Not sure whether to use some form of CONTAINS or what? Any help with the predicate syntax would be greatly appreciated!
I think you are asking how to query Realm for items that have keys that match a set of keys in an array.
So given a DogClass Realm Object
class DogClass: Object {
#objc dynamic var dog_id = NSUUID().uuidString
#objc dynamic var dog_name = ""
override static func primaryKey() -> String? {
return "dog_id"
}
}
and suppose we know we want to retrieve three dogs that match some given primary keys
let keysToMatch = ["302AC133-3980-41F3-95E8-D3E7F639B769", "54ECC485-4910-44E5-98B9-0712BB99783E", "71FE403B-30CD-4E6C-B88A-D6FDBB08C509"]
let dogResults = realm.objects(DogClass.self).filter("dog_id IN %#", keysToMatch)
for dog in dogResults {
print(dog.dog_id, dog.dog_name)
}
Note the use of IN in the filter, which will match any dogs with id's in the given array.
You can also pass in a Realm List Object instead of a Swift array and get the same result.
let listOfKeysToMatch = List<String>()
listOfKeysToMatch.append("302AC133-3980-41F3-95E8-D3E7F639B769")
listOfKeysToMatch.append("54ECC485-4910-44E5-98B9-0712BB99783E")
listOfKeysToMatch.append("71FE403B-30CD-4E6C-B88A-D6FDBB08C509")
let dogResults2 = realm.objects(DogClass.self).filter("dog_id in %#", listOfKeysToMatch)
for dog in dogResults2 {
print(dog.dog_id, dog.dog_name)
}
let predicate = NSPredicate(format: "itemKey IN %#", primaryKeyArray)

Swift - How to save entries to core data in a loop

I have 2 entities: Series and Season. A Series can have multiple seasons, so I set the relationship type to "to many". season_counter is an Int Array, containing the amount of episodes.
let newSeries = NSEntityDescription.insertNewObjectForEntityForName("Series", inManagedObjectContext: self.context!) as! Series
for var i = 0; i < season_counter.count; ++i{
let newSeason = NSEntityDescription.insertNewObjectForEntityForName("Season", inManagedObjectContext: self.context!) as! Season
newSeason.value = i+1
newSeason.episodes = season_counter[i]
newSeries.setValue(NSSet(object: newSeason), forKey: "seasons")
do {
try context?.save()
} catch _ {
}
}
While debugging I noticed, that season_counter stores the correct values. When I display the results, I have only the last season stored (for example 13 episodes, seasons.count is 1):
do {
let fetchRequest = NSFetchRequest(entityName: "Season")
fetchRequest.predicate = NSPredicate(format: "series = %#", series)
try seasons = context?.executeFetchRequest(fetchRequest) as! [Season]
print(seasons.count)
print(seasons[0].episodes)
} catch {
}
Any tips to solve this?
In each pass through your loop, you're throwing away the previous value of seasons and replacing it with a set that contains only one season (the newly created newSeason):
newSeries.setValue(NSSet(object: newSeason), forKey: "seasons")
Instead of doing that, build up a set of all of the newSeason instances, and then call setValue:forKey: once, with that set.
Your life will be easier, by the way, if you create subclasses of NSManagedObject and get out of the "value for key" business.
newSeries.setValue(NSSet(object: newSeason), forKey: "seasons")
With this line you always create a new set of seasons containing exaclty one Season object - newSeason.
Try to do that:
var prevSeasons = newSeries.valueForKey("seasons")
prevSeasons.insert(newSeason)
newSeries.setValue(prevSeasons, forKey: "seasons")

Searchbar with core data issues

I have a search bar.And data displayed in labels with scrollview.
For ex:
core data Fields :
1.id
2.company
3.Employe Name
4.Address
If i type id,company or Employee Name in searchbar i want to dispaly associated results.
my code :
For search data :
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
var request = NSFetchRequest(entityName: "Agency")
request.returnsObjectsAsFaults = false
var countResult : NSArray = context.executeFetchRequest(request, error: nil)!
let result = NSPredicate(format: "SELF CONTAINS[c] %#",searchText)
self.filtered = self.countResult.filteredArrayUsingPredicate(result!)
if (filtered.count == 0 ) {
searchActive = false;
}else {
searchActive = true;
}
println(filtered)
}
It shows an error " 'Can't use in/contains operator with collection".
These codes cannot satisfy what i want.And also i dont have a idea how to fetch the related rows according to enter value in search bar.
Thanks in Advance.
The first problem is your predicate - you're trying to use CONTAINS on an NSManagedObject subclass, but CONTAINS only works with String. To check whether your search text is contained within any of your managed objects you need to evaluate whether it is contained in each attribute (in your case id, company and empolyeeName, I'm assuming they're all Strings).
To do this you should change your predicate to:
let searchPredicate = NSPredicate(format: "id BEGINSWITH %# OR
company BEGINSWITH %# OR
employeeName BEGINSWITH %#",
searchText, searchText, searchText)
I would recommend using BEGINSWITH instead of CONTAINS[c] since when searching your user is likely to be entering the first part of the phrase. Also, as Apple said in their 2013 WWDC talk Core Data Performance Optimization and Debugging -
...we've got begins with and ends with and that's by far the cheapest query that you can execute.
...
Contains is more expensive because we have to work along and see
whether it contains...
And in the case of a search, you want it to be fast!
Secondly, you don't need to filter your results after getting them back from CoreData. You can set the predicate property on your NSFetchRequest and your returned results will be filtered. For example:
let request = NSFetchRequest(entityName: "Agency")
request.predicate = // Your predicate...
let results = context.executeFetchRequest(request, error)
// Now do what you need with the results.
A final note, it's best not to force unwrap your results from executeRequest in case there is some problem and nil is returned - in that case your app would crash. You could instead use:
if let unwrappedResults = results {
// Now do what you want with the unwrapped results.
}
I suspect it has something to do with your use of SELF in the predicate format, and the "collection" referred to in the error message is the controller sub/class within which your code resides.
Try something like this (forgive me I'm Obj-C not Swift so may have the syntax incorrect).
let searchAttribute = <<entity.attribute key path>>
let result = NSPredicate(format:"%K CONTAINS[cd] %#",searchAttribute, searchText)
Where %K refers to the key path, that in the case of Core Data is your entity attribute. For example: Agency.name if that attribute exists for your Agency object.
Read about Predicate Format String Syntax.
UPDATE after third comment...
In my apps my solution includes the creation of a custom method in an extension of the Core Data generated NSManagedObject subclass. If that sounds like you know what I mean, let me know and I will post details.
In the meantime, create a custom method in whatever class your UISearchBar is controlled... (apologies Obj-C not Swift)
- (NSString *)searchKey {
NSString *tempSearchKey = nil;
NSString *searchAtrribute1 = Agency.attribute1;
NSString *searchAtrribute2 = Agency.attribute2;
NSString *searchAtrribute3 = Agency.attribute3;
NSString *searchAtrribute4 = Agency.attribute4;
tempSearchKey = [NSString stringWithFormat:#"%# %# %# %#", searchAtrribute1, searchAtrribute2, searchAtrribute3, searchAtrribute4];
return tempSearchKey;
}
You'll obviously need a strong reference for your Agency entity object to persist within the class, otherwise you will need to embed this bit of code into your searchBar function.
Work OK?