I have the following core data relationship set up in my model.
Category -> Product -> CartProduct <<- Cart (See picture below).
But I have a hard time figuring out how to establish these relationships (in code). I have made 2 Objective-C Categories, with the names: CartProduct+Product & Cart+CartProduct.
CartProduct+Product contains the following code - this method is called, when the user pushes the "add to cart" button.
+ (CartProduct *)addProductToCartProducts:(Product *)theProduct inManagedObjectContext:(NSManagedObjectContext *)context {
CartProduct *cartProduct = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"CartProduct"];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"products" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSError *error = nil;
NSArray *cartProducts = [context executeFetchRequest:request error:&error];
if (!cartProducts || ([cartProducts count] > 1)) {
// handle error
} else if (![cartProducts count]) {
cartProduct = [NSEntityDescription insertNewObjectForEntityForName:#"CartProduct"
inManagedObjectContext:context];
/*This method is called from an background context, to prevent context conflicting, get nsmangedobject by its id, which is threadSafe. */
NSManagedObjectID *retID = [theProduct objectID];
//Setup One-One relationship from Product to CartProduct
cartProduct.product = (Product *) [context objectWithID:retID];
/*Call method from class Cart+CartProduct to establish to-many relationship from Cart to CartProduct.*/
[Cart addCartProductToCart:cartProduct inManagedObjectContext:context];
} else {
cartProduct = [cartProducts lastObject];
}
return cartProduct;
}
Cart+CartProduct contains the following code:
+ (Cart *)addCartProductToCart:(CartProduct *)theCartProduct inManagedObjectContext:(NSManagedObjectContext *)context {
Cart *cart = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"CartProduct"];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"productsInCart" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSError *error = nil;
NSArray *cartProducts = [context executeFetchRequest:request error:&error];
if (!cartProducts || ([cartProducts count] > 1)) {
// handle error
} else if (![cartProducts count]) {
cart = [NSEntityDescription insertNewObjectForEntityForName:#"Cart"
inManagedObjectContext:context];
[cart addProductsInCartObject:theCartProduct];
} else {
cart = [cartProducts lastObject];
}
return cart;
}
Now I want to view, the objects the user has added to his Cart, I therefore fetch from the Cart entity. But I can't figure out if the I have "connected" the relationships correct? and how to fetch the products in the cart, so I can show the products. (which is in a one-many relationship with CartProduct).
So my question is:
Is the relationship established correct?
How do I manage to fetch the products which is added to the cart?
NB: Earlier this year, I made the following post: Add to cart functionality - Core data and this question is based on that.
Your relationships seem mostly correct, though I think you do not even need the CartProduct entity. Core Data can handle many-to-many relationships behind the scenes. You can just have Cart have a relationship "products" and Product have the inverse relationship "inCarts". A Cart can have many Products and a Product can be in many Carts.
But anyway, you can keep it as is. The only problem is that Product's relationship "cartProduct" should be to-many (and therefore should be named "cartProducts". That is, if you want it to be possible that a product is in more than one cart. Also, be sure you have all your inverse relationships correctly defined.
Your code looks all wrong to me. It's way too complicated for achieving what you're trying to do. Core Data makes it simple. It looks like you are trying to create a new Cart and add the Product to it. You shouldn't need to do any fetching to do this. It seems like maybe you are thinking like a database. With Core Data, you do not think about modifying tables, you think about setting pointers between objects. I would do it this way:
+ (Cart *)startNewCartWithProduct:(Product *)product inManagedObjectContext:(NSManagedObjectContext *)context {
Cart *cart = [NSEntityDescription insertNewObjectForEntityForName:#"Cart" inManagedObjectContext:context];
CartProduct *cartProduct = [NSEntityDescription insertNewObjectForEntityForName:#"CartProduct" inManagedObjectContext:context];
// with correct inverse relationships, automatically adds cartProduct to cart:
cartProduct.cart = cart;
cartProduct.product = product;
return cart;
}
Now for your question #2. To get the products in a cart, use code like this:
+ (NSSet *)productsInCart:(Cart *)cart {
NSMutableSet *result = [NSMutableSet setWithCapacity:[cart.productsInCart size]];
for (CartProduct *cartProduct in cart.productsInCart)
[result addObject:cartProduct.product];
return result;
}
Ha det bra!
Related
I have made one sample demo on core data relationships.I have one Table "User" which is connected another table "Account" In form of "One to Many" relation.
Code
-(IBAction)savePersonData:(id)sender
{
NSManagedObjectContext *context = appDelegate.managedObjectContext;
NSManagedObject *newDevice = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:context];
[newDevice setValue:self.personName.text forKey:#"name"];
[newDevice setValue:self.personAddress.text forKey:#"address"];
[newDevice setValue:self.personMobileNo.text forKey:#"mobile_no"];
NSManagedObject *newAccount = [NSEntityDescription insertNewObjectForEntityForName:#"Account" inManagedObjectContext:context];
[newAccount setValue:self.accountNo.text forKey:#"acc_no"];
[newAccount setValue:self.accountType.text forKey:#"acc_type"];
[newAccount setValue:self.balance.text forKey:#"balance"];
NSLog(#"Saved Successfully");
[self dismissViewControllerAnimated:YES completion:nil];
}
Image is
My Question is
I have find so many time but could not find proper answer.So I post this question second time.
My question is I have insert Manually Three person With their Account's Details.
Now ,I Want A balance which I have entered for specific person when I enter Mobile number.
Ex
1)Enter 1st Mobile Num. Should be display first Person's Balance.
2)Enter 2nd Mobile Num. Should be display second Person's Balance.
1)Enter 3rd Mobile Num. Should be display third Person's Balance.
Balance Check Code
-(IBAction)checkBalance:(id)sender
{ NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Person"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"mobile_no = %#",self.textField.text];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray *result = [appDelegate.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if(!([result count] == 0))
{
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
NSFetchRequest *newFetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Account"];
NSMutableArray *temp = [[managedObjectContext executeFetchRequest:newFetchRequest error:nil] mutableCopy];
for (NSManagedObject *object in temp)
{
NSString *intValue = [object valueForKey:#"balance"];
NSString *alertString = [NSString stringWithFormat:#"%#",intValue];
[self displayAlertView:#"Available Balance" withMessage:alertString];
}
}
else
{
[self displayAlertView:#"Error" withMessage:#"Please Enter Valid Mobile Number That you have entered in Bank"];
}
}
Images
Output
I want Balance With Specific Person Which I enter Person Mobile No on TextField.
Sorry Guys,I have asked second time ,But Could no able to solve this core data relationships.
Thank you.
First...
Fix the naming of your relationships. As others have pointed out in comments on your other question, you have named them back to front: in the Person entity, the to-many relationship to Account should be named "accounts" not "person". Likewise in the Account entity, the to-one relationship to Person should be named "person" not "accounts".
Next...
Although you have defined the "accounts" relationship as to-many, the savePersonData code in this question creates only one Account and one Person - but does not then set the relationship between them. (You can see this in your Output: each Account has nil for its "accounts" relationship).
The code in your previous question did set the relationship (by adding the newAccount to the relationship on the newPerson object). In your code above you could use (after fixing the relationship names):
NSMutableSet *setContainer = [newDevice mutableSetValueForKey:#"accounts"];
[setContainer addObject:newAccount];
but with one-many relationships it is easier to set the inverse relationship:
[newAccount setValue:newDevice forKey:#"person"];
Next...
Your checkBalance method correctly fetches any Person objects whose "mobile_no" attribute matches. But your subsequent code then fetches ALL Account objects - even if you had correctly set the relationship.
If you want only those Account objects that are related to a given Person, that is precisely what the "accounts" relationship represents. So you could just use that relationship to access the related Account objects:
if(!([result count] == 0)) {
NSManagedObject *requiredPerson = (NSManagedObject *)result[0];
NSSet *temp = [requiredPerson valueForKey:#"accounts"];
for (NSManagedObject *object in temp) {
NSString *intValue = [object valueForKey:#"balance"];
NSString *alertString = [NSString stringWithFormat:#"%#",intValue];
[self displayAlertView:#"Available Balance" withMessage:alertString];
}
}
Alternatively, fetch the relevant Account objects directly (without fetching the Person object first) by specifying a predicate that traverses the relationship:
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
NSFetchRequest *newFetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Account"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"person.mobile_no = %#",self.textField.text];
[newFetchRequest setPredicate:predicate];
NSMutableArray *temp = [[managedObjectContext executeFetchRequest:newFetchRequest error:nil] mutableCopy];
for (NSManagedObject *object in temp)
{
NSString *intValue = [object valueForKey:#"balance"];
NSString *alertString = [NSString stringWithFormat:#"%#",intValue];
[self displayAlertView:#"Available Balance" withMessage:alertString];
}
I have a Core Data based application with a table view controller; the user presses the plus button in the navigation bar and they're taken modally to a view controller where they can enter information into text fields. Pressing the save button saves this information Core Data and displays it in the Table view.
I've configured the app so that when I click on a cell, I'm taken to a view that has the information passed across successfully.
What I want to achieve is renaming a text field (for example, a name) and pressing Save updates THAT attribute in Core Data, rather than creating a brand new one with the same name and keeping the old one.
My save method in the editing view controller is:
- (IBAction)save:(id)sender
{
NSManagedObjectContext *context = [self managedObjectContext];
Transaction *transaction = [NSEntityDescription insertNewObjectForEntityForName:#"Transaction" inManagedObjectContext:context];
Item *itemType = [NSEntityDescription insertNewObjectForEntityForName:#"Item" inManagedObjectContext:context];
Person *enteredPerson = (Person *)[Person personWithName:self.editingNameTextField.text inManagedObjectContext:context];
transaction.whoBy = enteredPerson;
NSError *error = nil;
if (![context save:&error])
{
NSLog(#"Can't save! %# %#", error, [error localizedDescription]);
}
}
Which calls:
+ (Person *)personWithName:(NSString *)name inManagedObjectContext:(NSManagedObjectContext *)context
{
Person *person = nil;
// Creating a fetch request to check whether the name of the person already exists
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Person"];
request.predicate = [NSPredicate predicateWithFormat:#"name = %#", name];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSError *error = nil;
NSArray *people = [context executeFetchRequest:request error:&error];
if (!people)
{
// Handle Error
}
else if (![people count])
{
// If the person count is 0 then let's create it
person = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:context];
person.name = name;
}
else
{
// If the object exists, just return the last object .
person = [people lastObject];
}
return person;
/*
for (Person *info in people)
{
NSLog(#"Names are: %#", person.name);
}
*/
}
So when I click on a cell for Jack, it shows me all of Jack's information. I want to rename Jack to Jackie and have that update Core Data for ALL "Jackie's transactions" but with overwriting the Jack object, rather than keeping that and creating another called Jackie.
Any help on this would be appreciated!
To rename a person, you just have to fetch the object (using the old name) and then
update the name attribute. For example:
Person *person = [Person personWithName:oldName inManagedObjectContext:context];
person.name = newName;
All relationships to other objects (like transactions etc) are not modified by this.
I have the following relationship in my app:
Product ->> ProductOrder <<- Order
I then have two Obj-c categories in order to etablish these relationship:
ProductOrder+Product
+ (ProductOrder *)addProductToOrderWithProduct:(Product *)product inManagedObjectContext:(NSManagedObjectContext *)context {
ProductOrder *orderProduct = nil;
orderProduct = [NSEntityDescription insertNewObjectForEntityForName:#"ProductOrder" inManagedObjectContext:context];
NSManagedObjectID *productID = [product objectID];
orderProduct.qnty = product.qnty;
orderProduct.price = product.price;
[(Product *)[context objectWithID:productID] addOrderedProductsObject:orderProduct];
return orderProduct;
}
And then Order+ProductOrder
+ (Order *)addOrderedProductToOrderWithOrderedProduct:(ProductOrder *)orderedProduct inManagedObjectContext:(NSManagedObjectContext *)context {
Order *order = nil;
order = [NSEntityDescription insertNewObjectForEntityForName:#"Order" inManagedObjectContext:context];
NSManagedObjectID *orderedProductID = [orderedProduct objectID];
[order addOrderProductsObject:(ProductOrder *)[context objectWithID:orderedProductID]];
return order;
}
I add products to these relationship like so:
for (Product *prod in [self.fetchedResultsController fetchedObjects]) {
[[[DataManager sharedInstance] backgroundManagedObjectContext] performBlock:^{
ProductOrder *prodOrder = [ProductOrder addProductToOrderWithProduct:prod inManagedObjectContext:[[DataManager sharedInstance] backgroundManagedObjectContext]];
Order *order = [Order addOrderedProductToOrderWithOrderedProduct:prodOrder inManagedObjectContext:[[DataManager sharedInstance] backgroundManagedObjectContext]];
NSInteger amount = [order.orderNumber integerValue];
amount++;
order.orderNumber = [NSString stringWithFormat:#"Order %#", [NSNumber numberWithInteger:amount]];
[[DataManager sharedInstance] saveBackgroundContext];
[[DataManager sharedInstance] saveMasterContext];
}];
}
I now want to group these products in headers. I have this orderNumber attribute in my Order entity, I want to increment for every group of products added to the relationship. How would I manage to do this?
Visual example:
Your code has a number of problems.
First, is there a particular reason you need different managed object context? If not, eliminate that part. Remember, you can access each managed object's context with
product.managedObjectContext;
Second, you are using ObjectIDs which is really only necessary if you pass objects across contexts. In your method, you extract the ID from product and then call objectWithID to get it back. That does not make any sense at all.
Third, you need a very good reason to have this ProductOrder entity at all. Even if you have not told us, let's assume it is necessary because you want to include, say, different quantities for the products in an order, as well as an ordering number. However, the name you chose is very confusing. Let's call it Item.
Your scheme should now look like this:
Product <---->> Item <<------> Order
You could simply use the Core Data generated methods to add relationships and throw away your categories.
For your table you could fetch the Order entity and inform the datasource as follows:
// number of sections
fetchedObjects.count;
// title for section
Order *order = fetchedObjects[section];
order.name;
// number of rows in section
Order *order = fetchedObjects[section];
order.items.count;
// row data
Order *order = fetchedObjects[section];
NSArray *items = [order.items sortedArrayUsingSortDescriptors:
#[[NSSortDescriptor sortDescriptorWithKey:#"sequenceNumber" ascending:YES]]];
Item *item = items[indexPath.row];
item.product.name;
I have two entities. (Deal, Customer)
Deal and Customer have 1:1 relationship. so Deal has customer, and Customer has deal.
first, I made Customer object named "John".
second, I made Deal object and set customer with "John" (#1 deal)
third, I made another Deal object and set customer with "John" (#2 deal)
at that time, I found some problem.
that is #1 deal's customer set nil automatically, and #2 deal's customer is "John".
how can I solve that?
ps1. I got the data from web server as JSON like this
deals = [id: .., ..., customer: { ... }]
ps2. I update objects whenever receive data from server.
+ (Deal *)dealWithDealsDictionary:(NSDictionary *)dic inManagedObjectContext:(NSManagedObjectContext *)context
{
Deal *deal = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Deal"];
request.predicate = [NSPredicate predicateWithFormat:#"deal_id = %#", [dic[#"id"] description]];
// Execute the fetch
NSError *error = nil;
NSArray *matches = [context executeFetchRequest:request error:&error];
// Check what happened in the fetch
if (!matches || ([matches count] > 1)) { // nil means fetch failed; more than one impossible (unique!)
deal = [matches lastObject];
// handle error
} else if (![matches count]) {
deal = [NSEntityDescription insertNewObjectForEntityForName:#"Deal" inManagedObjectContext:context];
} else {
deal = [matches lastObject];
}
deal.deal_id = [dic[#"id"] description];
deal.deal_status = [dic[#"deal_status"] description];
deal.deal_stage = [dic[#"deal_stage"] description];
deal.deal_desc = [dic[#"deal_desc"] description];
deal.localized_deal_status = [dic[#"localized_deal_status"] description];
deal.localized_deal_stage = [dic[#"localized_deal_stage"] description];
if (dic[#"customer"]) {
[context performBlock:^{
deal.customer = [Customer customerWithDictionary:dic[#"customer"] inManagedObjectContext:context];
}];
}
return deal;
}
you don't have a 1:1 relationship:it is 1:N
2 deals have the same customer, so 1 customer has N deals.
CoreData wanted to keep the 1:1 constraints where 1 deal has always 1 unique customer and vice versa.
Change to one-to-many
Make the relationship 1 to many or many to many if you want a customer to have many deals and / or many customers each to have many deals (where customers can each have the same deal).
The reference was set to nil because you said there could only be 1 reference at a time.
I am developing an application where i used core data framework for the purpose of maintaining a database. My entity contains three attributes called: name, start time and end time of a list of applications. I am getting the correct values for name and start time attribute.
Now my problem is my end time attribute should contain the value of the next entries start time value. If anybody having any idea about this please let me know.
Thanks
You can leave the endTime attribute blank until you create the next entity. In the +Create category on the entity, get the last/first object (assuming you are using ordered entities) and update the endTime with the same value used for the new startTime.
If your objects are not ordered it could be a bit tricky since all the entities are in a set. But if ordered, you are good since NSOrderedSet responds to lastObject (and firstObject).
Enjoy,
Damien
EDIT: Here is an example factory method that either 1) returns the existing stock entity for a stock symbol or 2) creates a new entity for that symbol. Pretty easily modified to get entities and select the first/last depending on your sort order. Again see the Core Data classes from Prof. Hegarty.
+ (Stock *)stockForSymbol:(NSString *)symbol inManagedObjectContext:(NSManagedObjectContext *)context {
Stock *stock = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Stock"];
request.predicate = [NSPredicate predicateWithFormat:#"symbol = %#",symbol];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"symbol" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSError *error = nil;
NSArray *matches = [context executeFetchRequest:request error:&error];
if (!matches || [matches count] > 1) {
// handle error
} else if ([matches count] == 0) {
stock = [NSEntityDescription insertNewObjectForEntityForName:#"Stock" inManagedObjectContext:context];
stock.symbol = symbol;
stock.strategyPosition = [NSNumber numberWithInt:StrategyPositionFlat];
stock.userPosition = stock.strategyPosition;
stock.userOwns = [NSNumber numberWithBool:NO];
} else {
stock = [matches lastObject];
}
return stock;
}