Blog

How to Save Objects With Relations and Create Dynamic Schema

by on September 1, 2019

In this post, we will show how to save an object with relations in Backendless Database. We are using the “code first” approach, which means we will not be creating data tables in the backend. Instead, the code will dictate to the backend what the data schema should look like.

Our example demonstrating this feature consists of two classes: Order and OrderItem. An instance of the Order class may contain a collection of OrderItem objects. The example will create an order, populate it with order items and save the order on the server. As a result, Backendless will create data tables corresponding to the classes used in the examples and you will be able to see the data.

If you do not have an account in Backendless, you can easily create one. You will need to login to your application and get the Application ID and the API Key, both of which are used by Backendless to identify your app. You can obtain these values by following the steps below:

  1. Login to Console, select an app and click the Manage icon.
  2. The Application Settings screen shows the application ID and secret keys for the client-side environments. Make sure to copy and paste the App id and an API Key to the client-side code from this post if you decide to run it.

The Order class is shown below:

    Notice the class does not have to implement any of the Backendless interfaces or extend any special classes, just a plain old Java object (POJO that is):

    import java.util.ArrayList;
    import java.util.List;
    
    public class Order {
       private int orderNumber;
       private String orderName;
       private List orderItems;
    
       public String getOrderName() {
           return orderName;
       }
    
       public void setOrderName(String orderName) {
           this.orderName = orderName;
       }
    
       public int getOrderNumber() {
           return orderNumber;
       }
    
       public void setOrderNumber(int orderNumber) {
           this.orderNumber = orderNumber;
       }
    
       public void addOrderItem(OrderItem orderItem) {
           if (orderItems == null)
               orderItems = new ArrayList();
    
           orderItems.add(orderItem);
       }
    
       public List getOrderItems() {
           return orderItems;
       }
    
       public void setOrderItems(List orderItems) {
           this.orderItems = orderItems;
       }
    }

    import java.util.ArrayList
    
    data class Order(var orderNumber: Int = 0, var orderName: String? = null,
               val orderItems: List = ArrayList())

    @interface Order : NSObject
    
    @property (strong, nonatomic) NSString *objectId;
    @property (strong, nonatomic) NSString *orderName;
    @property (nonatomic) NSInteger orderNumber;
    @property (strong, nonatomic) NSArray *orderItems;
    
    @end

    @objcMembers class Order: NSObject {
        var objectId: String?
        var orderName: String?
        var orderNumber: Int = 0
        var orderItems: [OrderItem]?
    }

    function Order() {
      let orderNumber
      let orderName
      let orderItems = []
    
      this.getOrderName = function () {
        return orderName
      }
    
      this.setOrderName = function (orderName) {
        if (typeof orderName === 'string') {
          this.orderName = orderName
        }
      }
    
      this.getOrderNumber = function () {
        return orderNumber
      }
    
      this.setOrderNumber = function (orderNumber) {
        if (typeof orderNumber === 'number') {
          this.orderNumber = orderNumber
        }
      }
    
      this.addOrderItem = function (orderItem) {
        if (orderItem instanceof OrderItem) {
          orderItems.push(orderItem)
          this.setOrderItems(orderItems)
        }
      }
    
      this.getOrderItems = function () {
        return orderItems
      }
    
      this.setOrderItems = function (orderItems) {
        if (Array.isArray(orderItems)) {
          this.orderItems = orderItems
        }
      }
    }
    


    The SDK for Flutter uses the map-based approach and does not require any custom classes.

    The OrderItem class referenced in Order looks like this:

      public class OrderItem {
         private String name;
         private double price;
         private int quantity;
      
         public String getName() {
             return name;
         }
      
         public void setName(String name) {
             this.name = name;
         }
      
         public double getPrice() {
             return price;
         }
      
         public void setPrice(double price) {
             this.price = price;
         }
      
         public int getQuantity() {
             return quantity;
         }
      
         public void setQuantity(int quantity) {
             this.quantity = quantity;
         }
      }

      class OrderItem(var name: String? = null, var price: Double = 0.0, var quantity: Int = 0)

      @interface OrderItem : NSObject
      
      @property (strong, nonatomic) NSString *objectId;
      @property (strong, nonatomic) NSString *name;
      @property (nonatomic) double price;
      @property (nonatomic) NSInteger quantity;
      
      @end

      @objcMembers class OrderItem: NSObject {
          var objectId: String?
          var name: String?
          var price: Double = 0.0
          var quantity: Int = 0
      }

      function OrderItem() {
        let name
        let price
        let quantity
      
        this.getName = function () {
          return name
        }
      
        this.setName = function (name) {
          if (typeof name === 'string') {
            this.name = name
          }
        }
      
        this.getPrice = function () {
          return price
        }
      
        this.setPrice = function (price) {
          if (typeof price === 'number') {
            this.price = price
          }
        }
      
        this.getQuantity = function () {
          return quantity
        }
      
        this.setQuantity = function (quantity) {
          if (typeof quantity === 'number') {
            this.quantity = quantity
          }
        }
      }
      


      The SDK for Flutter uses the map-based approach and does not require any custom classes.

      Finally, the main block of code which creates a few instances of OrderItem  and puts them into an Order  object:

        private static void saveOrder() {
           Order order = new Order();
           order.setOrderName("Office Supplies");
           order.setOrderNumber(1);
        
           Backendless.Data.of(Order.class).save(order, new AsyncCallback() {
               @Override
               public void handleResponse(Order savedOrder) {
                   Log.i(TAG, "Order has been saved");
                   savedOrderItems(savedOrder);
               }
        
               @Override
               public void handleFault(BackendlessFault fault) {
                   Log.e(TAG, fault.getMessage());
               }
           });
        }
        
        private static void savedOrderItems(final Order savedOrder) {
           OrderItem orderItem1 = new OrderItem();
           orderItem1.setName("Printer");
           orderItem1.setQuantity(1);
           orderItem1.setPrice(99);
        
           final OrderItem orderItem2 = new OrderItem();
           orderItem2.setName("Paper");
           orderItem2.setQuantity(10);
           orderItem2.setPrice(19);
        
           final List savedOrderItems = new ArrayList<>();
        
           Backendless.Data.of(OrderItem.class).save(orderItem1, new AsyncCallback() {
               @Override
               public void handleResponse(OrderItem savedOrderItem1) {
                   Log.i(TAG, "First order item has been saved");
                   savedOrderItems.add(savedOrderItem1);
        
                   Backendless.Data.of(OrderItem.class).save(orderItem2, new AsyncCallback() {
                       @Override
                       public void handleResponse(OrderItem savedOrderItem2) {
                           Log.i(TAG, "Second order item has been saved");
                           savedOrderItems.add(savedOrderItem2);
        
                           setRelation(savedOrder, savedOrderItems);
                       }
        
                       @Override
                       public void handleFault(BackendlessFault fault) {
                           Log.e(TAG, fault.getMessage());
                       }
                   });
               }
        
               @Override
               public void handleFault(BackendlessFault fault) {
                   Log.e(TAG, fault.getMessage());
               }
           });
        }
        
        private static void setRelation(Order savedOrder, List savedOrderItems) {
           Backendless.Data.of(Order.class).addRelation(savedOrder,
               "orderItems:OrderItem:n", savedOrderItems,
               new AsyncCallback() {
                   @Override
                   public void handleResponse(Integer response) {
                       Log.i(TAG, "Relation has been set");
                   }
        
                   @Override
                   public void handleFault(BackendlessFault fault) {
                       Log.e(TAG, fault.getMessage());
                   }
               });
        }

        private fun saveOrder() {
           val order = Order(1, "Office Supplies")
        
           Backendless.Data.of(Order::class.java).save(order, object : AsyncCallback {
               override fun handleResponse(savedOrder: Order) {
                   Log.i(TAG, "Order has been saved")
                   savedOrderItems(savedOrder)
               }
        
               override fun handleFault(fault: BackendlessFault) {
                   Log.e(TAG, fault.message)
               }
           })
        }
        
        private fun savedOrderItems(savedOrder: Order) {
           val orderItem1 = OrderItem("Printer", 99.0, 1)
           val orderItem2 = OrderItem("Paper", 19.0, 10)
        
           val savedOrderItems = ArrayList()
        
           Backendless.Data.of(OrderItem::class.java).save(orderItem1, object : AsyncCallback {
               override fun handleResponse(savedOrderItem1: OrderItem) {
                   Log.i(TAG, "First order item has been saved")
                   savedOrderItems.add(savedOrderItem1)
        
                   Backendless.Data.of(OrderItem::class.java).save(orderItem2, object : AsyncCallback {
                       override fun handleResponse(savedOrderItem2: OrderItem) {
                           Log.i(TAG, "Second order item has been saved")
                           savedOrderItems.add(savedOrderItem2)
        
                           setRelation(savedOrder, savedOrderItems)
                       }
        
                       override fun handleFault(fault: BackendlessFault) {
                           Log.e(TAG, fault.message)
                       }
                   })
               }
        
               override fun handleFault(fault: BackendlessFault) {
                   Log.e(TAG, fault.message)
               }
           })
        }
        
        private fun setRelation(savedOrder: Order, savedOrderItems: List) {
           Backendless.Data.of(Order::class.java).addRelation(savedOrder,
               "orderItems:OrderItem:n", savedOrderItems,
               object : AsyncCallback {
                   override fun handleResponse(response: Int?) {
                       Log.i(TAG, "Relation has been set")
                   }
        
                   override fun handleFault(fault: BackendlessFault) {
                       Log.e(TAG, fault.message)
                   }
               })
        }

        Order *order = [Order new];
        order.orderName = @"Office Supplies";
        order.orderNumber = 1;
            
        OrderItem *orderItem1 = [OrderItem new];
        orderItem1.name = @"Printer";
        orderItem1.price = 99;
        orderItem1.quantity = 1;
            
        OrderItem *orderItem2 = [OrderItem new];
        orderItem2.name = @"Paper";
        orderItem2.price = 19;
        orderItem2.quantity = 10;
            
        DataStoreFactory *orderStore = [Backendless.shared.data of:[Order class]];
        DataStoreFactory *orderItemStore = [Backendless.shared.data of:[OrderItem class]];
            
            
        // saving order
        [orderStore saveWithEntity:order responseHandler:^(Order *savedOrder) {
                
            // saving orderItem1
            [orderItemStore saveWithEntity:orderItem1 responseHandler:^(OrderItem *savedItem1) {
                    
                // saving orderItem2
                [orderItemStore saveWithEntity:orderItem2 responseHandler:^(OrderItem *savedItem2) {
                        
                    // setting relations
                    [orderStore setRelationWithColumnName:@"orderItems:OrderItem:n" parentObjectId:savedOrder.objectId childrenObjectIds:@[savedItem1.objectId, savedItem2.objectId] responseHandler:^(NSInteger relationsSet) {
                    } errorHandler:^(Fault *fault) {
                        NSLog(@"Error: %@", fault.message);
                    }];
                        
                } errorHandler:^(Fault *fault) {
                    NSLog(@"Error: %@", fault.message);
                }];
                    
            } errorHandler:^(Fault *fault) {
                NSLog(@"Error: %@", fault.message);
            }];
                
        } errorHandler:^(Fault *fault) {
            NSLog(@"Error: %@", fault.message);
        }];

        let order = Order()
        order.orderName = "Office Supplies"
        order.orderNumber = 1
                
        let orderItem1 = OrderItem()
        orderItem1.name = "Printer"
        orderItem1.price = 99
        orderItem1.quantity = 1
                
        let orderItem2 = OrderItem()
        orderItem2.name = "Paper"
        orderItem2.price = 19
        orderItem2.quantity = 10
                
        let orderStore = Backendless.shared.data.of(Order.self)
        let orderItemsStore = Backendless.shared.data.of(OrderItem.self)
                
        // saving order
        orderStore.save(entity: order, responseHandler: { savedOrder in
            if let savedOrder = savedOrder as? Order,
                let orderId = savedOrder.objectId {
                       
                // saving orderItem1
                orderItemsStore.save(entity: orderItem1, responseHandler: { savedItem1 in
                    if let savedItem1 = savedItem1 as? OrderItem,
                        let itemId1 = savedItem1.objectId {
                              
                        // saving orderItem2
                        orderItemsStore.save(entity: orderItem1, responseHandler: { savedItem2 in
                            if let savedItem2 = savedItem2 as? OrderItem,
                                let itemId2 = savedItem2.objectId {
                                        
                                // setting relations
                                orderStore.setRelation(columnName: "orderItems:OrderItem:n", parentObjectId: orderId, childrenObjectIds: [itemId1, itemId2], responseHandler: { relationsSet in                                    
                                }, errorHandler: { fault in
                                    print("Error: \(fault.message ?? "")")
                                })
                            }
                        }, errorHandler: { fault in
                            print("Error: \(fault.message ?? "")")
                        })
                    }
                        
                }, 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 setRelation = ([order, orderItemsIds]) => {
          return Backendless.Data.of(Order).setRelation(order, 'orderItems:OrderItem:n', orderItemsIds)
        }
        
        const onSuccess = () => {
          console.log('Order has been saved')
        }
        
        const onError = error => {
          console.error('Server reported an error: ', error.message)
          console.error('error code: ', error.code)
          console.error('http status: ', error.status)
        }
        
        const order = new Order()
        
        const orderItem1 = new OrderItem()
        orderItem1.setName('Printer')
        orderItem1.setQuantity(1)
        orderItem1.setPrice(99)
        
        const orderItem2 = new OrderItem()
        orderItem2.setName('Paper')
        orderItem2.setQuantity(10)
        orderItem2.setPrice(19)
        
        order.addOrderItem(orderItem1)
        order.addOrderItem(orderItem2)
        order.setOrderName('Office Supplies')
        order.setOrderNumber(1)
        
        Promise.all([
          Backendless.Data.of(Order).save(order),
          Backendless.Data.of(OrderItem).bulkCreate(order.getOrderItems())
        ])
          .then(setRelation)
          .then(onSuccess)
          .catch(onError)
        

        void _saveOrder() {
           Map order = {
             "orderNumber": 1,
             "orderName": "Office Supplies",
           };
           Map orderItem1 = {
             "name": "Printer",
             "price": 99.0,
             "quantity": 1,
           };
           Map orderItem2 = {
             "name": "Paper",
             "price": 19.0,
             "quantity": 10,
           };
        
           Backendless.Data.of("Order").save(order).then((savedOrder) {
             print("Order has been saved");
        
             Backendless.Data.of("OrderItem").save(orderItem1).then((savedOrderItem1) {
               print("First order item has been saved");
              
               Backendless.Data.of("OrderItem").save(orderItem2).then((savedOrderItem2) {
                 print("Second order item has been saved");
        
                 Backendless.Data.of("Order").addRelation(savedOrder,
                   "orderItems:OrderItem:n", children: [savedOrderItem1, savedOrderItem2]).then(
                     (response) => print("Relation has been set"));
               });
             });
           });
         }

        Once you run the code and see the output from the program, go to your app in Backendless Console. Click the Data icon and you should see the tables created by Backendless with the objects saved in them:

        saved-order

        related-orderitems

        Enjoy!

        Leave a Reply