# Postmort hospitaleem: Stale UI Data in WPF MVVM Due to Unhandled Database Changes
## Summary
A WPF MVVM application failed to reflect external database changes in UI controls (`DataGrid`, `TextBox`, etc.) because it lacked a mechanism to detect/apply live updates. This led users to act on **stale data**.
## Root Cause
The core failure was:
- **Single-fetch data loading** β Initial data bindings worked via `ObservableCollection`𧨠but only at startup
- **Zero external change detection** β No architecture to detect changes made outside the app (e.g., SSMS)
- **State unawareness** β Running data refreshes during user edits destroyed pending changes
## Why This Happens in Real Systems
Common architectural gaps:
- π§Ύ **Assumption of data ownership**: Engineers assume no concurrent writes (false in multi-user systems)
- π **Polling complexity**: Implementing robust polling requires handling:
- Thread safety
- Update conflicts
- Performance tuning
- βοΈ **State complexity**: Ignoring UI state (e.g., edit-in-progress) during refreshes
## Real-World Impact
- **Financial loss** β Trading apps showing outdated prices
- **Data corruption** β Forced refreshes overwriting user edits mid-transaction
- **Reputation damage** β Medical apps displaying incorrect patient vitals
- **UX failure** β Users losing trust in system accuracy
## Example or Code
```csharp
// RefreshService.cs (Polling Implementation)
public class RefreshService
{
private readonly DispatcherTimer _refreshTimer;
private readonly Action _refreshAction;
private bool _isUserEditing;
public RefreshService(Action refreshAction, TimeSpan interval)
{
_refreshAction = refreshAction;
_refreshTimer = new DispatcherTimer { Interval = interval };
_refreshTimer.Tick += (s, e) => CheckForUpdates();
}
public void Start() => _refreshTimer.Start();
public void Pause() => _isUserEditing = true;
public void Resume() => _isUserEditing = false;
private void CheckForUpdates()
{
if (_isUserEditing) return;
lock (_refreshAction)
{
_refreshAction.Invoke();
}
}
}
// ViewModel.cs (ateral Usage)
public class MainViewModel
{
private RefreshService _refreshService;
public MainViewModel()
{
_refreshService = new RefreshService(RefreshData, TimeSpan.FromSeconds(30));
_refreshService.Start();
}
public void OnEditStarted() => _refreshService.Pause();
public void OnEditCompleted() => _refreshService.Resume();
private void RefreshData()
{
// Thread-safe DB fetch & ObservableCollection update
}
}
How Senior Engineers Fix It
-
Polling with locks:
- Wrap refreshes in
lock()blocks to prevent race conditions - Prioritize atomic operations for collection updates
- Wrap refreshes in
-
Edit-aware synchronization:
- Freeze refreshes during edits using UI state flags
- Implement dirty-checking to resume updates on save/cancel
-
Optimized updates:
- Use differential polling β Query DB for changes since last fetch
- Apply
INotifyPropertyChangedonly to changed items - Partial refresh for complex screens:
- Refresh only active controls
- Scope updates to tab-specific data
-
Advanced tooling:
- π‘SQL Dependency for SQL Server immediate change alerts
- Push notifications via SignalR for clustered systems
Why Juniors Miss It
- π MVVM misconception: Belief that
INotifyPropertyChangedauto-magically handles all updates - πΈοΈ Thread negligence: Updating UI-bound objects without
Dispatcher.Invoke - π₯ Scope blindness: Attempting full-page refreshes instead of targeted updates
- π€ Over-engineering fear: Avoiding refreshes altogether due to conflict handling complexity