This blog needs some programming experience.
Lc is developed for several years, but as each program it also has some hidden bugs. One of the basic functionality is to delete transactions. You do it rarely, so it is an ideal place for a bug to hide. At row deletion sometimes there was an exception, not all the selected rows were deleted, or the program crashed. But other times there was no problem at all, so I focused on more pressing issues.
Now I started to figure out the problem. I have a method CopyDataFromTableVmToDbAsync which compares the actual view model, the rows in the table, to the backup. If there is any change then the database is updated. After that the view is refreshed; deleting a row will change the USD balance of the subsequent rows. At the refresh the view model backup is also refreshed.
I have put a breakpoint to the first row of CopyDataFromTableVmToDbAsync. You can see Debug info there and after that comes a condition which wants to prevent that two instances of the this method run parallel. It is a previous attempt to handle the delete row problem.
The call stack at the breakpoint is strange. The method is called by some external code (not code I have written).
A normal call stack shows how I got to the current method. I did not remember how I called CopyDataFromTableVmToDbAsync and it was not easy to figure out from the code.
If I go to the Parallel stacks window I see this.
If I turn on "Show external code" then I see a more detailed view. The main thing is that I am in a Worker thread and not in the Main thread.
There is an additional problem. Row 636 did not modify DisplayedRows, it only selected the identifiers into a new variable. Still you can see in the watch window that DisplayedRows has changed. The count was 6 and now it is 5. The red color indicates the change. So while the CopyDataFromTableVmToDbAsync method is executed some other thread changes this variable. The effect of the delete happens in the background gradually. This is really bad. The setter of the SelectedRow is not a good place to handle row deletion because the view model may not fully updated at this time.
private async Task DeleteRowsAsync(object obj)
{
if (IsErrorMethod())
return;
var selectedIds = SelectedItems.Cast<IHasId>().Select(e => e.Id).ToList();
if (selectedIds.Count == 0)
{
MessageBox.Show(Translator["No item is selected."]);
return;
}
foreach (var selectedId in selectedIds)
DisplayedRows.RemoveAll(e => e.Id == selectedId);
await CopyDataFromTableVmToDbAsync();
}
Pressing this button will execute the command which performs the method above. I check if there is any validation error, check if no item is selected, then the selected rows are removed from DisplayedRows. At end I can call CopyDataFromTableVmToDbAsync using the await keyword. I also make sure that the button is disabled while the command is executing.
If you debug now there will be one instance of CopyDataFromTableVmToDbAsync and it is in the main thread. Moreover there will be no thread change during the debug.
The xaml above shows that the CanUserDeleteRows property is false now and I added InputBinding to the new command so the delete key still works.