WPF / EntityFramework Context

Question

We are currently having a WPF application architecture issue. This applies to managing the EntityFramework context, its instance is once and is used throughout the life of the application. Thus, we ended the cache problem, entities do not update when they are loaded once. When using the application, our objects are outdated.

Data sheet

  • Wpf Project
  • . Net Framework 4 Client Profile
  • MEF (Enable in Framework 4.0 System.ComponentModel.Composition)
  • MVVM Design Pattern
  • Multi-user application

Architecture

This is a diagram of the current architecture.

architecture schema

Service level

  • Business rule call management (business layer)
  • Save context (via UnitOfWork) after executing business rules.
  • Only ViewModel can be called.

Business level

  • Definition of business rules
  • It can only be called by the level of service.

Repository level

  • Execute methods that change context data (insert, update, delete)
  • Inherit ReadOnlyRepository
  • Can only be called up by the business layer.

ReadOnlyRepository Level

  • Execute method returning data (select)
  • It can be called everywhere (ViewModel, Service level, Business level).

UnitOfWork

  • Manage contextual settings
  • Save context
  • Context is available only to repositories.

The code

ViewModel

[Export(typeof(OrderViewModel))] [PartCreationPolicy(CreationPolicy.NonShared)] public class OrderViewModel : ViewModelBase { private readonly IOrderManagementService _orderManagementService; private readonly IOrderReadOnlyRepository _orderReadOnlyRepository; [ImportingConstructor] public OrderViewModel(IOrderManagementService orderManagementService, IOrderReadOnlyRepository orderReadOnlyRepository) { _orderManagementService = orderManagementService; _orderReadOnlyRepository = orderReadOnlyRepository; } } 

Service level

 public class OrderManagementService : IOrderManagementService { private readonly IUnitOfWork _unitOfWork; private readonly IOrderManagementBusiness _orderManagementBusiness; [ImportingConstructor] public OrderManagementService (IUnitOfWork unitOfWork, IOrderManagementBusiness orderManagementBusiness) { _unitOfWork= unitOfWork; _orderManagementBusiness = orderManagementBusiness; } } 

Business level

 public class OrderManagementBusiness : IOrderManagementBusiness { private readonly IOrderReadOnlyRepository _orderReadOnlyRepository; [ImportingConstructor] public OrderManagementBusiness (IOrderReadOnlyRepository orderReadOnlyRepository) { _orderReadOnlyRepository = orderReadOnlyRepository; } } 

ReadOnlyRepository Level

 public class OrderReadOnlyRepository : ReadOnlyRepositoryBase<DataModelContainer, Order>, IOrderReadOnlyRepository { [ImportingConstructor] public OrderReadOnlyRepository (IUnitOfWork uow) : base(uow) { } } 

ReadOnlyRepositoryBase

 public abstract class ReadOnlyRepositoryBase<TContext, TEntity> : IReadOnlyRepository<TEntity> where TEntity : class, IEntity where TContext : DbContext { protected readonly TContext _context; protected ReadOnlyRepositoryBase(IUnitOfWork uow) { _context = uow.Context; } protected DbSet<TEntity> DbSet { get { return _context.Set<TEntity>(); } public virtual IEnumerable<TEntity> GetAll(System.Linq.Expressions.Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "") { IQueryable<TEntity> query = DbSet.AsNoTracking(); if (filter != null) { query = query.Where(filter); } foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) { query = query.Include(includeProperty); } if (orderBy != null) { return orderBy(query).ToList(); } return query.ToList(); } public virtual IQueryable<TEntity> All() { return DbSet.AsNoTracking(); } public virtual IQueryable<TEntity> AllWhere(Expression<Func<TEntity, bool>> predicate) { return DbSet.Where(predicate).AsNoTracking(); } public virtual TEntity Get(Expression<Func<TEntity, bool>> predicate) { return DbSet.Where(predicate).AsNoTracking().FirstOrDefault(); } public virtual TEntity GetById(int id) { TEntity find = DbSet.Find(id); _context.Entry(find).State = System.Data.EntityState.Detached; return DbSet.Find(id); } 

We see that the context is provided to the repository in the constructor. Selection methods use the "AsNoTracking ()" method to not cache objects. This is a temporary solution that is obviously not practical in the long run.

UnitOfWork

 public class UnitOfWork : IUnitOfWork { private DataModelContainer _context; public UnitOfWork() : this(new DataModelContainer()) { } public UnitOfWork(DataModelContainer context) { _context = context; } public DataModelContainer Context { get { return _context; } } public int Save() { return _context.SaveChanges(); } } 

During the first composition of the service with MEF, UnitOfWork will be created using the default constructor, which creates an instance of the context.

Notes

Some code fragments were omitted for readability.

Goal of achievement

Context lifetime is clearly a problem. Knowing that all calls to the same service method must have the same context.

How can we consider changing the architecture to avoid having a single context?

Feel free to ask questions! If necessary, I can attach a test project that emphasizes the problem.

+5
c # architecture wpf entity-framework
source share
2 answers

There is only one unit of work in your application, but that does not mean that it is designed to work. Instead, you need to create a unit of work every time you "work with a database." In your case, UnitOfWork should not be part of the MEF container, but you can create a UnitOfWorkFactory and enter it from the container. Services can then create UnitOfWork every time โ€œwork needs to be doneโ€ with the database:

 using (var unitOfWork = unitOfWorkFactory.Create()) { // Do work ... unitOfWork.Save(); } 

I modified UnitOfWork to implement IDisposable . This will allow you to get rid of the EF context, as well as possibly roll back the transaction if Save was not called. If you don't need additional transaction processing, you can even get rid of the UnitOfWork class because it just wraps the EF context, and instead you can directly use the EF context as part of the work.

This change will change the strength for changing the structure of the service and repositories, but you really need it because your problem is that you have one unit of work that exists throughout the life of the application.

+2
source share

Draw clearly defined use cases that will support your own lifespan. This can help prevent other resources from leaking (which is quite common when using WPF).

Consider the general algorithm:

  • Initialize the scope of time.
  • Use area:
    • Allocate views and other WPF resources, allocate a business layer, access to data (UoW, context, repo).
    • Download data from db and show it to the user.
    • Wait for user action (1).
    • Make some changes or load even more data from the database.
    • Update data presentation for user.
    • Go to (1) until the script completes.
  • Delete area, select resources.

The problem is that your scope is currently your application.

Now imagine that you are managing a scope. You select, display the view, get user input, save the changes, and then the entire tree of objects is placed immediately.

Obviously, you must be flexible with areas. Sometimes it is useful to use it at the presentation level (for example, "Edit Element"), sometimes it can be extended to several types (for example, a wizard). You can even support data-oriented areas (imagine that you open a project in Visual Studio, start your whole life to manage all the resources that should be available while the life of the project).

+1
source share

All Articles