Managing DbContext in a WPF MVVM Application

I banged my head about this for several days and still cannot decide which one is correct. This question is specifically focused on WPF , because unlike a web application, many posts and articles on the Internet recommend using context for the view-model , rather than context for the request .
I have a WPF MVVM application that uses the Entity-Framework DB first model.
here is an example of two models used in my application (created by EF Designer):

 public partial class User { public User() { this.Role = new HashSet<Role>(); } public string ID { get; set; } public string Name { get; set; } public virtual ICollection<Role> Role { get; set; } } public class Role { public Role() { this.User = new HashSet<User>(); } public int ID { get; set; } public string Name { get; set; } public virtual ICollection<User> User { get; set; } } 

I narrowed down my options on how to handle the following:

1) Creating a DataAccess class that creates and removes DbContext with every method call:

 public class Dal { public User GetUserById(object userId) { using (var db = new DbEntities()) { return db.User.Find(userId); db.SaveChanges(); } } public void RemoveUser(User userToRemove) { using (var db = new DbEntities()) { db.User.Remove(userToRemove); db.SaveChanges(); } } } 

which I can use in my ViewModel as follows:

 public class UserManagerViewModel : ObservableObject { private readonly Dal dal = new Dal(); // models... //commands... } 

2) Similar to approach 1, but without Using statements:

 public class Dal : IDisposable { private readonly DbEntities db = new DbEntities(); public User GetUserById(object userId) { return db.User.Find(userId); db.SaveChanges(); } public void RemoveUser(User userToRemove) { db.User.Remove(userToRemove); db.SaveChanges(); } public void Dispose() { db.SaveChanges(); } } 

Inside ViewModel

used the same

3) Create a repository for each entity . It looks the same as the above parameters (it also matters with or without Using dilemma), however, each repository contains only methods related to its entity .
Afaik is used the same as above in my ViewModel .

4) Create a Unit-Of-Work class that will pass the corresponding repository on request:

 public class UnitOfWork : IDisposable { private DbEntities db = new DbEntities(); private IUserRepository userRepository; public IUserRepository UserRepository { get { return userRepository ?? new UsersRepository(db); } } public void Save() { db.SaveChanges(); } public void Dispose() { db.Dispose(); } } 

and use it inside my ViewModel as follows:

 public class UserManagerViewModel : ObservableObject { private readonly UnitOfWork unit = new UnitOfWork(); // models... //commands... } 

Which of the above approaches (if any) is preferable in terms of concurrency data, better abstraction and layering, and overall performance?
EDIT - Found the following paragraph in this article :

For Windows Presentation Foundation (WPF) or Windows Forms, use a context instance for each form. This allows you to use the change tracking features provided by the context.

However, the question arises whether I should create a DbContext object in my view-model or whether it is better to have a utility class such as my DAL class and reference it.

+8
c # architecture wpf entity-framework mvvm
source share
1 answer

This is what dependency injection solutions are for. Yes, this is another technology to add to your project, but as soon as you start using DI, you never look back.

The real problem is that you are trying to make this decision in your view models when you really need to use control inversion and make the decision above. The WPF / MVVM application will want a context for each form so that the changes are presented only after the user has finished editing, as well as to enable the user to discard the changes. I know that you are not using this in a web application, but a well-designed architecture means you should be able to, in which case you will need context for each request. You might want to write a console program that populates the database with static data, in which case you might need a global / singleton context for performance and ease of use. Finally, your unit tests should also mock the context, perhaps based on each test. All four of these cases should be configured in your injection infrastructure, and your viewing models should neither know nor care about them.

Here is an example. I personally use Ninject, which is specifically designed for .NET. I also prefer NHibernate, although the choice of ORM does not matter here. I have session objects that have different scope requirements, and this is set in the Ninject module that initializes my ORM classes:

 var sessionBinding = Bind<ISession>().ToMethod(ctx => { var session = ctx.Kernel.Get<INHibernateSessionFactoryBuilder>() .GetSessionFactory() .OpenSession(); return session; }); if (this.SingleSession) sessionBinding.InSingletonScope(); else if (this.WebSession) sessionBinding.InRequestScope(); else sessionBinding.InScope(ScreenScope); 

This sets the scope of ISession, which is the NHibernate equivalent of your context class. My repository classes that manage in-memory database objects contain a link to the session with which they are associated:

 public class RepositoryManager : IRepositoryManager { [Inject] public ISession Session { get; set; } ... etc... { 

The [Inject] attribute tells Ninject to automatically fill in this field using the scope rules that I configured. So far, all this is happening in my domain classes, but it extends to my model layer. In my scope rules, I pass an object called "ScreenScope", and although I will not go into it here, it basically means that at any time I request a session object in my ScreenViewModel or any view models that it has as members (including their own children), the same ISession object is automatically created and passed to all of them. Using the scope of the DI, I don’t even need to think about it, I just declare the members with the [Inject] attribute, and this happens:

 public class ScreenViewModel { [Inject] public CustomerService CustomerService { get; set; } [Inject] public SalesService SalesService { get; set; } [Inject] public BillService BillService { get; set; } ...etc... } 

These service classes contain the RepositoryManager set that was introduced, and since they are all in ScreenViewModel, the ISession object will be the same, at least in my WPF assembly. if I switch to my MVC assembly, they will be the same for all the view models created for this request, and if I switch to the console assembly, it uses the same ISession for everything in the whole program.

TL; DR: Use dependency injection and the scope of your contexts in one form.

+2
source share

All Articles