Setting up a "to-many" relationship value dependency for a transient Core Data attribute - objective-c

I've got a relatively complicated Core Data relationship structure and I'm trying to figure out how to set up value dependencies (or observations) across various to-many relationships. Let me start out with some basic info. I've got a classroom with students, assignments, and grades (students X assignments). For simplicity's sake, we don't really have to focus much on the assignments yet.
StudentObj <--->> ScoreObj <<---> AssignmentObj
Each ScoreObj has a to-one relation with the StudentObj and the AssignmentObj.
ScoreObj has real attributes for the numerical grade, the turnInDate, and notes.
AssignmentObj.scores is the set of Score objects for that assignment (N = all students).
AssignmentObj has real attributes for name, dueDate, curveFunction, gradeWeight, and maxPoints.
StudentObj.scores is the set of Score objects for that student (N = all assignments).
StudentObj also has real attributes like name, studentID, email, etc.
StudentObj has a transient (calculated, not stored) attribute called gradeTotal.
This last item, gradeTotal, is the real pickle. it calculates the student's overall semester grade using the scores (ScoreObj) from all their assignments, their associated assignment gradeWeights, curves, and maxPoints, and various other things.
This gradeTotal value is displayed in a table column, along with all the students and their individual assignment grades. Determining the value of gradeTotal is a relatively expensive operation, particularly with a large class, therefore I want to run it only when necessary. For simplicity's sake, I'm not storing that gradeTotal value in the core data model. I don't mind caching it somewhere, but I'm having a bitch of a time determining where and how to best update that cache.
I need to run that calculation for each student whenever any value changes that affects their gradeTotal. If this were a simple to-one relationship, I know I could use something like keyPathsForValuesAffectingGradeTotal ... but it's more like a many-to-one-to-many relationship. Does anyone know of an elegant (and KVC correct) solution? I guess I could tear through all those score and assignment objects and tell them to register their students as observers. But this seems like a blunt force approach.

I just postet a project on github which probably solves part of the problem with observings
http://github.com/mbrugger/CoreDataDependentProperties
A more detailed description of the project can be found there.
-(NSArray*) keyPathsForValuesAffecting would not have solved your problem as this only works across to-one relations
In addition you should not make the dependent attribute transient, as it makes your context "dirty" (unsaved changes) already after recalculating all values after loading

Related

Which database design is better?

I am about to create a database to track weight-lifting exercises.
Which approach would you prefer?
Solution A:
Two tables
Exercise (with ID, Name etc.)
Set (with ID, Set_Number, Date, FK_Exercise)
Here, one Exercise and Set have a one-to-many relationship.
Set_Number is supposed to track which set it is on a given date (1st set, 2nd set, 3rd set etc.)
Advantage: One table less to deal with.
Solution B:
Three tables:
Exercise (with ID, Name etc.)
Session (with ID, Date, FK_Exercise)
Set (with ID, Set_Number, FK_Session)
Here, a Session would be something like a connector between Exercise and Set. So basically a sequence of sets on a given day for a given exercise will be pooled in one Session instance.
In this case, Exercise and Session have a one-to-many relationship and Session and Set also have a one-to-many relationship.
Advantage: The Date property will not be redundant for any given day. And logically it makes sense to bundle sets.
A good data model falls out of a proper understanding of the domain. Your domain has three entities:
EXERCISE: particular type of weightlifting move (name and weight)
SET: number of reps of a given EXERCISE (depending on training goal - strength, muscle, endurance?)
SESSION: number of SETs undertaken on a given date
So you need at least three tables. At least, because EXERCISE has two levels of detail: one is the exercise name and the other is the exercise weight . It's quite likely you will need to store SETs of different combination of names and weights (Bicep curl / 10kg, Bicep curl / 15kg, etc) in which case you need a look-up table EXERCISE name and a fourth table SET_EXERCISE to store the weight used for a particular SET of reps.
Having gone through this exercise (o ho!) we can see that your foreign keys are wrong. A SESSION comprises a number of SETs; a SET comprises a number of EXERCISEs (SET_EXERCISEs).
Hence the logical data model should look something like:
EXERCISE (ID, Name, Weight, etc)
SET (ID, FK_Exercise, Reps, etc)
SESSION (ID, FK_Set, Date, etc)
Although this is not quite accurate: SET:SESSION is in fact a many-to- many relationship, as a SESSION will normally comprise more than one SET and a SET can be done in more than one SESSION.
When it comes to a physical data model i.e. tables you should have five tables:
EXERCISE (ID, Name, etc)
SET_EXERCISE (ID, FK_Exercise, FK_Set, Weight, etc)
SET (ID, FK_Set_Exercise, Reps, etc)
SESSION_SET (FK_Set, FK_Session, Set_Number, etc)
SESSION (ID, Date, etc)
The SESSION_SET table is necessary to resolve the many-to-many relationship between SET and SESSION .
The final model has five tables: three tables for the original entities and two intersection tables which join those entities. It so happens that all the relations between the logical entities (EXERCISE, SET, SESSION) have been implemented as intersection tables rather than foreign keys. This doesn't always happen when transforming from a Logical to a Physical data model.
This is not the only way of modelling the domain. As a design activity data modelling is about interpreting the rules to fit the data you need to record. The data is the starting point.
"it seems I didn't make myself clear regarding the Session entity...he naming is probably bad and misleading"
This is why I said the data model follows from a proper understanding of the domain. EXERCISE, SET and SESSION are domain terms. You are of course welcome to make your own definitions of things for your private projects, but in real life data models are a mechanism for communication between Development and Business: the meaning of things is crucial, and must conform to a common understanding. We cannot build a data model where SESSION means something different from what the business understands by "session".
"I also don't understand how a Set can be done in more than one Session?"
A SET is a pattern of EXERCISE for a number of reps. So #1 / benchpress / 130KG / 8 reps is a SET and #2 / benchpress / 100KG / 12 reps is a different SET. If you benchpressed 130KG eight times on Monday and Wednesday then that's the same SET in two different SESSIONs. Maybe it's a layer of detail too far; but if you're going to build a database app to track your workouts instead of using a spreadsheet like most people you might as well build the best data model you can :-)
Again, data modelling is an exercise with a large dose of opinion: if your data model is good enough for your current needs then it is good enough. The thing is, a more rigorous data model is paradoxically more flexible (because enforcing data integrity rules makes it easier to write queries and be sure that the results are correct). What might be good enough now might be a terrible brake on innovation in the future.

Core Data ordered many-to-many relationships

Using Core Data, I have two entities that have many-to-many relationships. So:
Class A <<---->> Class B
Both relationships are set up as 'ordered' so I can track they're order in a UITableView. That works fine, no problem.
I am about to try and implement iCloud with this Core Data model, and find out that iCloud doesn't support ordered relationships, so I need to reimplement the ordering somehow.
I've done this with another entity that has a one-to-many relationship with no problem, I add an 'order' attribute to the entity and store it's order information there. But with a many-to-many relationship I need an unknown number of order attributes.
I can think of two solutions, neither of which seem ideal to me so maybe I'm missing something;
Option 1. I add an intermediary entity. This entity has a one-to-many relationship with both entities like so:
Class A <<--> Class C <-->> Class B
That means I can have the single order attribute in this helper entity.
Option 2. Instead of an order attribute that stores a single order number, I store a dictionary that I can store as many order numbers as I need, probably with the corresponding object (ID?) as the key and the order number as the value.
I'm not necessarily looking for any code so any thoughts or suggestions would be appreciated.
I think your option 1, employing a "join table" with an order attribute is the most feasible solution for this problem. Indeed, this has been done many times in the past. This is exactly the case for which you would use a join table in Core Data although the framework already gives you many-to-many relationships: if you want to store information about the relationship itself, which is precisely your case. Often these are timestamps, in your case it is a sequence number.
You state: "...solutions, neither of which seem ideal to me". To me, the above seems indeed "ideal". I have used this scheme repeatedly with great performance and maintainability.
The only problem (though it is the same as with a to-one relationship) is that when inserting an item out of sequence you have to update many entities to get the order right. That seems cumbersome and could potentially harm performance. In practice, however, it is quite manageable and performs rather well.
NB: As for arrays or dictionaries to be stored with the entity to keep track of ordering information: this is possible via so-called "transformable" attributes, but the overhead is daunting. These attributes have to be serialized and deserialized, and in order to retrieve one sequence number you have to get all of them. Hardly an attractive design choice.
Before we had ordered relationships for more than 10 years, everyone used a "helper" entity. So that is the thing that you should do.
Additional note 1: This is no "helper" entity. It is a entity that models a fact in your model. In my books I always had the same example:
You have a group entity with members. Every member can belong to many groups. The "helper" entity is nothing else than membership.
Additional note 2: It is hard to synchronize such an ordered relationship. This is why it is not done automatically. However, you have to do it. Since CD and synchronizing is no fun, CD and synchronizing a model with ordered relationship is less than no fun.

How to work with Core Data relationships?

I have two entities:
Patient
- firstName
- lastName
- scheduledAppointments <---->> Appointment
Appointment
- date
- times
- scheduledPatient <<----> Patient
Basically I have one Patient with many appointments. How do I set the scheduledPatient in the Appointments entity? I've tried this so far:
[self.appointment setScheduledPatient:[self.patientArray objectAtIndex:indexPath.row]];
self.appointment.scheduledPatient = [self.patientArray objectAtIndex:indexPath.row];
They work when I'm editing an appointment. But it returns a SIGBRT when I'm adding a new appointment.
Your code seems to be correct.
Therefore I suppose that most likely you have not defined the inverse relation properly in the .xcdatamodel file.
For what I understand you have a one-to-many relationships. That is, one Patient may have a number of Appointments. Therefore, an appointment belongs to one patient. In order for this relation to be semantically correct, you need to let it know how they relate to each other. In order to do so you need to specify what is the inverse element of each of the elements in the relation.
In the picture below you can see how a Region may have a number of states, and a state belongs uniquely to a region. Notice the arrow connecting the elements of the relation, how the "many" has a double arrow and the "one" has a single arrow.
I believe that you most likely forgot to specify this in the xcdatamodel file.
Check out this link for more information:https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdRelationships.html
Inverse Relationships
Most relationships are inherently bi-directional. If a Department has a to-many relationship to the Employees that work in a Department, there is an inverse relationship from an Employee to the Department. The major exception is a fetched property, which represents a weak one-way relationship—there is no relationship from the destination to the source (see “Fetched Properties”).
You should typically model relationships in both directions, and specify the inverse relationships appropriately. Core Data uses this information to ensure the consistency of the object graph if a change is made (see “Manipulating Relationships and Object Graph Integrity”). For a discussion of some of the reasons why you might not want to model a relationship in both directions, and some of the problems that might arise if you don’t, see “Unidirectional Relationships.”

Is it acceptable to have multiple aggregation that can theoretically be inconsistent?

I have a question about the modelling of classes and the underlying database design.
Simply put, the situation is as follows: at the moment we have Positions and Accounts objects and tables and the relationship between them is that a Position 'has an' Account (an Account can have multiple Positions). This is simple aggregation and is handled in the DB by the Position table holding an Account ID as a foreign key.
We now need to extend this 'downwards' with Trades and Portfolios. One or more Trades make up a Position (but a Trade is not a Position in itself) and one or more Portfolios make up an Account (but a Portfolio is not an Account in itself). Trades are associated with Portfolios just like Positions are associated with Accounts ('has a'). Note that it is still possible to have a Position without Trades and an Account without Portfolios (i.e. it is not mandatory to have all the existing objects broken down in subcomponents).
My first idea was to go simply for the following (the first two classes already exist):
class Account;
class Position {
Account account;
}
class Portfolio {
Account account;
}
class Trade {
Position position;
Portfolio portfolio;
}
I think the (potential) problem is clear: starting from Trade, you might end up in different Accounts depending if you take the Position route or the Portfolio route. Of course this is never supposed to happen and the code that creates and stores the objects should never be able create such an inconsistency. I wonder though whether the fact that it is theoretically possible to have an inconsistent database implies a flawed design?
Looking forward to your feedback.
The design is not flawed just because there are two ways to get from class A to class D, one way over B and one over C. Such "squares" will appear often in OOP class models, sometimes not so obvious, especially if more classes lie in the paths. But as Dan mentioned, always the business semantics determine if such a square must commute or not (in the mathematic sense).
Personally I draw a = sign inside such a square in the UML diagram to indicate that it must commute. Also I note the precise formula in an UML comment, in my example it would be
For every object a of class A: a.B.D = a.C.D
If such a predicate holds, then you have basically two options:
Trust all programmers to not break the rule in any code, since it is very well documented
Implement some error handling (like Dan and algirdas mentioned) or, if you don't want to have such code in your model, create a Checker controller, which checks all conditions in a given model instance.

Many to Many relationship for single entity

I'm currently writing my first project using core data, and am having trouble working out how to query the relationship between some of my data.
In sql language, i have a Country table, which joins to a CountryLink M-M table containing the following fields:
countryId1
countryId2
bearing
What would be the correct way to model this in Core Data?
So far i have set up a single Country entity and a CountryLink entity (containing only a bearing field) and have added two 1-to-Many relationships from Country to CountryLink ('CountryLink1' and 'CountryLink2').
I've run the project and looked at the Sqlite db structure produced by Core Data (found here, using this sqlite gui), and the M-M join table seems correct (it contains the bearing, CountryLink1 and CountryLink2 fields), but i'm not sure how i would go about carrying out a fetch request for a single Country NSManagedObject to return an array of related Countries and their bearings?
Any help or related links would be much appreciated.
Thanks, Ted
First a word of warning:
Core Data is not SQL. Entities are not tables. Objects are not rows. Columns are not attributes. Core Data is an object graph management system that may or may not persist the object graph and may or may not use SQL far behind the scenes to do so. Trying to think of Core Data in SQL terms will cause you to completely misunderstand Core Data and result in much grief and wasted time.
See the Tequilla advice
Now, forgetting SQL and thinking in object graphs, your entities would look something like this:
Country{
someAttribute:string // or whatever
countryLinks<-->>CountryLink.country
}
CountryLink{
countryID1:string // or whatever
countryID2:string // or whatever
country<<-->Country.countryLinks
}
As you add Country and CountryLink objects you add them to the relationships as needed. Then to find CountryLink objects related to a specific Country object, you would perform a fetch on the Country entity for Country objects matching some criteria. Once you have that object, you simply ask it for the CountryLink objects in its countryLinks relationship. And your done.
The important thing to remember here is that entities in combination with managedObjects are intended to model real-world objects, conditions or events and the relationship between the same. e.g. a person and his cars. SQL doesn't really model or simulate, it just stores.