2

I'm developing WPF application using MVVM, Code-First and the repository pattern. I need to have a background task, which processes webserver requests from clients and saves new data into database.

The problem is each ViewModel has a property (ObservableCollection), which gets Repository.GetObservableCollection(). So each ViewModel has a repository instance, which has the same DbContext (so I don't get DbException when saving complex entities). This DbContext is long-living until the end of app in each repo and is injected from MainViewModel to the contructors of VMs.

When I save new data to the database from the background task, the GUI doesn't update, because I'm using different DbContext there (I have to due to concurrent requests):

using (var db = new DbContextManager())
{
    var client = new Client();
    db.Client.Add(client);
    db.SaveChanges();
}

There are two ways, which I tried:

  1. Set up DispatcherTimer in MainViewModel to update ViewModels every 2secs using ViewModel.Repository.LoadAll for each repo. This lags my UI every 2secs, but it works.
  2. When saving new data into the DB, also add the entities to Repositories via.

    Application.Current.Dispatcher.Invoke ( () => { _clientRepository.Add(client); } );

That way, the entities appear in GUI immidiately, but there's also slight lag (when moving window) and I can't update existing entity's property.

The question is how can I refactor this to allow both GUI and background interaction with entities. How to properly combine repository and MVVM?

0

2 Answers 2

1

Don't directly link a ViewModel entity with a DB DataContext. You need to define an ObservableCollection in the ViewModel and its binding in the XAML.

After that you'll asynchronously update the collection by calling functions from your persistence layer. So you better off with a LoadAsync, alternatively you could wrap your DB retrieval into a Task. This approach will work because await will capture the SynchronizationContext of the UI thread and will update your component in the UI thread.

The actual DataContext has to be hidden in that persistence layer and must remain unknown to the ViewModel.

Reply to comments

if I don't pass DbContext to the ViewModels (which in turn pass it to its Repository), how do I get the context to the repository, so I can call

point void LoadAll() { 
    context.Set<T>().Load(); 
} 

and

public ObservableCollection<T> GetObservableCollection() { 
    return context.Set<T>().Local; 
}

As per my answer, the ViewModel can call the persistence layer function to load the collection, (it's not even necessary that the type returned from the persistence layer is observable). The point is that you won't return void and you'll possibly use a LoadAsync so that you can await it when you assign the returned data to the underlying collection of your ViewModel property.

so I have to use Dispatcher.Invoke to interact with repos/viewmodels

No, don't use Dispatcher.Invoke to interact with the repository DB context. Make the interaction with the DB async. Notice that Dispatcher.Invoke is only for the UI layer.

Sign up to request clarification or add additional context in comments.

3 Comments

Ok, but if I don't pass DbContext to the ViewModels (which in turn pass it to its Repository), how do I get the context to the repository, so I can call public void LoadAll() { context.Set<T>().Load(); } and public ObservableCollection<T> GetObservableCollection() { return context.Set<T>().Local; } I'm already using ObservableCollection and binding it. Also, the background data insert runs in different thread, so I have to use Dispatcher.Invoke to interact with repos/viewmodels.
Answer updated, if you still have doubts, please create mcve
The solution for me was utilizing the Messenger/Dispatcher pattern to pass messages between ViewModels.
0

Do you use the binding mechanism? You could update your collection after changing it (assuming that your VM implements INotifyPropertyChanged interface).

4 Comments

Yes, I do. This works for me. My problem lies in the two DbContext instances I have - first one in ViewModel.Repository and the second DbContext is created on demand when request comes. How can I influence (add) to the first DbContext from the second one (= keep them in sync)? I mean its .local property. I can't just pass the first DbContext in constructor, because concurrent requests on the same context result in Exception. Dispatcher.Invoke introduces a small lag in the UI. I just need to unify those contexts, or something like that.
Well, can you use just a global version of context?
The part of code, which handles web requests and inserts new data into the database probably can't use the same global context, because when two requests are processed concurrently, then those operations on the same contexts results in Concurrency Exception.
Perhaps you should take a look at these articles: source 1, source 2. This can be useful.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.