BlogAll Blog Posts | Next Post | Previous Post
Monday, April 26, 2021
In this last blog article of 4 blog posts, we introduce a last major new feature in the Google Firestore dataset, i.e. new async features. Before providing details about these new features, let's first look at the classic VCL TClientDataSet, how it can work how it works and what the implications are of inherently asynchronous implementations for use with a REST API based communication between a client dataset and the Google Firestore backend.
Classic VCL TClientDataSet operations
The editing and navigation interface for a TClientDataSet is best provided by using data-aware controls such as TDBGrid, TDBNavigator and more. Still, many times you need to process some records in code according to specific business logic. When it comes to populating and modifying a TClientDataSet in classic VCL code, you would use code similar to the following code snippets.
Inserting a record in the dataset
ClientDataSet1.Insert; ClientDataSet1.FieldByName('Description').AsString := 'Call up a meeting with Sales group.'; ClientDataSet1.FieldByName('Status').AsString := 'New'; ClientDataSet1.Post;
Modifying a record in the dataset
ClientDataSet1.Edit; ClientDataSet1.FieldByName('Status').AsString := 'Done'; ClientDataSet1.Post;
Deleting a record in the dataset
Processing all the records in the dataset
ClientDataSet1.First; while not ClientDataSet1.EOF do begin //do some processing based on one or more fields of the record ClientDataSet1.Next; end;
Equivalents with Firestore?
Question is, will the above code work in Firestore ClientDataSet too? Yes the above code will work as standalone. But the code that follows the above code will fail if it depends on the success of the database operation in the previous code. This is important to understand so let's look at 2 examples of how the code that follows can fail in Firestore TClientDataSet.
if not ClientDataSet1.Active then ClientDataSet1.Open; ClientDataSet1.First; while not ClientDataSet1.EOF do begin // do some processing based on one or more fields of the record .. ClientDataSet1.Next; end;
This will fail on TClientDataSet1.First. Why? Because an open is an asynchronous operation that takes time to open the cloud database. There is no guarantee that the open will be complete by the time you reach the next statement. What will happen is unpredictable. In fact, this is true of all the ClientDataSets in TMS Web Core dealing with cloud databases.
Failing code example 2
Consider another example where we want to first insert a record in a Firestore collection, then get the record's id that Firestore generates and stuff it as a foreign key value in another dataset's new record.
// adding a new customer's record Customers.Insert; Customers.FieldByName('FirstName').AsString := 'John'; ... more data for the customer record as needed ... Customers.Post; // getting the generated id from Firestore newid := Customers.FieldByName('id').AsString; // adding the invoice for the customer Invoices.Insert; ... more data for the invoice record as needed ... // setting the foreign key value as the id generated earlier Invoices.FieldByName('customer_id').AsString := newid; Invoices.Post;
To rescue with async
Fortunately, we have a solution for the above coding problems in the new version of TWebFirestoreClientDataSet. They consist of the following methods that allow you to wait for the outcome of the previous async operation before proceeding with code that depends on the outcome.
fireStoreClientDataSet.OpenAsync( procedure(success: Boolean; errorName, errorMsg: String) begin if not success then begin .. handle error case by using errorMsg .. end else begin .. further processing on success .. .. First, Insert, Edit, etc .. end; end);
The method SignInAsync discussed in Part 1 of this article is another async method to use when you also need to login with the Email and Password of the end user.
fireStoreClientDataSet.SignInAsync( aUserEmail, aPassword, False, // IsSignup flag procedure(success: Boolean; errorName, errorMsg: String) begin .. do something on success or failure .. end );
PostAsync after Insert
Similarly, if you were to do an Insert and obtain the generated ID for the record in the Firestore collection, you will use this kind of code.
fireStoreClientDataSet.Insert; fireStoreClientDataSet.FieldByName('FirstName').AsString := 'John'; .. set other field values as required in the new record .. fireStoreClientDataSet.PostAsync( procedure(success: Boolean; data: JSValue; errorName, errorMsg: String) begin if not success then begin .. handle error case .. end else begin .. data parameter coming above is the id generated by Firestore for the new record .. .. do something with the id if required .. .. further processing on success .. end; end);
PostAsync after Edit
Here is an example of modifying a record and waiting for the outcome.
fireStoreClientDataSet.Edit; fireStoreClientDataSet.FieldByName('FirstName').AsString := 'John'; .. set other field values as required in the new record .. fireStoreClientDataSet.PostAsync( procedure(success: Boolean; data: JSValue; errorName, errorMsg: String) begin if not success then begin .. handle error case .. end else begin .. data parameter is the JSON data object of the record that was updated .. end; end);
Similarly, there are DeleteAsync and CloseAsync methods that return a success or failure to the passed response procedure with a signature similar to OpenAsync.
- First, we discussed some issues with the classic VCL code used to process the records of a Firestore ClientDataSet in code. This is due to the async nature of the cloud database operations.
- Next, we showed how the new async methods help you wait for the outcome of previous database operation before proceeding with code that depends on it.
We hope that you have enjoyed discovering the new features in the Firestore ClientDataSet component in this 4 part series.
If you have missed the previous articles, here are the links:
- Firestore server-side filtering
- Multi-tenant Firestore apps
- Introduction to using Firestore in web client apps
We will look forward to any additional ideas and feedback from you!
This blog post has not received any comments yet.
All Blog Posts | Next Post | Previous Post