Update a one-to-many relationship

Hello,
First of all I would like to say thanks for doing such a great job and product to be used :slight_smile:
I am having problems updating a one-to-many relationship when I want to add objects to it. My setup has only one Consulting table and the default Users table (BackendlessUser). So one user can have multiple Consulting instances. The Consulting instance also keeps a relation one-to-one (as we canā€™t have the many-to-one?) to the User, so I can fetch the user data automatically when fetching Consultingā€™s.
This is the code I am trying to execute:

Consultation consultation = new Consultation();
consultation.setTitle(title);
consultation.setBody(body);
consultation.setUser(Backendless.UserService.CurrentUser());
consultation.setCreated(new Date());



Consultation[] newConsultations = null;
BackendlessUser user = Backendless.UserService.CurrentUser();



Object consultationsObj = user.getProperty("consultations");
if ((consultationsObj != null) && (consultationsObj instanceof Consultation[]))
{
 Consultation[] consultations = (Consultation[])consultationsObj;
  if (consultations.length > 0)
 {
 newConsultations = new Consultation[consultations.length+1];
  for (int i=0; i<consultations.length; i++)
 {
 newConsultations [ i ] = consultations [ i ];
 }
 newConsultations[consultations.length] = consultation;
 }
}



if (newConsultations == null)
{
 newConsultations = new Consultation[1];
 newConsultations[0] = consultation;
}



user.setProperty("consultations", newConsultations);



Backendless.UserService.update(user, new AsyncCallback&lt;BackendlessUser&gt;() {
 @Override
  public void handleResponse(BackendlessUser response) {
 Log.v("Persist", "Success");
 finish();
 }



 @Override
  public void handleFault(BackendlessFault fault) {
 Log.v("Persist", "Fault: " + fault.toString());
 }
});

The first think to consider is that it looks redundant that I need to reassign the old members of the relation in order to not to loose them. I miss a method addToProperty() to be used instead of the setProperty(). But so far so good, I add the old members to the collection.
The problem is that when doing this, the old instances of the Consulting class get duplicated. This is my backend state when I add one Consulting to the user:
http://support.backendless.com/public/attachments/2cd4adad88e971079559bb4c38147083.png&lt;/img&gt;
And this is the state when I add a second Consulting to it. As you can see, the first Consulting has been duplicated:
http://support.backendless.com/public/attachments/e7fbff7347b98dce93aba5df50e43903.png&lt;/img&gt;
Thanks for your help!
ā€“ Ricard Perez

Hi Ricard,

This looks overly complicatedā€¦ try this instead (skipping async handling code for brevity):

Consultation consultation = new Consultation();
consultation.setTitle(title);
consultation.setBody(body);
consultation.setUser(Backendless.UserService.CurrentUser());
consultation.setCreated(new Date());
Backendless.Data.of( Consultation.class ).save( consultation, asyncHandlerGoesHere );

As a result of that code, you will have your Consultation object stored with a link to the appropriate user.

It also appears you have a bidirectional linking between Users and Consultations:

    Consultation has a one-to-one relation to a User User has a one-to-many relation to Consultation
I think this is rather excessive - one of them is definitely redundant. The code I showed above will work for (1). However, if you prefer to have only (2), then the code above can be changed like this (without all the "mambo-jambo" handling of the Consultation objects):
Consultation consultation = new Consultation();
consultation.setTitle(title);
consultation.setBody(body);
consultation.setCreated(new Date());


ArrayList&lt;Consultation&gt; consultationsList = new ArrayList&lt;Consultation&gt;();
Object consultationsObj = user.getProperty("consultations");


if ((consultationsObj != null) && (consultationsObj instanceof Consultation[]))
{
  Consultation[] consultations = (Consultation[])consultationsObj;
  consultationsList.addAll( consultations );
}


consultationsList.add( consultation );
user.setProperty( "consultations", consultationsList );
Backendless.Data.of( BackendlessUser.class ).save( user, userSaveHandler );

Hope this helps.

Mark

Hello Mark,

Thanks for the quick answer.

Ok, going with the option 1, I manage to avoid the duplicated Consulting entries, but now the User class only keeps track of the last added Consulting object.
All the Consulting objects will have the correct User reference, but the User objects will only point to the last added Consulting.

You said that

    Consultation has a one-to-one relation to a User User has a one-to-many relation to Consultation

But only point 1 is true. I can go with this for now, but I still miss the many-to-one relationship. I guess that if I need to access all the Consulting for a particular User I can create a specific Consulting query.

About the option 2, I canā€™t see any difference. I tried it and I still get duplicated Consulting entries.

Thank you!

Hi Ricard,

I think youā€™re somewhat confused by how it works and I apologize I am not making it clearer (although I am trying hard). Letā€™s take a look at the following relationship:

Consultation has a one-to-one relation to a User

The relationship is FROM Consultation TO a Userā€¦ any given Consultation has a User. Which means the same User may be referenced by different Consultations. Which also means that if you declare it as an explicit one-to-one relationship from Consultation to User, it will automatically become an implicit one-to-many relationship from User to Consultation (the same User may be referenced by multiple Consultations).

The ā€œmany to oneā€ relationship you mention is created as a side effect of establishing of one-to-one between Consultation and User. If you have that relationship, it means you will be able to do the following data retrievals:

    For any given Consultation, retrieve the User it references For any given User, retrieve all Consultations which reference that User
Does it help? Or you're even more confused now?

I am also trying to wrap my head around that stuff. I understand that when you have a one-to-one relation you form an implicit one-to-many. This implicit relation is shown in the console but is there a chance to autoload them. Consider a simple Class like a Category. A Category has a name and and another property named ā€œparentā€ which points to the parent Category.

Having a one-to-many relation (i.e. a childCategories property) will allow you to choose autoload which works fine. You can receive the whole tree with a simple GET request. But, when adding a new Category in the app, you will have to send the whole tree back to Backendless to get that updated.

Having a one-to-one relation from the child to the parent (i.e. a parentCategory property) will enable easy adding and deletion of objects in the tree with a single request. But you cannot get the tree with one GET request as there is no autoload feature. But I donā€™t want to create multiple requests just to load the categories (to show them in a Tree Control in the App).

What to do? Is there a way to autoload the implicit one-to-many relation?

Having a one-to-many relation (i.e. a childCategories property) will allow you to choose autoload which works fine. You can receive the whole tree with a simple GET request. But, when adding a new Category in the app, you will have to send the whole tree back to Backendless to get that updated.

That is not correct. If you need to add a ā€œchildā€ object to the ā€œparentā€™sā€ collection of children, you do not need to have the whole collection present. You can simply persist the ā€œparentā€ object with an array containing only one new ā€œchildā€.

Having a one-to-one relation from the child to the parent (i.e. a parentCategory property) will enable easy adding and deletion of objects in the tree with a single request. But you cannot get the tree with one GET request as there is no autoload feature

What tree are you talking about here? Get a list of subcategories for a category? Thatā€™s not a problem, you can still do it. For example the following query (whereClause) will handle it for you:

parentCategory.objectId = ā€˜category-objectidā€™

Hi Mark,

I have the same problem as the original poster. I have a BackendlessUser with a 1:Many relationship to a Car object and am having trouble doing anything but the initial setProperty. My test case is:

    Create the user with relation to 2 Cars This creates my User and 2 Car objects in the database that are children of the User Add a new car using setProperty This results in an additional Car object added to the Car table but my User only being related to the newsest entry, and the link to the 2 "old" entries is deleted
Because of how the BackendlessUser object works (that you can't really subclass or extend it) does it really make sense to try to have the relation from User to Car? I've been playing around with it all day and it seems like I'd have an easier time if I just create the Car with a reference back to the User but this feels wrong to me as logically speaking it makes more sense for the User to own the car than vice versa.

There doesnā€™t seem to be much documentation on various ways to manipulate custom user properties, Iā€™m looking for the following functionality:

    Add cars to user view cars associated with user edit car properties delete car
If you can suggest a recommended approach or point me to some specific documentation that would be great.

Thanks!
Chris

Hi Chris,

When you add a 3rd car to the user, how do you obtain the BackendlessUser object? Is it retrieved from the backend? OR perhaps you create new instance of BackendlessUser and assign objectId to it so it is properly identified?

Regards,
Mark

Appreciate the fast reply!!

Iā€™m using the following function which results in car3 added to the Car table but removes the link to the 2 existing cars


 @IBAction func addCar() {
 let car3 = Car()
 car3.make = "Audi"
 car3.model = "A4"
 car3.year = "2010"
 car3.color = "Blue"
 car3.licensePN = "TEST01"
 
 let currentUser = self.backendless.userService.currentUser
 
 currentUser.setProperty("Car", object: [car3])
 
 self.backendless.userService.update(currentUser)
 }

I think maybe Iā€™m just not conceptually understanding. What Iā€™d like is some method that lets me just add a Car object to the existing set but looking at your example above it seems like maybe I need to create a new array, add the existing cars to that array, then add my new car to the Array and save that Array as my new Car setProperty? Can I directly add Cars or edit them? Or do I first need to get the data into the client, manipulate it, then send back the full set of new data?

Been trying to figure out a way to do it, and think maybe something like this is a little closer but could use some more guidance (using car3 from above)ā€¦

var cars = currentUser.getProperty("Car")
var car1 = cars as! [Car]
car1.append(car3)
 
currentUser.setProperty("Car", object: car1)
 
self.backendless.userService.update(currentUser) 

What should getProperty return, I was hoping it would return an array of cars but seems to return a single AnyObject? Iā€™m coming from Parse so Iā€™m struggling a bit with some of the concepts in backendlessā€¦ thanks!

Edit: Should maybe add that I have autoload checked

Hi Chris,

The behavior youā€™re observing is consistent with the code you shared.

Suppose you have an object retrieved from the backend with some related objects (obtained via auto-load). When you retrieve a value of a property, you must cast it to the appropriate type (it should be an array in case of one to many relations). When you assign an array to the same property and then save the ā€œparentā€ object (user object in your case), Backendless will make bring the backend storage in line with the state of the array. That means any related object you deleted from the array, will be ā€œunlinkedā€ from the parent. Anything new you added to the array, will be established as a new related object.

Hope this helps.

Regards,
Mark