Desmond
Desmond

Reputation: 5001

How to NSPredicate for a to-one relationship when using Core Data?

I have a Core Data with Song, Artist and Album entity.

Song have an optional one to one relationship artist to the Artist entity and album to Album entity Both entity has an inverse relationship to the Song entity.

Album have an optional one to one relationship artist to the Artist entity and an optional one to many relationship songs to Song entity

Both entity has an inverse relationship to the Album entity.

Artist have an optional one to one relationship album to the Album entity and an optional one to many relationship songs to Song entity Both entity has an inverse relationship to the Artist entity.

I would like to fetch distinct artist name and started to do this

NSArray *getAllArtistArray = [Song findBy:nil orderBy:@"songName asc"]; // fetch all songs in Song entity  

then i loop them to find unique artist name

 for(Song *songItem in getAllArtistArray)
 {
        BOOL hasDuplicate = [[uniqueArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"%K == %@",@"artist.artistName", songItem.artist.artistName]] count] > 0;
        if (!hasDuplicate)//if uniqueArray found artistname, don add songItem
        {
            [uniqueArray addObject:songItem];
        }
  }

here is the problem :/

I need to find the Song and Album count of individual artist.

//fixed Indention
//this works for finding the song count of individual Artist but is this the right way ?
  -(int)searchSongItemCountInSongbasedOnArtist:(NSString *)searchStr //song count of artist
 {
     NSPredicate *getSearchPred = [NSPredicate predicateWithFormat:@"%K == %@", @"artist.artistName", searchStr];
     NSArray *countArray = [Song findBy:getSearchPred orderBy:@"artist.artistName asc"];        
     [self searchAlbumCountBasedOnArtist:searchStr andSongArray:countArray];

     return [countArray count];
 }

how to i find the album count of individual based on the artist name ? MPMediaQuery can easily do it, how i can do it using NSPredicate with coredata ?

-(int)searchAlbumCountBasedOnArtist:(NSString *)searchStr andSongArray:(NSArray *)songArray
{
    MPMediaQuery *albumQuery = [MPMediaQuery albumsQuery];
    NSArray *albumCollection = [albumQuery collections];
    [albumQuery addFilterPredicate: [MPMediaPropertyPredicate predicateWithValue:searchStr
                                      forProperty:MPMediaItemPropertyArtist]];
    return [albumCollection  count];
}

enter image description here enter image description here enter image description here

Album

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Artist, Song;

@interface Album : NSManagedObject

@property (nonatomic, retain) NSNumber * albumID;
@property (nonatomic, retain) NSString * albumTitle;
@property (nonatomic, retain) NSString * albumTitleEN;
@property (nonatomic, retain) NSSet *artists;
@property (nonatomic, retain) NSSet *songs;
@end

@interface Album (CoreDataGeneratedAccessors)

- (void)addArtistsObject:(Artist *)value;
- (void)removeArtistsObject:(Artist *)value;
- (void)addArtists:(NSSet *)values;
- (void)removeArtists:(NSSet *)values;

- (void)addSongsObject:(Song *)value;
- (void)removeSongsObject:(Song *)value;
- (void)addSongs:(NSSet *)values;
- (void)removeSongs:(NSSet *)values;

@end

Song

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Album, Artist;

@interface Song : NSManagedObject

@property (nonatomic, retain) NSNumber * acousticness;
@property (nonatomic, retain) NSNumber * danceability;
@property (nonatomic, retain) NSString * deleted;
@property (nonatomic, retain) NSNumber * durationEN;
@property (nonatomic, retain) NSNumber * energy;
@property (nonatomic, retain) NSString * genre;
@property (nonatomic, retain) NSNumber * key;
@property (nonatomic, retain) NSNumber * liveness;
@property (nonatomic, retain) NSNumber * loudness;
@property (nonatomic, retain) NSString * md5Hash;
@property (nonatomic, retain) NSNumber * mode;
@property (nonatomic, retain) NSNumber * mood;
@property (nonatomic, retain) NSNumber * pID;
@property (nonatomic, retain) NSNumber * playbackDuration;
@property (nonatomic, retain) NSString * serverID;
@property (nonatomic, retain) NSString * songName;
@property (nonatomic, retain) NSString * songNameEN;
@property (nonatomic, retain) NSString * songURL;
@property (nonatomic, retain) NSNumber * speechiness;
@property (nonatomic, retain) NSNumber * tempo;
@property (nonatomic, retain) NSNumber * valance;
@property (nonatomic, retain) Album *albums;
@property (nonatomic, retain) Artist *artists;

@end

Artist

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Album, Song;

@interface Artist : NSManagedObject

@property (nonatomic, retain) NSNumber * artistID;
@property (nonatomic, retain) NSString * artistIDEN;
@property (nonatomic, retain) NSString * artistName;
@property (nonatomic, retain) NSString * artistNameEN;
@property (nonatomic, retain) NSSet *albums;
@property (nonatomic, retain) NSSet *songs;
@end

@interface Artist (CoreDataGeneratedAccessors)

- (void)addAlbumsObject:(Album *)value;
- (void)removeAlbumsObject:(Album *)value;
- (void)addAlbums:(NSSet *)values;
- (void)removeAlbums:(NSSet *)values;

- (void)addSongsObject:(Song *)value;
- (void)removeSongsObject:(Song *)value;
- (void)addSongs:(NSSet *)values;
- (void)removeSongs:(NSSet *)values;

@end

Any comments / answers are greatly appreciated!

Upvotes: 0

Views: 1169

Answers (2)

Duncan Groenewald
Duncan Groenewald

Reputation: 8988

Firstly let me point out that I have no knowledge of the library you are using (VPPCoreData or whatever) - I try and avoid anything that seems to make things simpler because invariably there is a tradeoff somewhere.

Secondly - trying to figure out the code where things get added and relationships get set.

Songs get created and the album is assigned with this code (note albums and artists are To-One relationships and should be renamed to singular album and artist to avoid confusion with a To-Many relationship)

song.albums = [self findOrCreateAlbumFromMediaItem:mediaItem];
song.artists = [self findOrCreateArtistFromMediaItem:mediaItem];

I assume that a song exists in only one Album and a song has but one Artist.

Now you don't set any relationships between the Artist and the Album which is why it always comes up ZERO. And its probably better not to because you can get this relationship from the artist.songs relationship.

So you have two choices, 1) determine the albums based on the songs that are related to the artist and don't maintain a artist.albums or albums.artists relationship, or 2) set the album.artists relationship yourself and use that.

For 1) You need to do this

// Get the array of Albums
NSArray *list = [artist.songs valueForKeyPath:@"@distinctUnionOfObjects.album"];
NSLog(@" album count is %d", list.count);
for (Album *album in list) {
    NSLog(@" album is %@", album.albumTitle);
}

For 2) it means maintain duplicate information and the risk that something gets out of sync, but it could be easier to simply set the relationship when getting the album and artist. If anything changes you have to change the relationships too.

song.album = [self findOrCreateAlbumFromMediaItem:mediaItem];
song.artist = [self findOrCreateArtistFromMediaItem:mediaItem];

NSMutableSet *albumArtists = [song.album mutableSetValueForKey:@"artists"];
[albumArtists addObject:song.artist];

You could add a read only property to the Artist object like this

in Artist.h

@property (readonly) NSArray *albumList;

in Artists.m

- (NSArray *)albumList {
    return [artist.songs valueForKeyPath:@"@distinctUnionOfObjects.album"];
}

Then you can just do the following to get the count

albumCount = [[artist albumList] count];

or perhaps dot notation will work

albumCount = artist.albumList.count;

Upvotes: 1

Duncan Groenewald
Duncan Groenewald

Reputation: 8988

Fetch the artist and then use artist.albums.count.

This assumes you have a To-Many relationship between Artist and Album called albums and another between Artists and Song called songs.

Similarly you could use artist.songs.count to get the number of songs

Here is a generic method to fetch an array of entities

- (NSArray *)getData:(NSString*)entityName sortField:(NSString*)sortKey predicate:(NSPredicate*)predicate managedObjectContext:(NSManagedObjectContext*)managedObjectContext
{
    NSLog(@"getData called");

    if (managedObjectContext == nil) {
        NSLog(@"Error can't continue with null managedObjectContext");
        return nil;
    }
    if (entityName == nil) {
        NSLog(@"Error can't continue with null entityName");
        return nil;
    }
    if (sortKey == nil) {  // if its not set then just set it as follows...
        NSLog(@"Error can't continue with null sortField");
        return nil;
    }
    NSLog(@" entity is %@", entityName);

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:managedObjectContext];
    if (entity == nil) {
        NSLog(@"  error finding entity %@ in class %@", entityName, [self class]);
        return nil;
    }

    [fetchRequest setEntity:entity];

    if (predicate)
        [fetchRequest setPredicate:predicate];

    // Edit the sort key as appropriate.
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey ascending:YES];
    NSArray *sortDescriptors =[NSArray arrayWithObjects:sortDescriptor,nil];
    [fetchRequest setSortDescriptors:sortDescriptors];

    NSError *error = nil;
    NSArray *result = [managedObjectContext executeFetchRequest:fetchRequest error:&error];

    if (!result) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        return nil;
    }

    return result;
}

Then to get the album and song counts

- (void)listAllMethod {

   NSArray *artists = [self getData:@"Artists" sortField:@"artistName" predicate:nil managedObjectContext:self.managedObjectContext;

   for (Artist *artist in artists) {
      NSLog(@" artist is %@", artist.artistName);
      NSLog(@"  album count is %d", [artist.albums.count intValue]);
      NSLog(@"  song count is %d", [artist.songs.count intValue]);
      for (Album *album in artist.albums)
         NSLog(@"     album is %@", album.albumTitle);
      for (Song *song in artist.songs)
         NSLog(@"     song is %@", song.songName);

   }

   NSArray *albums = [self getData:@"Albums" sortField:@"albumTitle" predicate:nil managedObjectContext:self.managedObjectContext;

   for (Album *album in albums) {
      NSLog(@" album is %@", album.albumTitle);
      NSLog(@"  artist count is %d", [album.artists.count intValue]);
      for (Artist *artist in albums.artists)
         NSLog(@"     artist is %@", artist.artistName);
   }

}

Upvotes: 2

Related Questions