UPDATE 8/17/16
If you want to remove local AND server data, all you have to do is call the delete method from your MSSyncTable.
It sends a request to your local data source to remove the given item, then queues a request to send the delete
to the mobile service.
It first removes the data locally.
Then, when the queued request goes through into the mobile service, then it will update remotely,
and you can log into your Azure account, look at the Easy Tables, and see that the item has been removed.
1 2 3 4 5 6 7 8 9 10 11 |
/// Sends a request to the MSSyncContext's data source to delete the given /// item in the local store. In addition QUEUES a request to send the delete /// to the mobile service. [self.syncTable delete:item completion:^(NSError * _Nullable error) { if(error) { NSLog(@"ERROR %@", error); } else { NSLog(@"DELETED LOCALLY!"); dispatch_async(dispatch_get_main_queue(), completion); } }]; //delete |
Let’s say you have added 4 entries:
The database reflects this:
Then, let’s say there is a direct deletion in the DB. The item we deleted it is has id A24D9651-A252-4A70-81A8-61520BB5C0D1
Now, even though this is not the way Microsoft wants us to do deletion, we do need a way to sync our local DB with the server DB just in case this happens.
Open the project. Let’s implement a log method to display the data in our local db.
QSAppDelegate.h
1 |
-(void)logAllTodoItem; |
The logging is to verify correctness of local db. It is also used to get the id of users you want to delete.
For example, on the server side, if someone deletes Ivan, and in case you forgot to safe the ID, you can always use the log method to display the local results:
2 — text = Ivan, id = 46206A2F-26A7-4667-8A8E-391CA51EE731
QSAppDelegate.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
-(void)logAllTodoItem { NSLog(@"%s - logAllTodoItem method called", __FUNCTION__); //Get all the projects in the data store NSFetchRequest * request = [[NSFetchRequest alloc] init]; NSEntityDescription * entity = [NSEntityDescription entityForName:@"TodoItem" inManagedObjectContext:self.managedObjectContext]; request.entity = entity; //use performBlock if your self.mainQueueContext is a Private Concurrent Queue //[self.mainQueueContext performBlock:^{ //use it like this if your mainQueueContext is of Private MAIN Queue, which updates the UI NSArray * list = [self.managedObjectContext executeFetchRequest:request error:nil]; //List out contents of each project if( [list count] == 0 ) { NSLog(@"%s - There are no Events in the data store yet", __FUNCTION__); } else { NSLog(@"------------ RESULTS FROM DATABASE ------------"); NSLog(@"%lu", (unsigned long)[list count]); [list enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"%lu --- text = %@, id = %@", (unsigned long)idx, [obj valueForKey:@"text"], [obj valueForKey:@"id"]); }]; } } |
Delete – server and local
Then, let’s write the delete method for the service, so that we can remove the server data, and the local data.
Notice that we use MSTable’s deleteWithId to remove the item from the server.
We use MSSyncTable’s delete:item to remove item locally.
QSTodoService.h
1 2 |
- (void)deleteItem:(NSDictionary *)item completion:(QSCompletionBlock)completion; |
QSTodoService.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
//mark the delete column to true - (void)deleteItem:(NSDictionary *)item completion:(QSCompletionBlock)completion { //get the entity's id NSString * rowId = (NSString*)[item objectForKey:@"id"]; //get the Easy Table name on server MSTable *itemTable = [self.client tableWithName:@"TodoItem"]; //tell this table to delete the entity with specified ID [itemTable deleteWithId: rowId completion:^(id _Nullable itemId, NSError * _Nullable error) { if(!error) { NSLog(@"DELETED IN SERVER"); } //LOCAL DELETE [self.syncTable delete:item completion:^(NSError * _Nullable error) { if(error) { NSLog(@"ERROR %@", error); } else { NSLog(@"DELETED LOCALLY!"); dispatch_async(dispatch_get_main_queue(), completion); } }]; //delete }]; //deleteWithId } |
Now, given an ID, let’s check to see if the server has that ID. Insert the ID of the item you just deleted (A24D9651-A252-4A70-81A8-61520BB5C0D1) into rowId.
For matching local data wit direct delete in the server DB, we simply use local delete.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
-(void)checkServerDeletionCompletion:(QSCompletionBlock)completion { //you can do this periodically //for example, if read your local data, and want to check if an id still exist in the server NSString * rowId = [NSString stringWithFormat:@"A24D9651-A252-4A70-81A8-61520BB5C0D1"]; //get the Easy Table name on server MSTable *itemTable = [self.client tableWithName:@"TodoItem"]; [itemTable readWithId:rowId completion:^(NSDictionary * _Nullable item, NSError * _Nullable error) { if(!error) { NSLog(@"%@", item); } else { NSLog(@"%@", error); NSDictionary * toDelete = [NSDictionary dictionaryWithObjectsAndKeys:rowId, @"id", nil]; [self.syncTable delete:toDelete completion:^(NSError * _Nullable error) { if(error) { NSLog(@"ERROR %@", error); } else { NSLog(@"DELETED LOCALLY!"); dispatch_async(dispatch_get_main_queue(), completion); } }]; } }]; } |
For simplicity purposes, we’ll just do the logging, and checking in refresh method of QSTodoListViewController:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
- (void) refresh { QSAppDelegate *appDelegate = (QSAppDelegate *)[[UIApplication sharedApplication] delegate]; [appDelegate logAllTodoItem]; [self.todoService checkServerDeletionCompletion:^{ QSAppDelegate *appDelegate = (QSAppDelegate *)[[UIApplication sharedApplication] delegate]; [appDelegate logAllTodoItem]; }]; [self.refreshControl beginRefreshing]; [self.todoService syncData:^ { [self.refreshControl endRefreshing]; }]; } |
Run the program…first you’ll see the logging to prove that your local database is out of sync with the server database:
2016-08-16 16:19:42.297 EpamEvents[4441:456591] -[QSAppDelegate logAllTodoItem] – logAllTodoItem method called
2016-08-16 16:19:42.297 EpamEvents[4441:456591] ———— RESULTS FROM DATABASE ————
2016-08-16 16:19:42.298 EpamEvents[4441:456591] 4
2016-08-16 16:19:42.298 EpamEvents[4441:456591] 0 — text = Dean, id = 1D9AB54C-793A-4240-A8FB-3E968BB16D09
2016-08-16 16:19:42.298 EpamEvents[4441:456591] 1 — text = Ricky, id = A24D9651-A252-4A70-81A8-61520BB5C0D1
2016-08-16 16:19:42.298 EpamEvents[4441:456591] 2 — text = Ralph, id = 49E6A521-CB9D-4D37-9CEB-F81403707202
2016-08-16 16:19:42.299 EpamEvents[4441:456591] 3 — text = Ivan, id = 46206A2F-26A7-4667-8A8E-391CA51EE731
Then, when you run through the method, you will see the checkServerDeletionCompletion method call readWithId. First it checks for item (A24D9651-A252-4A70-81A8-61520BB5C0D1). Remember, someone has mistakenly deleted this item from the DB directly, hence you’ll get an error warning like so:
2016-08-16 16:20:06.084 EpamEvents[4441:456591] Error Domain=com.Microsoft.MicrosoftAzureMobile.ErrorDomain Code=-1302 “The item does not exist” UserInfo={NSLocalizedDescription=The item does not exist, com.Microsoft.MicrosoftAzureMobile.ErrorRequestKey=
“Cache-Control” = “no-cache”;
“Content-Length” = 35;
“Content-Type” = “application/json; charset=utf-8”;
Date = “Tue, 16 Aug 2016 08:19:58 GMT”;
Etag = “W/\”23-xvKyQMaUWUD9x7DI0LISBQ\””;
Expires = 0;
Pragma = “no-cache”;
Server = “Microsoft-IIS/8.0”;
“Set-Cookie” = “ARRAffinity=871fe01e072348697c5ee601ae1b8377c6473f1f3b3bb965170b664f5c32221d;Path=/;Domain=epamevents.azurewebsites.net”;
“X-Powered-By” = “Express, ASP.NET”;
} }}
Then once the error is noticed, we delete it from our local via the delete method. As you can see, locally, Ricky has been deleted.
The result is:
2016-08-16 16:20:06.091 EpamEvents[4441:457812] DELETED LOCALLY!
2016-08-16 16:20:09.886 EpamEvents[4441:456591] -[QSAppDelegate logAllTodoItem] – logAllTodoItem method called
2016-08-16 16:20:09.887 EpamEvents[4441:456591] ———— RESULTS FROM DATABASE ————
2016-08-16 16:20:09.887 EpamEvents[4441:456591] 3
2016-08-16 16:20:09.887 EpamEvents[4441:456591] 0 — text = Dean, id = 1D9AB54C-793A-4240-A8FB-3E968BB16D09
2016-08-16 16:20:09.888 EpamEvents[4441:456591] 1 — text = Ralph, id = 49E6A521-CB9D-4D37-9CEB-F81403707202
2016-08-16 16:20:09.888 EpamEvents[4441:456591] 2 — text = Ivan, id = 46206A2F-26A7-4667-8A8E-391CA51EE731