In another article, we described how to load complex data objects from the persistent storage using the “auto-load” technique. Using that approach, a developer can statically identify specific (child) properties that should be returned along with the parent object(s) when a client app sends a request to load them.
That approach works well for object relations which must be unconditionally returned with the parent object. However, there are many scenarios when the client app must control which relations should be preloaded and returned along with the parent. The API described below allows you to accomplish that.
Consider the following example which uses the client-side code generated by Backendless based on the database schema. In that schema, the Restaurant table has the “locations” column which represents a one-to-many relationship between the Restaurant and Locations table. This means that a restaurant may have several related locations as shown in the screenshots below.
The restaurant objects:
The related location object for the “Cantina Laredo” restaurant:
private static void loadRelationsAsync() { Log.i(TAG, "============ Loading relations with the ASYNC API ============"); final AsyncCallback<List> callback = new AsyncCallback<List>() { @Override public void handleResponse(List restaurants) { Log.i(TAG, "Loaded " + restaurants.size() + " restaurant objects"); for (Restaurant restaurant : restaurants) { Log.i(TAG, "Restaurant name = " + restaurant.getName()); printLocations(restaurant.getLocations()); } } @Override public void handleFault(BackendlessFault fault) { Log.e(TAG, fault.getMessage()); } }; final DataQueryBuilder dataQuery = DataQueryBuilder.create(); dataQuery.setRelated("locations"); Backendless.Data.of(Restaurant.class).getObjectCount(new AsyncCallback() { @Override public void handleResponse(Integer count) { Log.i(TAG, "Total restaurants in the Backendless storage - " + count); Backendless.Data.of(Restaurant.class).find(dataQuery, callback); } @Override public void handleFault(BackendlessFault fault) { Log.e(TAG, fault.getMessage()); } }); }
private fun loadRelationsAsync() { Log.i(TAG, "============ Loading relations with the ASYNC API ============") val callback = object : AsyncCallback<List> { override fun handleResponse(restaurants: List) { Log.i(TAG, "Loaded ${restaurants.size} restaurant objects") for (restaurant in restaurants) { Log.i(TAG, "Restaurant name = ${restaurant.name}") printLocations(restaurant.locations) } } override fun handleFault(fault: BackendlessFault) { Log.e(TAG, fault.message) } } val dataQuery = DataQueryBuilder.create() dataQuery.setRelated("locations") Backendless.Data.of(Restaurant::class.java).getObjectCount(object : AsyncCallback { override fun handleResponse(count: Int?) { Log.i(TAG, "Total restaurants in the Backendless storage - $count") Backendless.Data.of(Restaurant::class.java).find(dataQuery, callback) } override fun handleFault(fault: BackendlessFault) { Log.e(TAG, fault.message) } }) }
NSLog(@"============ Loading relations ============"); DataQueryBuilder *queryBuilder = [DataQueryBuilder new]; [queryBuilder addRelatedWithRelated:@"locations"]; DataStoreFactory *dataStore = [Backendless.shared.data of:[Restaurant class]]; [dataStore findWithQueryBuilder:queryBuilder responseHandler:^(NSArray *restaurants) { NSLog(@"Loaded %lu restaurant objects", (unsigned long)restaurants.count); [dataStore getObjectCountWithResponseHandler:^(NSInteger totalRestaurants) { NSLog(@"Total restaurants in the Backendless storage - %li",(long)totalRestaurants); for (Restaurant *restaurant in restaurants) { NSLog(@"\nRestaurant name = %@", restaurant.name); [self printLocations:restaurant.locations]; } } errorHandler:^(Fault *fault) { NSLog(@"Error: %@", fault.message); }]; } errorHandler:^(Fault *fault) { NSLog(@"Error: %@", fault.message); }];
print("============ Loading relations ============") let queryBuilder = DataQueryBuilder() queryBuilder.addRelated(related: "locations") let dataStore = Backendless.shared.data.of(Restaurant.self) dataStore.find(queryBuilder: queryBuilder, responseHandler: { restaurants in print("Loaded \(restaurants.count) restaurantObjects") dataStore.getObjectCount(responseHandler: { totalRestaurants in print("Total restaurants in the Backendless storage - \(totalRestaurants)") if let restaurants = restaurants as? [Restaurant] { for restaurant in restaurants { print("\nRestaurant name = \(restaurant.name ?? "")") self.printLocations(restaurant.locations) } } }, errorHandler: { fault in print("Error: \(fault.message ?? "")") }) }, errorHandler: { fault in print("Error: \(fault.message ?? "")") })
const Backendless = require('backendless') /* Or use `import Backendless from 'backendless'` for client side. If you don't use npm or yarn to install modules, you can add the following line <script src="//api.backendless.com/sdk/js/latest/backendless.min.js"></script> to your index.html file and use the global Backendless variable. */ Backendless.initApp('YOUR_APP_ID', 'YOUR_JS_API_KEY') const loadRestaurants = () => { const dataQuery = Backendless.DataQueryBuilder.create().setRelated('locations') return Backendless.Data.of('Restaurant').find(dataQuery) } const onSuccess = restaurants => { restaurants.forEach(restaurant => { printLocations(restaurant.locations) }) } const onError = error => { console.error('Server reported an error: ', error.message) console.error('error code: ', error.code) console.error('http status: ', error.status) } Promise.resolve() .then(loadRestaurants) .then(onSuccess) .catch(onError)
static void _loadRelationsAsync() { print("============ Loading relations with the ASYNC API ============"); DataQueryBuilder dataQuery = DataQueryBuilder() ..related = ["locations"]; Backendless.Data.of("Restaurant").getObjectCount().then((count) { print("Total restaurants in the Backendless storage - $count"); Backendless.Data.of("Restaurant").find(dataQuery).then((restaurants) { print("Loaded ${restaurants.length} restaurant objects"); restaurants.forEach((restaurant) { print("Restaurant name = ${restaurant['name']}"); _printLocations(restaurant['locations'].cast<Map>()); }); }); }); }
Notice how the related locations column in set in the DataQueryBuilder class. Specifying the column name requests that the data objects referenced by the column must be initialized and returned with the every parent Restaurant object. The printLocations method displays the loaded Locations objects:
private static void printLocations(List locations) { if (locations == null) { Log.i(TAG, "Restaurant locations have not been loaded"); } else if (locations.size() == 0) { Log.i(TAG, "There are no related locations"); } else { for (Location location : locations) { Log.i(TAG, "Location: Street address - " + location.getStreetAddress() + ", City - " + location.getCity()); } } }
private fun printLocations(locations: List?) { when { locations == null -> Log.i(TAG, "Restaurant locations have not been loaded") locations.isEmpty() -> Log.i(TAG, "There are no related locations") else -> for (location in locations) { Log.i(TAG, "Location: Street address - ${location.streetAddress}, City - ${location.city}") } } }
- (void)printLocations:(NSArray *)locations { if (!locations) { NSLog(@"Restaurant locations have not been loaded"); } else { if (locations.count == 0) { NSLog(@"There are no related locations"); } else { for (Location *location in locations) { NSLog(@"Location: Street address - %@, City - %@", location.street, location.city); } } } }
func printLocations(_ locations: [Location]?) { if locations == nil { print("Restaurant locations have not been loaded") } else if let locations = locations { if locations.count == 0 { print("There are no related locations") } else { for location in locations { if let city = location.city, let street = location.street { print("Location: Street address - \(street), City - \(city)") } } } } }
const printLocations = locations => { if (!locations) { return console.log('Restaurant locations have not been loaded') } if (!locations.length) { return console.log('There are no related locations') } locations.forEach(location => { console.log(`Location: Street address - ${ location.streetAddress }, City - ${ location.city }`) }) }
static void _printLocations(List<Map> locations) { if (locations == null) { print("Restaurant locations have not been loaded"); } else if (locations.isEmpty) { print("There are no related locations"); } else { for (Map location in locations) { print("Location: Street address - ${location['streetAddress']}, City - ${location['city']}"); } } }
The code above produces the following output. Notice that the “Cantina Laredo” restaurant is fetched with the related location:
============ Loading relations with the SYNC API ============ Loaded 4 restaurant objects Total restaurants in the Backendless storage - 4 Restaurant name = McDonald's There are no related locations Restaurant name = Buca Di Bepo There are no related locations Restaurant name = Cantina Laredo Location: Street address - 123 Main St., City - Frisco Restaurant name = Endless Sweets There are no related locations ============ Loading relations with the ASYNC API ============ Loaded 4 restaurant objects Total restaurants in the Backendless storage - 4 Restaurant name = McDonald's There are no related locations Restaurant name = Buca Di Bepo There are no related locations Restaurant name = Cantina Laredo Location: Street address - 123 Main St., City - Frisco Restaurant name = Endless Sweets There are no related locations
See the documentation for more information on loading related objects with the “one-step” approach.
Enjoy!