I have an app that uses CoreData. There are several entities - user, conversation, message. It looks like this:
Conversation: (1-many) Message (the messages in a conversation)
Conversation: (many-many) User (the users in a conversation)
Message (1-1) User (the sender of a message)
I have a strange situation where sometimes the sender of a message is an empty database object even though the same user in the conversation is not empty. I've looked at the two user objects and they have a very similar URI representation:
message.user (empty)
x-coredata://68B3C0B2-8441-4BD7-BA58-358959F87947/CDUser/p7
vs:
conversation.users[0] (not empty)
x-coredata://68B3C0B2-8441-4BD7-BA58-358959F87947/CDUser/p4
I'm not sure what the p7 vs p4 refers to and why CoreData would create a duplicate of the entity.
So there are two versions of the same database entity. My CoreData setup looks like this:
_parentMoc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_parentMoc.persistentStoreCoordinator = self.persistentStoreCoordinator;
_mainMoc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_mainMoc.parentContext = _parentMoc;
_mainMoc.automaticallyMergesChangesFromParent = YES;
_mainMoc.undoManager = [[NSUndoManager alloc] init];
_backgroundMoc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_backgroundMoc.parentContext = _mainMoc;
_backgroundMoc.automaticallyMergesChangesFromParent = YES;
What I don't understand is why I have one entity duplicated... Any help would be appreciated.
Related
I have three entities
Forms{
name:string
jobs<-->>JSAjobs.form
}
JSAjobs{
name:string
form<<-->Forms.jobs
}
Jobs{
step:string
jobs<<-->Forms.jobs
}
I am getting this error:
to-many relationship fault "jobs" for objectID 0x95afe60. . . fulfilled from database. Got 0 rows
Now I save the row for Forms entity first later on I need to fetch the last record on the Form entity create a new row on JSAjobs with details on JSAjop like next
Thanks
NSMutableArray *jobData = [[NSMutableArray alloc]initWithArray:controller.jobData];
NSManagedObjectContext *context = [self managedObjectContext];
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"JSAform" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *testForFalse = [NSPredicate predicateWithFormat:#"emailed == NO"];
[fetchRequest setPredicate:testForFalse];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
NSLog(#"Fetched Rows: %i", [fetchedObjects count]);
//NSManagedObject *existingParent= //... results of a fetch
JSAform *lastForm = [fetchedObjects objectAtIndex:0];
JSAjobs *newJobs = [NSEntityDescription insertNewObjectForEntityForName:#"JSAjobs" inManagedObjectContext:context];
// Setting new values
newJobs.jobType = [NSString stringWithFormat:#"%#", [jobData objectAtIndex:0]];
newJobs.jobName = [NSString stringWithFormat:#"%#", [[[jobData objectAtIndex:1]objectAtIndex:0] objectAtIndex:0]];
newJobs.comments = [NSString stringWithFormat:#"%#", [[[jobData objectAtIndex:1]objectAtIndex:0] objectAtIndex:1]];
newJobs.date = [NSDate date];
[newJobs setValue:lastForm forKey:#"form"];
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
//New SOP Value
JOBsop *jobSOP = [NSEntityDescription insertNewObjectForEntityForName:#"JOBsop" inManagedObjectContext:context];
for (int i = 0; i< [[jobData objectAtIndex:1]count]; i++){
NSLog(#"Value for key: %i", i);
if (i > 0){
for (int k = 0; k< [[[jobData objectAtIndex:1]objectAtIndex:i] count]; k++){
jobSOP.step = [[[jobData objectAtIndex:1]objectAtIndex:i] objectAtIndex:k];
[jobSOP setValue:newJobs forKey:#"jobs"];
// [NSNumber numberWithInt:[txtBoots.text integerValue]];
NSLog(#"Simple key: %#", [[[jobData objectAtIndex:1]objectAtIndex:i] objectAtIndex:k]);
}
}
}
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
enter code here
Your entities are very confusing because you did not pick usable entity names. You were too confused to lay out these simple relationships yourself. This results in chaotic code and does not allow you to think things through in a structured way.
Your code is completely incomprehensible. You have a data array despite a fetched results controller (presumably). The second part of your code sports a mysterious and cryptic new entity JOBsob. No way you can ask a meaningful question like this, let alone get an answer.
You have nested arrays without any type checking which is bound to break and in any way completely impossible to debug. Get rid of all of these.
Nevertheless, let's five it a try to get you started:
First, it does not make sense to use the plural for the entity name. If the entity represents a "form", it should be Form not Forms.
Maybe you want this setup:
Form <<----> Job <----->> JobDetail
One job has many forms and many job details. So the Form has a relationship job while the Job has a relationship forms. Similarly, a Jobdetail has a relationship job while Job has a relationship details.
When you have a form and create a new job you can only assign one job to it. Thus, the old job (if any) relationship would be broken.
oldForm.job = newJob;
This is a much safer way to assign relationships. Of course, you have created NSManagedObject subclasses for these entities for this purpose.
If however, the relationship between Job and Form is one-to-many in the other direction, your scheme would look like this.
Form <---->> Job <------>> JobDetail
I do not really now what a "form" would mean in this case - I will just rename it Project for clarity.
Project <---->> Job <------>> JobDetail
Now you can assign the a new job to a project and link the other relationships like this:
newJob.project = existingProject;
newJobDetail.job = newJob;
I've read a lot of similar posts but after two days, I thought I should ask my own question.
I have a separate CoreData Controller. This passes the entity object fine from AppDelegate to the RootViewController. It does not pass it to a specific (Category) view controller, and I cant figure out why.
The code in App Delegate where I try to pass the object is this:
rootViewController.managedObjectContext = self.coreDataController.mainThreadContext;
categoryListViewController.managedObjectContext = self.coreDataController.mainThreadContext;
NSLog(#"AD/core data controller is %#", coreDataController.mainThreadContext);
NSLog(#"AD- rootVC is %#", rootViewController.managedObjectContext);
NSLog(#"AD/category list is %#", categoryListViewController.managedObjectContext);
and the logs show that the core data controller and the root vc get populated, but the Category vc doesn't.
2012-12-02 14:28:33.187 [50351:907] AD/coredatacontroller moc is <NSManagedObjectContext: 0x21065160>
2012-12-02 14:28:33.188 [50351:907] AD/categorycontroller moc is (null)
2012-12-02 14:28:33.190 [50351:907] AD- rootVC moc is <NSManagedObjectContext: 0x21065160>
Any ideas why?
UPDATE
If I do as suggested by Valentin, and init the Category VC in the App Delegate, I certainly get the managed objects passed through, however, as I call the view from the Detail VC. When I do that, I get the error "Application tried to push a nil view controller on target ".
If I try to init the category VC (and load the context) in the detail VC, it does not convey, and the logs show the context to be nil.
Init the VC (in App Delegate):
categoryListViewController = [[CategoryListViewController alloc] initWithNibName:#"CategoryList-iPad" bundle:nil];
// we have loaded from our xib, so has our CoreDataController,
// so connect as its delegate and setup its persistent store
//
self.coreDataController.delegate = self;
[self.coreDataController loadPersistentStores];
UINavigationController *rootNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
UINavigationController *detailNavigationController = [[UINavigationController alloc] initWithRootViewController:detailViewController];
// Set up MASTER and DETAIL delegation so we can send messages between views
rootViewController.detailViewController = detailViewController;
detailViewController.rootViewController = rootViewController;
splitViewController = [[UISplitViewController alloc] init];
splitViewController.viewControllers = #[rootNavigationController, detailNavigationController];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
splitViewController.delegate = detailViewController;
rootViewController.managedObjectContext = self.coreDataController.mainThreadContext;
categoryListViewController.managedObjectContext = self.coreDataController.mainThreadContext;
NSLog(#"AD - coreDataController is %#", coreDataController.mainThreadContext);
NSLog(#"AD - rootViewController is %#", rootViewController.managedObjectContext);
NSLog(#"AD - categoryListVC is %#", categoryListViewController.managedObjectContext);
Call the view (in DetailViewController):
-(void)categoryButtonTapped {
NSLog(#"%s", __FUNCTION__);
//categoryListViewController = [[CategoryListViewController alloc] initWithNibName:#"CategoryList-iPad" bundle:nil];
//categoryListViewController.managedObjectContext = coreDataController.mainThreadContext;
//categoryListViewController.managedObjectContext = self.coreDataController.mainThreadContext;
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:categoryListViewController];
nc.modalPresentationStyle = UIModalPresentationFormSheet;
NSLog(#"DVC FRC is %#", self);
NSLog(#"DVC FRC/moc is %#", coreDataController.mainThreadContext);
NSLog(#"DVC FRC/self.moc is %#", self.coreDataController.mainThreadContext);
[self presentViewController:nc animated:YES completion:nil];
//[self.navigationController pushViewController:categoryListViewController animated:YES];
}
Most probably your categoryListViewController is nil as well. Try to see if it gets alloc'ed/initialised correctly.
Ok I am trying to grab a NSMutable Set. Yes I have a previous post on this but this is slightly different. I have A player entity and a team entity. It is set up as a one to many relationship... On a different view controller I added players to the team. Now I am trying to get that teams players to show up on a table view... I am fetching the information as follows.
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSString *entityName = #"Team";
NSLog(#"Setting up a Fetched Results Controller for the Entity named %#", entityName);
// 2 - Request that Entity
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
_managedObjectContext = delegate.managedObjectContext;
// 4 - Sort it
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"players"
ascending:NO
selector:#selector(localizedCaseInsensitiveCompare:)]];
// 5 - Fetch it
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
Then on my cell for row at index path I am setting the Player object to the fetched results as follows
Player *p = [_fetchedResultsController objectAtIndexPath:indexPath];
Then, I am setting the title of the cell like so.
cell.textLabel = p.firstName;
I am getting the error
reason: 'to-many key not allowed her
I am wondering what am I doing wrong???
Figured it out! I was sorting on a one to many relationship which is a NO NO in Core Data. I switched to sort on the player object and added a predicate to find the proper players I needed.
This is a follow up question to my previous question on:
Core data: Managing employee contracts in a many-to-many relationship?
There is a diagram on that question, and as a quick reminder there is the following:
company --< contracts >-- employees
I have been able to manually save 1 entity inside each of the entities, and verified them all in NSLog.
I've created a CompanyListPage which lists all companies. The idea is that when you click on a company you will be presented with a list of all employees who have a contract with said company.
As context see below:
Company:
name: A
Contract:
length: 33 wks
salary: 20000
Employee:
name: Bob Jones
In my didSelectRowAtIndex page within my CompanyListPage I have the following.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Company *c = [fetchedResultsController objectAtIndexPath:indexPath];
NSLog(#"You clicked %#", c.name);
NSString *companyName = c.name;
EmployeesListPage *employeesListPage = [[EmployeesListPage alloc] initWithNibName:#"EmployeesListPage" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:employeesListPage animated:YES];
employeesListPage.title = companyName;
employeesListPage.managedObjectContext = self.context;
employeesListPage.managedObject = c;
[superstarsList release];
}
The problem however is, I am not sure what my NSPredicate should look like when I eventually go to the employeesListPage.
At the moment, its this:
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
// Create and configure a fetch request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Employees" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
// Create the sort descriptors array
NSSortDescriptor *authorDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:authorDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Create and initialize the fetch results controller
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
self.fetchedResultsController = aFetchedResultsController;
fetchedResultsController.delegate = self;
// Memory management.
[aFetchedResultsController release];
[fetchRequest release];
[authorDescriptor release];
[sortDescriptors release];
return fetchedResultsController;
}
Obviously this is wrong, because its not:
a) Looking in the contracts entity
b) Using the company entity in any way, shape or form
I know I need to use a NSPredicate, but I just know how to make it say "Find me all the employees with a contract length > 0 and working with company A" then order it by the name of the person descending, or even order it by the least contract length first.
Any pointers or help on this would be great. Thank you.
EDIT: First attempt (removed because I got it to work following an answer provided below)
EDIT: Unable to get contract information back?
I've been able to get all the employees that work for Company A back in my table view controller.
However I'm wanting to display in my cell information about the employee and their contract length/salary.
I've tried the following:
Employee *emp = [fetchedResultsController objectAtIndexPath:indexPath];
NSString *firstname = emp.firstname;
NSString *surname = emp.surname;
NSString *fullname = [firstname stringByAppendingString:#" "];
fullname = [fullname stringByAppendingString:surname];
// Logging tests
NSLog(#"Name: %#", fullname); // This is fine
NSLog(#"Contracts: %#", emp.empContracts); // This tells me of a problem with "Relationship fault for <NSRelationshipDescription: 0x602fdd0>"
I believe I need to get the NSPredicate to grab all the contract data, and not just the contract data; however I may be mistaken.
Again, help on this would be greatly appreciated.
I think if you use the ANY keyword, you'll restore the left side of the equation to a single quantity, which will agree with the right side of the equation:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#“ANY contracts.employer == %#“, employer];
I think I have an answer now.
I did the following in my table view and got a valid relationship and all the output; however I am not sure if I am doing it right because it involves a for-loop.
I used the following StackOverflow related question/answer for my solution.
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
Employee *emp = [fetchedResultsController objectAtIndexPath:indexPath];
NSString *firstname = emp.firstname;
NSString *surname = emp.surname;
NSString *fullname = [firstname stringByAppendingString:#" "];
fullname = [fullname stringByAppendingString:surname];
NSLog(#"Employee: %#", fullname);
// Output the contract deals.
for (Contracts *deals in emp.contracts)
{
NSNumber *cLength = deals.length;
NSNumber *cSalary = deals.salary;
NSLog(#"Length: %#", cLength);
NSLog(#"Salary: %#", cSalary);
} // next
}
The next step for me is to include this in a custom view with custom labels to hold all this info.
If the for-loop is not the most efficient way to do this, I would welcome any suggestions/improvements, or even what is considered best practice.
Thank you.
Looks like it’s coming along well. I think all you need from there is to set the cell’s text: cell.textLabel = fullname.
But I’m not clear why you’re delving back into the contracts relationship — until I look at your sample text at the top. There, it looks like what you want is a list of contracts for the employer, and for each contract you want to show its to-one employee relationship and other attrs. In which case you don’t need a new fetch at all; you already have the employer and, through its to-many contracts relationship, you also have the contracts entities and, through them, the term, salary, employee, whatever.
Upon selection change in the employers table, you would do something like this:
NSUInteger row = [indexPath row];
Employer *employer = [self.arrayOfEmployersUsedToPopulateTable objectAtIndex:row];
self.selectedEmployer = employer;
self.arrayOfSelectedEmployersContracts = [employer.contracts allObjects]; // which probably faults them, but I think that’s OK here
// Then make a call to reload the other table, the one presenting the contracts info.
In the contracts table, you’d refer back to the selected employer, and present info for each contract:
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
Contract *contract = [self.arrayOfSelectedEmployersContracts objectAtIndex:row];
NSUInteger column = [indexPath column];
switch (column) {
(0) :
cell.textLabel = contract.length; // maybe you have to use an NSNumber method to convert to string, but probably not
break;
(1):
cell.textLabel = contract.employee.firstname;
break;
// and so forth
}}
P.S. For the UITableCell stuff, I consulted Beginning iPhone Development, by Mark and LaMarche, “Using the New Table View Cell.” You might like this book too.
The code bellow add to Core data issues, but after it added, I can't save with error (multiply validation error occured)
MySQLIXC *ixcDatabase = [[MySQLIXC alloc] init];
NSArray *destinationsForSaleList = [ixcDatabase destinationsForSaleList:carrier];
NSFetchRequest *request1 = [[[NSFetchRequest alloc] init] autorelease];
[request1 setEntity:[NSEntityDescription entityForName:#"DestinationsList"
inManagedObjectContext:managedObjectContext]];
for (NSDictionary *destinationsForSale in destinationsForSaleList) {
NSManagedObject *object1 = [NSEntityDescription
insertNewObjectForEntityForName:#"DestinationsList"
inManagedObjectContext:managedObjectContext];
NSLog(#"Moc: %#",managedObjectContext);
[object1 setValue:#"y" forKey:#"weAreSoldIt"];
// changeDate
NSString *chdate = [destinationsForSale objectForKey:#"chdate"];
NSDateFormatter *changeDate = [[[NSDateFormatter alloc] init] autorelease];
[object1 setValue:[changeDate dateFromString:chdate] forKey:#"changeDate"];
NSLog(#"Carrier :%# Destination name:%#",carrier, destinationsForSale);
//Country
[object1 setValue:[destinationsForSale objectForKey:#"country"] forKey:#"country"];
//rate
NSNumberFormatter *rate = [[[NSNumberFormatter alloc]init ]autorelease];
[object1 setValue:[rate numberFromString:[destinationsForSale objectForKey:#"price"]] forKey:#"rate"];
Unfortunately I can't fix a bug by the way which u propose bellow.
Bcs Entity DestinationList must have relations with Entity Carriers by project understanding.
That is how I try to fix it:
[objectDestinationList setValue:objectCarrier forKey:#"carrier"];
I was send to method my carrier object as object, but it doesn't work.
In this case, I don't know how is a way to fix it around. Bcs I see error, but don't see case why error is start.
Do u know a simple code to correct add relationships to Entity? All what I catch around internet is a core data book ,my Marcus Zarra and his very hard to understanding example. His showing a complex solution, I can understand it, but using programming style which not very easy for me at this moment (according my one month experience in cocoa programming ;)
Here is additional information: How I create Carrier instance. I have managedObjectContext, which I receive to class from AppDelegate.
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[NSEntityDescription entityForName:#"Carrier"
inManagedObjectContext:managedObjectContext]];
NSManagedObject *carrier = [managedObjectContext executeFetchRequest:request error:&error]
by the same way I prepare NSManagedObject for DestinationsList Entity.
After that I add all values to NSManagedObject for destinationsList, I have to make relationship between Carrer NSManagedObject and destinationsList. In this case I have trouble. Bellow is how I try to update relationship for Carrier entity:
NSSet *newDestSet = [NSSet setWithObjects:objectDestination,nil];
[objectCarrier setValue:newDestSet forKey:#"destinationsList"];
finally I have 2010-11-03 21:22:56.968 snow[20301:a0f] -[NSCFArray initialize]: unrecognized selector sent to instance 0x1c44e40
Bellow is my class interface deescription:
#interface InitUpdateIXC : NSObject {
NSInteger destinationType;
}
-(void) updateCarrierList:(NSManagedObjectContext *)managedObjectContext;
-(void)updateDestinationList:(NSManagedObjectContext *)managedObjectContext
forCarrier:(NSString *)carrierName
forCarrierObject:(NSManagedObject *)objectCarrier
destinationType:(NSInteger)destinationType;
#end
Yep, bellow in answer present correct model, but some different is here.
At first, i don't have separate class for Entity as u present in you model. My current class is just NSManagedObject
In second, relationship "carrier" is non-optional for Entity DestinationsList.
SOLUTION AND ERROR DESCRIPTION:
In case of trouble, what happened with my code:
When i try to add setValue forKey with back relationship from DestinationsList to Carrier, i forget that NSManagmentObject return every time array, not just object.
This is a reason why i receive error about array init problem.
Solution is not sent Carrier object to method, bcs for me was too hard to extract from array correct object without key value. I was using a predicate access to extract object to array and lastObject function to extract correct object from array. After that i set accroding value and everything working fine.
A solution not look like cocoa-style, so better way is refactor it in future, any suggestion wellcome.
Here is appropriate code:
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[NSEntityDescription entityForName:#"Carrier"
inManagedObjectContext:managedObjectContext]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name =%#",carrierName];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *currentCarriers = [managedObjectContext executeFetchRequest:request error:&error];
[objectDestination setValue:[currentCarriers lastObject] forKey:#"carrier"];
Try adding something like this for you 'save'
NSError *error = nil;
if (![managedObjectContext save:&error])
{
// Handle the error.
NSLog(#"Failed to save to data store: %#", [error localizedDescription]);
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0)
{
for(NSError* detailedError in detailedErrors)
{
NSLog(#" DetailedError: %#", [detailedError userInfo]);
}
}
else
{
NSLog(#" %#", [error userInfo]);
}
}
At least, then you can see what the multiple errors are. If you post those, someone may be able to offer more help.
One thought, though, is that there is something buggy about your data model - like non-optional attribute with no value, etc.
If you create NSManagedObject subclassed Carrier and DestinationsList, then in Carrier.h you should have some method declarations like this. (Assuming that Carrier to-many DestinationsList is called 'destinationsLists'.)
#interface Carrier (CoreDataGeneratedAccessors)
- (void)addDestinationsListsObject:(Run *)destinationsList;
- (void)removeDestinationsListsObject:(Run *)destinationsList;
- (void)addDestinationsLists:(NSSet *)destinationsLists;
- (void)removeDestinationsLists:(NSSet *)destinationsLists;
#end
Once these are declared, you should be able to add a DestinationsList to a Carrier with a line like this:
[myCarrier addDestinationsListsObject:myDestinationsList];
Not seeing your full data model, it is difficult to know what is happening and what would help fix it.
Do you have something like this for your model definition?