I have an object OrganizationalUnit which is linked to parent entities through a parentId attribute. OrganizationalUnit has also a primary key attribute ouId (mapped from the JSON "_id" field).
This is my code:
RKManagedObjectMapping *ouMapping = [RKManagedObjectMapping mappingForClass:[OrganizationalUnit class] inManagedObjectStore:manager.objectStore];
[ouMapping mapAttributes:#"name",#"type", nil];
[ouMapping mapKeyPath:#"_id" toAttribute:#"ouId"];
[ouMapping setPrimaryKeyAttribute:#"ouId"];
[ouMapping mapKeyPath:#"parent" toAttribute:#"parentId"];
[ouMapping hasOne:#"parent" withMapping:ouMapping];
[ouMapping connectRelationship:#"parent" withObjectForPrimaryKeyAttribute:#"parentId"];
Which works the same way of the example found in RestKit:
RKManagedObjectMapping* taskMapping = [RKManagedObjectMapping mappingForClass:[Task class] inManagedObjectStore:objectStore];
taskMapping.primaryKeyAttribute = #"taskID";
[taskMapping mapKeyPath:#"id" toAttribute:#"taskID"];
[taskMapping mapKeyPath:#"name" toAttribute:#"name"];
[taskMapping mapKeyPath:#"assigned_user_id" toAttribute:#"assignedUserID"];
[objectManager.mappingProvider setMapping:taskMapping forKeyPath:#"task"];
// Hydrate the assignedUser association via primary key
[taskMapping hasOne:#"assignedUser" withMapping:userMapping];
[taskMapping connectRelationship:#"assignedUser" withObjectForPrimaryKeyAttribute:#"assignedUserID"];
which should create locally a relationship between objects based on the ids.
Unfortunately at run-time, when the actual mapping of received data is happening, I get the error:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFString 0x6d9c6f0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key _id.'
Any idea?
I think I'm getting to the root of the problem, probably caused by the children not able to reference the parents because they do not yet exist.
Related
To preface this question: my problem is not because I'm directly setting a key property in my model entity object (which is the cause of the issue in other search results for the same exception message).
I'm making heavy use of composite keys in my application, here's a simplified version of my current DB schema (key fields in *asterisks*):
Tenants( *TenantId*, ... )
Categories( *TenantId*, *CategoryId*, ... )
Documents( *TenantId*, *DocumentId*, CategoryId, ... )
The Documents table has FK relationships with both Tenants and Categories, both using the same Documents.TenantId column. The Documents.CategoryId column is NULLable.
When I do something like this, I get the exception:
Tenant tenant = GetTenant( 123 );
Document doc = tenant.Documents.First();
Category newCategory = new Category();
newCategory.TenantId = 123;
dbContext.Categories.Add( newCategory );
doc.Category = newCategory; <-- exception is thrown on this line, without calling dbContext.SaveChanges() at all.
I believe the exception is because setting Category on the Document instance causes the TenantId property to be set indirectly by EF (because it's part of the Documents -> Categories FK association.
What is the solution?
Workaround Update
I'm able to hack it by creating the new Category entities then saving them, to get the IDENTITY values back, then setting the Document properties directly:
Tenant tenant = GetTenant( 123 );
Document doc = tenant.Documents.First();
Category newCategory = new Category();
newCategory.TenantId = 123;
dbContext.Categories.Add( newCategory );
dbContext.SaveChanges();
doc.CategoryId = newCategory.CategoryId
dbContext.SaveChanges();
But ideally I'd like this to work in a single call to SaveChanges() and using the Entity Model Navigation Properties instead of scalar attribute properties.
For this initial problem, I worked-around it using the "Workaround Update" I posted to my original posting.
However this problem happened again for a different entity type (again, with a composite key involved in a foreign-key) and I noticed that EF throws the exception even if you call dbContext.Entry() on any entity in the graph while the new entity is in the Added state - but it does not throw the exception again if you re-call Entry() or even SaveChanges(), and in fact it saves the new entities correctly in spite of the initial exception - so I'm thinking this might just be a bug in EF.
Here's essentially what I have now:
Tenant tenant = GetTenant( 123 );
Document doc = tenant.Documents.First();
Category newCategory = new Category();
newCategory.TenantId = 123;
dbContext.Categories.Add( newCategory );
doc.CategoryId = newCategory.CategoryId
try {
dbContext.Entry( doc );
}
catch(InvalidOperationException) {
}
dbContext.SaveChanges();
It's ugly, but works - and avoids having to call SaveChanges twice.
private void SuaKH()
{
KhachHang kh = new KhachHang();
kh.MaKH = textBox1.Text;
kh.TenKH = textBox2.Text;
kh.SDT = textBox4.Text;
kh.DiaChi = textBox3.Text;
db.KhachHangs.Attach(kh);
db.Entry(kh).State = EntityState.Modified;
db.SaveChanges();
}
Attaching an entity of type 'WindowsFormsApplication1.Models.KhachHang' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the
I have this entity class having more than one primary key (#Id) which resulted me to use #RooJpaActiveRecord(identifierType = '<IdentifierClass.class>') and #RooIdentifier(dbManaged=true). Yet I am having a problem on accessing Identifier.class from the entity class itself.
My questions is how can I access Identifier in the entity class without, at most, removing the #RooJpaActiveRecord(identifierType = '<Identifier.class>') code.
Roo will generate a property id on your entity. This property is declared on MyEntity_Roo_Jpa_Entity.aj file (this includes a getter/setter). So, you simple use:
MyEntity myEntity = new MyEntity();
myEntity.setId(new Identifier());
myEntity.getId().setPk1(value1);
// ....
// ....
myEntity.getId().setPkn(valuen);
Below is my schema generation code, Two variables (storeIdForSurvey & questionIdForAnswer) are not being auto generated in the model class( Survey & Question), though they are present the in auto generated dao classes (SurveyDao & QuestionDao).
Object Oriented description of the domain is as : User has Store, Store has Survey,Survey has FollowupItems, Survey has Category, Category has Question, Question has History, Question has Answer.
private static void addUser(Schema schema) {
//User
Entity user = schema.addEntity("User");
user.addIdProperty();
user.addStringProperty("districtId");
user.addStringProperty("employeeId");
user.addStringProperty("name");
user.addStringProperty("sessionToken");
user.addStringProperty("userId");
//Store
Entity store = schema.addEntity("Store");
// foreign key
Property userIdForStore = store.addLongProperty("userIdForStore").getProperty();
store.addToOne(user, userIdForStore);
user.addToMany(store, userIdForStore);
store.addIdProperty();
store.addStringProperty("storeId");
store.addStringProperty("address");
store.addStringProperty("city");
store.addStringProperty("storeName");
store.addStringProperty("state");
store.addStringProperty("zip");
store.addStringProperty("storeManagerName");
store.addBooleanProperty("isSurveyHistoryAvailable");
//Survey
Entity survey = schema.addEntity("Survey");
//foreign key
Property storeIdForSurvey = survey.addLongProperty("storeIdForSurvey").getProperty();
survey.addToOne(store, storeIdForSurvey); // one store can have one survey at a time
store.addToOne(survey, storeIdForSurvey);
survey.addIdProperty();
survey.addStringProperty("surveyId");
survey.addStringProperty("dmSignImagePath");
survey.addStringProperty("dmSignImageName");
survey.addStringProperty("smSignImagePath");
survey.addStringProperty("smSignImageName");
survey.addStringProperty("startlatitude");
survey.addStringProperty("startlongitude");
survey.addStringProperty("submitLatitude");
survey.addStringProperty("submitLongitude");
survey.addStringProperty("acknowledgedBy");
survey.addStringProperty("deliveredBy");
survey.addStringProperty("name");
survey.addStringProperty("createdBy");
survey.addStringProperty("description");
survey.addStringProperty("storeId");
survey.addStringProperty("districtManager");
survey.addDateProperty("startDate");
survey.addDateProperty("submitDate");
survey.addDateProperty("syncDate");
survey.addDateProperty("createdDate");
survey.addDateProperty("actionItemAssignDate");
survey.addDateProperty("actionItemDueDate");
survey.addDoubleProperty("score");
//FolloupItems
Entity followupItem = schema.addEntity("FollowupItem");
//foreign key
Property surveyIdForFollowupItem = followupItem.addLongProperty("surveyIdForFollowupItem").getProperty();
followupItem.addToOne(survey, surveyIdForFollowupItem);
survey.addToMany(followupItem, surveyIdForFollowupItem);
followupItem.addIdProperty();
followupItem.addStringProperty("assignedTo");
followupItem.addStringProperty("comment");
followupItem.addStringProperty("photoName");
followupItem.addStringProperty("photoURL");
followupItem.addDateProperty("assignedDate");
followupItem.addDateProperty("dueDate");
followupItem.addDateProperty("expeireDate");
//Category
Entity category = schema.addEntity("Category");
//foreign key
Property surveyIdForCategory = category.addLongProperty("surveyIdForCategory").getProperty();
category.addToOne(survey, surveyIdForCategory);
survey.addToMany(category, surveyIdForCategory);
category.addIdProperty();
category.addStringProperty("categoryId");
category.addStringProperty("name");
category.addStringProperty("weight");
category.addStringProperty("surveyId");
category.addDoubleProperty("totalScore");
category.addIntProperty("sortOrder");
category.addBooleanProperty("completionStatus");
category.addBooleanProperty("hasActionItem");
//Question
Entity question = schema.addEntity("Question");
//foreign key
Property categoryIdForQuestion = question.addLongProperty("categoryIdForQuestion").getProperty();
question.addToOne(category, categoryIdForQuestion);
category.addToMany(question, categoryIdForQuestion);
question.addIdProperty();
question.addStringProperty("questionId");
question.addDateProperty("startDate");
question.addDateProperty("endDate");
question.addStringProperty("statement");
question.addStringProperty("type");
question.addStringProperty("weight");
question.addStringProperty("surveyCategoryName");
question.addIntProperty("displayOrder");
question.addBooleanProperty("naFlag");
question.addBooleanProperty("isRequired");
//Question History
Entity questionHistory = schema.addEntity("questionHistory");
//foreign key
Property questionIdForQuestionHistory = questionHistory.addLongProperty("questionIdForQuestionHistory").getProperty();
questionHistory.addToOne(store, questionIdForQuestionHistory);
question.addToMany(questionHistory, questionIdForQuestionHistory);
questionHistory.addIdProperty();
questionHistory.addStringProperty("questionId");
questionHistory.addStringProperty("secondLastHistory");
questionHistory.addStringProperty("lastHistory");
//Answer
Entity answer = schema.addEntity("Answer");
//foreign key
Property questionIdForAnswer = answer.addLongProperty("questionIdForAnswer").getProperty();
question.addToOne(answer, questionIdForAnswer);
answer.addToOne(question, questionIdForAnswer);
answer.addIdProperty();
answer.addStringProperty("projectType");
answer.addStringProperty("assignedTo");
answer.addStringProperty("comment");
answer.addStringProperty("photoUrl");
answer.addStringProperty("photoNmae");
answer.addStringProperty("selectedOption");
answer.addDateProperty("assignedDate");
answer.addDateProperty("dueDate");
answer.addDateProperty("expireDate");
answer.addDoubleProperty("score");
}
please read the documentation carefully:
public ToOne addToOne(Entity target, Property fkProperty)
Adds a to-one relationship to the given target entity using the given given
foreign key property (which belongs to this entity).
This means the following statement is correct:
Property storeIdForSurvey = survey.addLongProperty("storeIdForSurvey").getProperty();
survey.addToOne(store, storeIdForSurvey);
but the next statement is incorrect since the Property storeIdForSurvey is not member of the Entity store:
store.addToOne(survey, storeIdForSurvey);
Try to use this statement instead:
store.addToOneWithoutProperty("Survey", survey, "storeIdForSurvey");
I am using Apple's CoreDataBooks sample application as a basis for pulling data into a secondary managed object context in the background, and then merging that data into the primary managed object context.
The data I am pulling in is a Book entity with a to-one relationship with an Owner entity (called "owner"). The Owner entity has a to-many relationship with the Book (called "books").
My data is an XML document of the form:
<Owner>
<Name>alexpreynolds</Name>
<ID>123456</ID>
</Owner>
<Books>
<Book>Book One</Book>
<Book>Book Two</Book>
...
<Book>Book N</Book>
</Books>
Book One through Book N are associated with one Owner ("alexpreynolds, 123456").
I am parsing this into an Owner instance and an NSMutableSet made up of Book instances.
When I attempt to save the first time, it saves fine and the merged data shows up in the table view.
On the second save, however, when the XML content contains a new book, it doesn't work.
Here's what happens:
I then attempt to load in an XML document that contains a new Book not already in the primary managed object context. The new Book is using the same Owner as that which is associated with the other Books.
I have routines that pick out this unique Owner managed object (which I already have in my primary managed object context) and the unique Book that is not found in the primary MOC.
From this, I create a new Book object in the secondary MOC, and I set its "owner" relationship to point to the unique Owner I found in the primary MOC.
When I save, I get the following error:
*** Terminating app due to uncaught
exception 'NSInvalidArgumentException',
reason: 'Illegal attempt to establish a
relationship 'owner' between objects in
different contexts
(source = <Book: 0x7803590>
(entity: Book; id: 0x7802ae0 <x-coredata:///
Book/t527F06B2-3EB5-47CF-9A29-985B0D3758862>
; data: {
creationDate = 2009-10-12 06:01:53 -0700;
name = nil;
nameInitial = nil;
operations = (
);
owner = nil;
type = 0;
}) ,
destination = <Owner: 0x78020a0> (entity:
Owner; id: 0x3a56f80 <x-coredata://043AF2F0-1AD0-
4078-A5E8-E9D7071D67D1/Owner/p1> ; data: {
books = "<relationship fault: 0x7801bf0 'books'>";
displayName = alexpreynolds;
ownerID = 123456;
}))'
How do I create a new Book entity in the secondary MOC, so that I can still associate it with a pre-existing Owner in the primary MOC?
You can't have relationships between objects in different managed object contexts. So one way of getting around that is to bring the object into the managed object context.
For example:
NSManagedObject *book = // get a book in one MOC
NSManagedObject *owner = // get an owner in a different MOC
[[owner mutableSetValueForKey:#"books"] addObject:[owner.managedObjectContext objectWithID:[book objectID]]];
So what you're doing is actually fetching the Book into the same managed object context with owner. Keep in mind, though, that this is only possible if book has already been saved. The managed object context is going to look for the object in the persistent store, so it has to be saved first.
I had the same problem and this sentence helped me to solve the error.
You can't have relationships between objects in different managed
object contexts. So one way of getting around that is to bring the
object into the managed object context.
Here's my code (I replaced the variables to make it work for you):
// Have the owner object and get the managedObjectContext
Owner *owner = [[DataFunctions alloc] getCurrentOwner];
NSManagedObjectContext *context = [owner managedObjectContext];
// Create a book and use the manageObjectContext of owner
Book *book = [NSEntityDescription insertNewObjectForEntityForName:#"Book" inManagedObjectContext:context];
[newQuote setValue: "Book Title" forKey:#"title"];
[newQuote setOwner:owner];
I hope this helps :)
Here, you are trying to establish a relation between two objects which are fetched/created with a different context. Core data won't allow you to establish such relations. To achieve this, you should fetch the second object (you are trying to make a relationship with) with the context of the first object with objectID. Now you should be able to establish a relation between these two objects. For example:
MagicalRecord.saveWithBlock({[unowned self] (localContext: NSManagedObjectContext!) in
let user = User.MR_createEntityInContext(localContext)!
.....
.....
}) //here local context stores data on end of block itself
MagicalRecord.saveWithBlock({[unowned self] (localContext: NSManagedObjectContext!) in
let address = Address.MR_createEntityInContext(localContext)!
.....
.....
let user = localContext.objectWithID(self.user!.objectID) as! User
user.address = address
})
Hope this will help you!
As the error says, you're not allowed to have a relationship in one Core Data object whose value is set to another object held in a different context. One way you can get around this is to wait until after you save the new object and merge it back into the primary context, then set the owner relationship as appropriate (since both objects are now in the same context, there's no problem with that).
book *book = [mainContext ........] //Get book from default context
NSManagedObjectID *objectId = [book objectID];
Book *tmpBook = [tmpContext objectWithID:objectId]; //Now book has the legal relationship
Swift version...
context.insert(objectFromOtherContext)