Azure Mobile Client: Conflict Handling UPDATE Operation error.Result == null - azure-mobile-services

In my Conflict Handling code occacionally it happens that the error.Result is null in case of an UPDATE operation and I don't know why or what causes that.
That is my code:
private async Task HandleConflictResolutionOfLocalTablePushAttemptAsync(ReadOnlyCollection<MobileServiceTableOperationError> syncErrors)
{
/*
* * Handling Conflict Resolution
* * see :https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/chapter3/client/#handling-conflict-resolution
* * */
// Simple error/conflict handling. A real application would handle the various errors like network conditions,
// server conflicts and others via the IMobileServiceSyncHandler.
if (syncErrors != null)
{
foreach (var error in syncErrors)
{
var localItem = error.Item;
var serverItem = error.Result;
if (error.OperationKind == MobileServiceTableOperationKind.Update && error.Result != null)
{
var localVersion = localItem?[MobileServiceSystemColumns.Version];
var serverVersion = serverItem?[MobileServiceSystemColumns.Version];
if (localVersion == null)
{
if (await IsRemoteBackendReachable())
{
//Update failed, reverting to server's copy.
await error.CancelAndUpdateItemAsync(serverItem);
await MetricsManagerHelper.Instance
.SendErrorToApplicationInsightsAsync($"Operation: {error.OperationKind} LocalVersion == NULL." +
"Action: Reverting to remote-db server's copy\r\n" +
$"Table: {error.TableName} Record Id: {localItem?[MobileServiceSystemColumns.Id]}\r\n" +
$"Local: {localItem}\r\n" +
$"Server: {serverItem}");
}
continue;
}
if (serverVersion == null)
{
if (await IsRemoteBackendReachable())
{
//Update failed, reverting to server's copy.
await error.CancelAndUpdateItemAsync(serverItem);
await MetricsManagerHelper.Instance
.SendErrorToApplicationInsightsAsync($"Operation: {error.OperationKind} ServerVersion == NULL.\r\n" +
"Action: Reverting to remote-db server's copy\r\n" +
$"Table: {error.TableName} Record Id: {localItem?[MobileServiceSystemColumns.Id]}\r\n" +
$"Local: {localItem}\r\n" +
$"Server: {serverItem}");
}
continue;
}
// Set Local version to Server Version an Update again. PendingOpertation is set to new updated localItem and in outer loop a PushAsync is attempted again.
localItem[MobileServiceSystemColumns.Version] = serverItem[MobileServiceSystemColumns.Version];
if (await IsRemoteBackendReachable())
{
await error.UpdateOperationAsync(JObject.FromObject(localItem));
await MetricsManagerHelper.Instance.SendInfoToApplicationInsightsAsync($"Operation: {error.OperationKind} LocalVersion older than (<) ServerVersion. LocalItem updated in local.db.");
}
}
else
{
if (await IsRemoteBackendReachable())
{
// Discard local change.
await error.CancelAndDiscardItemAsync();
await MetricsManagerHelper.Instance
.SendErrorToApplicationInsightsAsync($"Operation: {error.OperationKind} PUSH failed.\r\n" +
"Action: Discard local-db change.\r\n" +
$"Table: {error.TableName} Record Id: {localItem?[MobileServiceSystemColumns.Id]}\r\n" +
$"Local: {localItem}\r\n" +
$"Server: {serverItem}");
}
}
}
}
}
In case when error.Result is null I have no other option than discarding the localItem changes.
Can you explain me when it can happen that error.Result is null?
The only reasonable guess I have is that the intenet connection is lost and no serverItem can be retrieved. But immediatly before I call the conflict handler I check if my remote backend is reachable.
Thanks in advance
Eric

Related

Flutter: Failed assertion: 'file.absolute.existsSync()': is not true

In my app, a user can send a file to others in a group chat. First, the user records some audio using their mic. The file is then touched up using FFMPEG. Then, the file is uploaded to Firebase Cloud Storage and if this is successful, a record is written in Firebase Realtime Database.
I'm getting the error below when the user records a long audio file and then presses submit. It almost seems as though FFMPEG hasn't finished processing the file...but I thought I used my async/await correctly to make sure that this processing is finished before moving on?
##MyAppFile## saveMyAppFileToCloudStorage Error: 'package:firebase_storage/src/reference.dart': Failed assertion: line 127 pos 12: 'file.absolute.existsSync()': is not true.
Psuedo-code:
User records audio
Audio file is processed using FFMPEG and the new processed file is created on the user's phone
User hits submit, uploading the file to Cloud Storage and, if successful, writing a record to Realtime Database
Order of Functions After User Hits Submit:
msgInput.dart -> sendMyAppFile()
msgInput.dart -> prepareMyAppFileForSending()
msgInput.dart -> runFFMPEGHighLow()
message_dao.dart -> sendMyAppFile()
message_dao.dart -> saveMyAppFileToCloudStorage() //ERROR COMES FROM THIS FUNCTION
The Code:
//msgInput.dart
Future<void> sendMyAppFile() async {
if (sendableMyAppFileExists == 1) {
final MyAppFileReadyToBeSent = await prepareMyAppFileForSending();
if (MyAppFileReadyToBeSent == '1') {
messageDao.sendMyAppFile(MyAppFile, filepath, filename);
} else {
}
}
setState(() {
sendableMyAppFileExists = 0;
});
}
Future<String> prepareMyAppFileForSending() async {
if (sendableMyAppFileExists == 1) {
if (recordedMyAppFileFilterID == '1') {
await runFFMPEGHighLow('1');
return '1';
}
if (recordedMyAppFileFilterID == '2') {
await runFFMPEGHighLow('2');
return '1';
}
}
return '0';
}
Future<void> runFFMPEGHighLow(String filterID) async {
if (filterID != '1' && filterID != '2') {
return;
}
if (sendableMyAppFileExists == 1) {
if (filterID == '1') {
await FFmpegKit.executeAsync(/*...parms...*/);
setState(() {
currentMyAppFileFilename = currentMyAppFileFilename + '1.mp3';
});
}
if (filterID == '2') {
await FFmpegKit.executeAsync(/*...parms...*/);
setState(() {
currentMyAppFileFilename = currentMyAppFileFilename + '2.mp3';
});
}
}
}
//message_dao.dart
void sendMyAppFile(ChatData MyAppFile, String filepath, String filename) {
saveMyAppFileToCloudStorage(filepath, filename).then((value) {
if (value == true) {
saveMyAppFileToRTDB(MyAppFile);
}
});
}
Future<bool> saveMyAppFileToCloudStorage(String filepath, String filename) async {
//filepath: /data/user/0/com.example.MyApp/app_flutter/MyApp/MyAppAudioFiles/MyAppFiles/2d7af6ae-6361-4be5-8209-8498dd17d77d1.mp3
//filename: 2d7af6ae-6361-4be5-8209-8498dd17d77d1.mp3
_firebaseStoragePath = MyAppFileStorageDir + filename;
File file = File(filepath);
try {
await _firebaseStorage
.ref(_firebaseStoragePath)
.putFile(file);
return true;
} catch (e) {
print('##MyAppFile## saveMyAppFileToCloudStorage Error: ' + e.toString()); //ERROR COMES FROM THIS LINE
return false;
}
return true;
}
I assume you're using the package ffmpeg_kit_flutter.
First, why it's not working: execute and executeAsync return FFmpegSession objects. The run of FFmpeg doesn't need to be finished for these methods to complete. In fact, the returned session object has methods like getState to monitor whether the run of FFmpeg has completed.
A good way to fix this: The documentation for executeAsync has a hint for what to do here.
Note that this method returns immediately and does not wait the execution to complete. You must use an FFmpegSessionCompleteCallback if you want to be notified about the result.
You can set a completion callback by passing a function to executeAsync. Here's the full function signature from the docs:
Future<FFmpegSession> executeAsync(
String command,
[FFmpegSessionCompleteCallback? completeCallback = null,
LogCallback? logCallback = null,
StatisticsCallback? statisticsCallback = null]
)
FFmpegSessionCompleteCallback is just a function that accepts an FFmpegSession and returns nothing. You can provide your own.
void someCompletionFunction() {
setState(() {
currentMyAppFileFilename = currentMyAppFileFilename + '1.mp3';
});
}
await FFmpegKit.executeAsync(/*...parms...*/, someCompletionFunction);
Future vs callback: If you prefer to use Futures and async-await instead of callbacks, you'll need to create your own Future and update it in the callback. See Dart, how to create a future to return in your own functions? for an example.

The function call is not returning null but still giving "null" to be returned error

I'm migrating my old version flutter code to latest version with null safety feature.
In a function call I am getting the error "The body might complete normally, causing 'null' to be returned, but the return type is a potentially non-nullable type". I have enclosed my code in try catch block and in catch block I added rethrow statement to prevent null exception.
This is my code.
Future<Map<String, dynamic>> fetchTimeline(http.Client client) async {
try {
print('INVOICE URL: ${globals.ursl.getURL(URLS.GETINVOICEURL)}');
Response response;
Dio dio = new Dio();
response = await dio.get(globals.ursl.getURL(URLS.GETINVOICEURL));
print('INVOICE GET RESPONSE: $response');
if (response.statusCode == 200) {
Map mapobject = (json.decode(response.toString()));
var succes = mapobject['success'];
if (succes == 1) {
if (mapobject['Invoice'][0]['address'] == null ||
mapobject['Invoice'][0]['address'] == '') {
address = '';
} else {
address = mapobject['Invoice'][0]['address'];
}
if (mapobject['Invoice'][0]['contact'] == null ||
mapobject['Invoice'][0]['contact'] == '')
phone = '';
else
phone = mapobject['Invoice'][0]['contact'];
if (mapobject['Invoice'][0]['restaurant_name'] == null ||
mapobject['Invoice'][0]['restaurant_name'] == '') {
name = ' ';
} else {
name = mapobject['Invoice'][0]['restaurant_name'];
}
logo = mapobject['Invoice'][0]['logo'];
globals.invoiceData = mapobject['Invoice'][0];
startTime();
return mapobject['Invoice'][0];
} else {
return {};
}
}
} catch (error) {
client.close();
print("CONNECTION CLOSED: $error");
rethrow;
}
}
I have added rethrow in catch block but still error is there.
Anyone there to help me out.
Thanks
It's a little hard to see with all the nested if statements, but you aren't returning a Map<String, dynamic> in every branch. This condition if (response.statusCode == 200) { ... } does not have a corresponding else branch, and so if the statusCode is some value other than 200 you are not returning anything (which means you are implicitly returning null in that case).

How can I get multiple messages from dart isolate?

How can I get multiple messages from dart isolate?
I'm trying to create an excel file and want to do some operation on that file in an isolate. Before doing an operation on that file, I want to return an message to main isolate, that excel file is created.
Here is function goes in isolate :
foo(String filePath){
// create excel file
var bytes = File(filePath).readAsBytesSync();
var excel = Excel.decodeBytes(bytes);
//HERE I WANT TO SEND THE MESSAGE THAT CREATING EXCEL FILE IS DONE
// some operatoin on excel file
var result = doSomeOperation(excel);
return result;
}
Main isolate code :
var result = await compute(foo, filePath);
What should I do to get creating file message before the actual result comes?
For excel, I'm using excel: ^2.0.0-null-safety-3 package.
Compute only returns one result. If you want to pass multiple 'events' back to the main isolate then you need to use the full Isolate logic (with sendPort and receivePort).
For example, the following code runs in an isolate, and downloads a file while emitting float values to represent progress, potentially a String to indicate log messages and then a bool to indicate success or failure upon completion.
Future<void> isolateDownload(
DownloadRequest request) async {
final sendPort = request.sendPort;
if (sendPort != null) {
var success = false;
var errorMessage = '';
var url = Uri.parse('a_url_based_on_request');
IOSink? out;
try {
http.StreamedResponse response =
await http.Client().send(http.Request('GET', url));
if (response.statusCode == 200) {
var filePath =
join(request.destinationDirPath, '${request.fileName}.ZIP');
var contentLength = response.contentLength;
var bytesLoadedUpdateInterval = (contentLength ?? 0) / 50;
var bytesLoaded = 0;
var bytesLoadedAtLastUpdate = 0;
out = File(filePath).openWrite();
await response.stream.forEach((chunk) {
out?.add(chunk);
bytesLoaded += chunk.length;
// update if enough bytes have passed since last update
if (contentLength != null &&
bytesLoaded - bytesLoadedAtLastUpdate >
bytesLoadedUpdateInterval) {
sendPort.send(bytesLoaded / contentLength);
bytesLoadedAtLastUpdate = bytesLoaded;
}
});
success = true;
if (contentLength != null) {
sendPort.send(1.0); // send 100% downloaded message
}
} else {
errorMessage =
'Download of ${request.fileName} '
'received response ${response.statusCode} - ${response.reasonPhrase}';
}
} catch (e) {
errorMessage = 'Download of ${request.chartType}:${request.chartName} '
'received error $e';
} finally {
await out?.flush();
await out?.close();
if (errorMessage.isNotEmpty) {
sendPort.send(errorMessage);
}
sendPort.send(success);
}
}
}
The code that spawns the isolate then simply checks for the type of the message passed to it to determine the action.
Future<bool> _downloadInBackground(
DownloadRequest request) async {
var receivePort = ReceivePort();
request.sendPort = receivePort.sendPort;
var isDone = Completer();
var success = false;
receivePort.listen((message) {
if (message is double) {
showUpdate(message);
}
if (message is String) {
log.fine(message); // log error messages
}
if (message is bool) {
success = message; // end with success or failure
receivePort.close();
}
}, onDone: () => isDone.complete()); // wraps up
await Isolate.spawn(isolateDownload, request);
await isDone.future;
return success;
}

Only Update Entity (Parent or children) if Property Values Have Changed with EF

As I build a project with Entity Framework 6 (using EF for the first time), I noticed that when I only Update the relationships of an Entity, EF updates the main Entity too.
I can tell this is happening because I'm using System Versioned tables on Sql Server 2017.
This is a made up scenario, but most of the concept is here.
public async Task<ActionResult> Edit([Bind(Include="Id,Name,LocationTimes")] LocationViewModel locationVM) {
if (ModelState.IsValid) {
var location = await _db.Locations.FirstOrDefaultAsync(l => l.Id == locationsViewModel.Id && l.UserId == UserId);
if (location == null) {
return HttpNotFound();
}
location.Name = locationsViewModel.Name;
// ... other properties
foreach (var day in locationsViewModel.LocationTimes.Days) {
var time = new Time {
Day = day.Day,
OpenTime = day.OpenTime,
CloseTime = day.CloseTime,
};
// Find current Time or keep newly created
time = await time.FindByTimeAsync(time, _db) ?? time;
// Find LocationTime with same day
var locationTime = location.LocationTimes.FirstOrDefault(lt => lt.Time.Day == day.Day);
// If all times are the same, skip (continue)
if (locationTime != null && locationTime.Time.OpenTime == time.OpenTime && locationTime.Time.CloseTime == time.CloseTime)
continue;
if (locationTime != null && (locationTime.Time.OpenTime != time.OpenTime || locationTime.Time.CloseTime != time.CloseTime)) {
// Remove, At least one of the Times do not match
locationTime.Time = time;
_db.Entry(locationTime).State = EntityState.Modified;
} else {
location.LocationTimes.Add(new LocationTime {
Location = location,
Time = time,
});
}
}
_db.Entry(location).State = EntityState.Modified;
await _db.SaveChangesAsync();
return RedirectToAction("Index");
}
}
I assume, that by marking the entire Entity as Modified, EF will call the update statement.
How can I avoid an UPDATE to the parent Entity, if no properties have changed on the parent, but still Add/Update the child relationships?
I assume I have to check that each property has not changed and therefore I should not be setting location state to Modified, but how would I handle the newly added Times?
Update #1
So I tried what I mentioned and it works, but is this the correct way to do this?
public async Task<ActionResult> Edit([Bind(Include="Id,Name,LocationTimes")] LocationViewModel locationVM) {
if (ModelState.IsValid) {
var location = await _db.Locations.FirstOrDefaultAsync(l => l.Id == locationsViewModel.Id && l.UserId == UserId);
if (location == null) {
return HttpNotFound();
}
/*******************
This is new part
*******************/
if (
location.Name != locationsViewModel.Name
// || ... test other properties
) {
location.Name = locationsViewModel.Name;
// ... other properties
_db.Entry(location).State = EntityState.Modified;
} else {
_db.Entry(location).State = EntityState.Unchanged;
}
/*******************/
foreach (var day in locationsViewModel.LocationTimes.Days) {
var time = new Time {
Day = day.Day,
OpenTime = day.OpenTime,
CloseTime = day.CloseTime,
};
// Find current Time or keep newly created
time = await time.FindByTimeAsync(time, _db) ?? time;
// Find LocationTime with same day
var locationTime = location.LocationTimes.FirstOrDefault(lt => lt.Time.Day == day.Day);
// If all times are the same, skip (continue)
if (locationTime != null && locationTime.Time.OpenTime == time.OpenTime && locationTime.Time.CloseTime == time.CloseTime)
continue;
if (locationTime != null && (locationTime.Time.OpenTime != time.OpenTime || locationTime.Time.CloseTime != time.CloseTime)) {
// Remove, At least one of the Times do not match
locationTime.Time = time;
_db.Entry(locationTime).State = EntityState.Modified;
} else {
location.LocationTimes.Add(new LocationTime {
Location = location,
Time = time,
});
}
}
/* removed, added above */
//_db.Entry(location).State = EntityState.Modified;
await _db.SaveChangesAsync();
return RedirectToAction("Index");
}
}
So after trial and error, I guess I misunderstood how EF handles the EntityState. I though if a child was Modified, you had to set the parent as Modified as well.
Gladly, that's not the case and the code below works as desired.
public async Task<ActionResult> Edit([Bind(Include="Id,Name,LocationTimes")] LocationViewModel locationVM) {
if (ModelState.IsValid) {
var location = await _db.Locations.FirstOrDefaultAsync(l => l.Id == locationsViewModel.Id && l.UserId == UserId);
if (location == null) {
return HttpNotFound();
}
/*******************
This is new part
check if at least one property was changed
*******************/
if (
location.Name != locationsViewModel.Name
|| location.Ref != locationsViewModel.Ref
// || ... test other properties
) {
location.Name = locationsViewModel.Name;
location.Ref = locationsViewModel.Ref;
// ... other properties
// Tell EF that the Entity has been modified (probably not needed, but just in case)
_db.Entry(location).State = EntityState.Modified;
} else {
// Tell EF that the Entity has *NOT* been modified
_db.Entry(location).State = EntityState.Unchanged;
}
/*******************/
foreach (var day in locationsViewModel.LocationTimes.Days) {
var time = new Time {
Day = day.Day,
OpenTime = day.OpenTime,
CloseTime = day.CloseTime,
};
// Find current Time or keep newly created
time = await time.FindByTimeAsync(time, _db) ?? time;
// Find LocationTime with same day
var locationTime = location.LocationTimes.FirstOrDefault(lt => lt.Time.Day == day.Day);
// If all times are the same, skip (continue)
if (locationTime != null && locationTime.Time.OpenTime == time.OpenTime && locationTime.Time.CloseTime == time.CloseTime)
continue;
if (locationTime != null && (locationTime.Time.OpenTime != time.OpenTime || locationTime.Time.CloseTime != time.CloseTime)) {
// Remove, At least one of the Times do not match
locationTime.Time = time;
_db.Entry(locationTime).State = EntityState.Modified;
} else {
location.LocationTimes.Add(new LocationTime {
Location = location,
Time = time,
});
}
}
/* removed, added above */
//_db.Entry(location).State = EntityState.Modified;
await _db.SaveChangesAsync();
return RedirectToAction("Index");
}
}

Entity Framework savechanges() update delete insert

I want my SaveChanges() function to update a record in my database and if the return value is '1' which is coming from my stored procedure then and only then 'delete' command (stored procedure) should not be executed.
Now, the problem is db.SaveChanges() (an instance of ObjectContext) is updating my record successfully but after updating, it executes the delete command. How should I tell my function that not to execute delete command.
using (var db = new PRLAdminEntities())
{
bool isExists = false;
string lastExisting = string.Empty;
string errorString = string.Empty;
db.Connection.Open();
trans = db.Connection.BeginTransaction();
//accounts to be sent back to client
var countriesToSendBack = new List<Polo.Common.Shared.Entities.Country>();
//process each account requiring database update
if (request.CountriesToUpdate != null)
{
foreach (var country in request.CountriesToUpdate)
{
//countriesToSendBack.Remove(country);
var temp = from row in db.Countries where row.Name.ToUpper() == country.Name.ToUpper() select row;
if (temp.Count<Polo.Common.Shared.Entities.Country>() > 0 && country.ChangeTracker.State == ObjectState.Added)
{
countriesToSendBack.Add(country);
db.Countries.ApplyChanges(country);
isExists = true;
lastExisting = country.Name;
errorString += country.Name + ", ";
//db.GetAllCountries();
//break;
continue;
}
if (country.ChangeTracker.State == ObjectState.Deleted)
{
db.DeleteObject(country);
}
//if a change or modification (not a delete)
if (country.ChangeTracker.State != ObjectState.Deleted)
{
//this account should be sent back
if (!countriesToSendBack.Contains((country)))
countriesToSendBack.Add(country);
if (country.Active == false)
{
db.Countries.ApplyCurrentValues(country);
}
}
//apply all changes
db.Countries.ApplyChanges(country);
}
if (isExists)
{
//response.Success = false;
//errorString.Replace(", " + lastExisting + ",", " & " + lastExisting);
//response.FaultMessage = "Duplicate Records";
}
}
//save all changes
int total = db.SaveChanges();
response.Success = true;
foreach (var countryItem in countriesToSendBack)
{
countryItem.Id = (from row in db.Countries where row.Name.ToUpper() == countryItem.Name.ToUpper() select row.Id).FirstOrDefault();
}
trans.Commit();
//refresh the account data which gets timestamp etc
db.Refresh(RefreshMode.StoreWins,countriesToSendBack);
//set the response values
response.Countries = countriesToSendBack;
}
}
Perhaps I misread your question, I do not totally get what you are trying to do.
But why not call SaveChanges() after the change and when all checks are positive perform a remove() and call savechanges() again?
There is no harm is calling SaveChanges() multiple times. It will mirror it's data to your database. If you perform a remove it will try to delete it in your database. That's the nice thing about it.. it does what you tell it to do ;-)