CoreData transient relationship example
Asked Answered
P

4

11

Does anybody have an example on how to model and code a transient to-one relationship in CoreData? For example, I have 2 entities with a one-to-many relationship. Doctor and Appointment. Now I want an transient relationship called mostRecentAppointment on the doctor entity. It's straightforward to model in the xcode designer, but I'm not sure about the implementation side. Also should I implement an inverse? Seems silly.

Pratincole answered 29/4, 2013 at 13:54 Comment(3)
Pretty sure there is no support for transient relationships out of the box, you'll have to incorporate one manually. This might not answer your question directly, but the easiest way to implement the mostRecentAppointment relationship is to create a method in your data acces layer class and retrieve it with NSFetchRequest with a date desc predicateFlasher
Hmm. The designer does allow you to tag a relationship as transient. So it would seem there is something there. I'm looking to take advantage of the KVO & caching that comes with it as I don't want to execute a fetchrequest every time I access the object. Hoping there is something similar like normal transient properties.Pratincole
Hi. Did you solve the problem? I am tring so solve [a related problem]( #42812651) It seems transient relationship doesn't work as expected.Shandeigh
S
4

Have a look at this code I wrote recently, to cache an image in an NSManagedObject:

First you define a transient property in your model (notice that if your transient property points to an object type other than those supported by CoreData you'll leave as "Undefined" in the model)

enter image description here

Then, you re-generate your NSManagedObject subclass for that entity or just add the new property manually, the header file should look like this:

@interface Card : NSManagedObject

@property (nonatomic, retain) NSString * imagePath;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * order;
@property (nonatomic, retain) NSString * displayName;
@property (nonatomic, retain) UIImage *displayImage;

@end

Here we change the class of the transient property to the actual class type

e.g. displayImage type here is UIImage.

In the implementation file (or an extension class) you implement the getter/setter for your transient property:

-(UIImage*)displayImage{

  //Get Value
  [self willAccessValueForKey:@"displayImage"];
  UIImage *img = (UIImage*)[self primitiveValueForKey:@"displayImage"];
  [self didAccessValueForKey:@"displayImage"];

   if (img == nil) {
    if ([self imagePath]) { //That is a non-transient property on the object
      img = [UIImage imageWithContentsOfFile:self.imagePath];
      //Set Value
      [self setPrimitiveValue:img forKey:@"displayImage"];
    }
  }
  return img;
}

Hope that helps you.

Sutton answered 29/4, 2013 at 15:27 Comment(1)
I know how to do this and it's a little bit more involved than you let on as you need to deal with cache invalidation and faulting. Regardless my question was about transient relationships NOT properties. There is little to none information out there on transient relationships. Hence me posting this question. For instance, how to handle cache invalidation when things get added or removed to the normal one-to-many relationship (adding a normal appointment).Pratincole
P
2

What you need to do is add an entity of type Appointment called newAppointment and set this each time you create a new appointment for a given doctor. Its that simple.

Always implement an inverse as apple recommend this for validation and core data efficiency.

Alternatively you could timestamp the appointments and use NSPredicates to search for the latest appointment in a given Doctor's linked appointments.

Peroration answered 29/4, 2013 at 14:0 Comment(7)
What about KVO support? I rather take advantage of the CoreData framework like normal transient properties do.Pratincole
This is true, but in most cases here you would need to link the actual appointments with a doctor and visa versa on creation anyway, so you would still need the doctor on instantiation. Given that it is the most recent appointment. Though i agree KVO is a good approach, might be overkill in this instance.Peroration
Ofc this is an example. My app is a bit more complex. So KVO comes in handy. Since the relationships are not saved (transient) how do you re-establish the links when the application starts up?Pratincole
I havent had an instant where i've needed transient relationships to be honest so kept everything in the store, but do know how they work. In this instance though i would keep the latest appointment in the store and use NSPredicates to search for the latest TBH. Main reason as both parts would be in the data store anyway and i would need to pull out either a doctor or an appointment to use in this example.Peroration
Like I said, this is just an example. I'm looking for details on how to implement transient relationships. Do you have any code handy for that?Pratincole
not to hand but i did read this a while back 2pi.dk/tech/cocoa/transient_properties.htmlPeroration
That article doesn't really go into transient relationships.Pratincole
S
1

In this case, the appropriate method to override is -awakeFromFetch in the Doctor entity, for example like so:

- (void)awakeFromFetch {
    [super awakeFromFetch];// important: call this first!
    self.mostRecentAppointment = <something>; // normal  relationship
    self.mostRecentAppointment.doctor = self; // inverse relationship
}

In the model designer, mark both the normal and the inverse relationship as transient. That should be it.

Stress answered 31/5, 2019 at 23:20 Comment(1)
You don't have to set both the relationship and the inverse relationship. Set one relationship and Core Data will take care of the inverse relationship.Aegospotami
S
0

Well, you'll just have to try out, in your own sample program that can be no more than an hour to set up correctly.

My guess is --- no extra coding will be needed. If Apple's documentation on CoreData is correct, the only difference between a normal attribute/relationship and a "transient" one is that the latter is not persisted, meaning, when you "save" it does not update the persistent-store.

I would guess that otherwise all the aspects of it are complete, together with KVO/KVC compliance, Undo support, validation, and automatic update by delete rules. The only thing is that after a fresh Fetch of the entity --- the transient relationship will always be nil.

For that --- I would of course NOT RECOMMEND setting up a transient relationship as "non-optional", because it is very likely to be null most of the time for most of the entities.

I would set up a reverse relationship (transient as well and named wisely) and have both delete rules be "Nullify".

So far is for transient relation.

But here is an alternative I came up with, trying to solve almost-the-same problem. My "appointment" is one of the related appointments, but not just the "latest", but the first "unfinished" one. Very similar logic.

Instead of a transient relationship, I added a new calculated property to my "Doctor" entitys generated NSManagedObject subclass, in a category, like this:

@interface XXDoctor (XXExtensions)
/**
 @brief Needs manual KVO triggering as it is dependent on a collection. 
        Alternatively, you can observe insertions and deletions of the appointments, and trigger KVO on this propertyOtherwise it can be auto-
 @return the latest of the to-many appointments relation.
 **/
@property (readonly) XXAppointment *latestAppointment;    // defined as the
@end

Implementation:

#import "XXDoctor".h"
#import "XXAppointment.h"
@implementation XXDoctor (XXExtensions)
// this won't work because "appointments" is a to-many relation.
//+ (NSSet *)keyPathsForValuesAffectingLatestAppointment {
//  return [NSSet setWithObjects:@"appointments", nil];
//}
- (XXAppointment *) latestAppointment {
    NSInteger latestAppointmentIndex = [self.appointments indexOfObjectPassingTest:^BOOL(XXAppointment *appointment, NSUInteger idx, BOOL *stop) {
        *stop =  (appointment.dateFinished == nil);
        return *stop;
    }];
    return (latestAppointmentIndex == NSNotFound) ? nil : [self.appointments objectAtIndex: latestAppointmentIndex];
}
@end
Subrogate answered 28/9, 2014 at 12:13 Comment(3)
Sorry, but you haven't really answered my question, which how to implement transient-relationships properly. I haven't been able to find much on this topic in the Apple docs. Do you have a link for this specific topic?Pratincole
I did. There is NO IMPLEMENTAtioN NEEDED. Only checkmark a relation as "transient", re-generate your NSManagedObject subclass, and use as you wish. No further implementation. I am now successfully using to-one and to-many transient relationships in my application without any special issue.Subrogate
Ah, In my app, I also defined inverse-relations to my transient relations, and set them up as transient as well. The reason I used transient relations in my app (as opposed to my suggested calculated properties) is that the UndoManager treats them as normal CoreData changes, and knows how to revert them gracefully.Subrogate

© 2022 - 2024 — McMap. All rights reserved.