WPF/EntityFramework 上下文生命周期 [英] WPF / EntityFramework Context Lifetime

查看:51
本文介绍了WPF/EntityFramework 上下文生命周期的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题

我们目前在 WPF 应用程序上遇到架构问题.它涉及 EntityFramework 上下文管理,它被实例化一次并在应用程序的整个生命周期中使用.所以我们最终遇到了缓存问题,实体在加载一次时不会更新.使用应用程序时,我们的实体已过时.

技术规范

  • Wpf 项目
  • .Net Framework 4 客户端配置文件
  • MEF(包含在 Framework 4.0 System.ComponentModel.Composition 中)
  • MVVM 设计模式
  • 多用户应用

架构

这是当前架构的架构.

服务层

  • 管理对业务规则的调用(业务层)
  • 在完成业务规则后保存上下文(通过 UnitOfWork)
  • 只能被 ViewModel 调用

业务层

  • 定义业务规则
  • 只能被服务层调用

存储层

  • 执行改变上下文数据的方法(插入、更新、删除)
  • 继承 ReadOnlyRepository
  • 只能被业务层调用

ReadOnlyRepository 层

  • 执行返回数据的方法(选择)
  • 可以在任何地方调用(ViewModel、Service 层、Business 层)

工作单元

  • 管理上下文实例化
  • 保存上下文
  • 上下文仅适用于存储库

代码

视图模型

[导出(typeof(OrderViewModel))][PartCreationPolicy(CreationPolicy.NonShared)]公共类 OrderViewModel : ViewModelBase{私有只读 IOrderManagementService _orderManagementService;私有只读 IOrderReadOnlyRepository _orderReadOnlyRepository;[导入构造函数]公共订单视图模型(IOrderManagementService orderManagementService,IOrderReadOnlyRepository orderReadOnlyRepository){_orderManagementService = 订单管理服务;_orderReadOnlyRepository = orderReadOnlyRepository;}}

服务层

公共类 OrderManagementService : IOrderManagementService{私有只读 IUnitOfWork _unitOfWork;私有只读 IOrderManagementBusiness _orderManagementBusiness;[导入构造函数]公共订单管理服务 (IUnitOfWork unitOfWork, IOrderManagementBusiness orderManagementBusiness){_unitOfWork=unitOfWork;_orderManagementBusiness = 订单管理业务;}}

业务层

公共类 OrderManagementBusiness : IOrderManagementBusiness{私有只读 IOrderReadOnlyRepository _orderReadOnlyRepository;[导入构造函数]公共订单管理业务(IOrderReadOnlyRepository orderReadOnlyRepository){_orderReadOnlyRepository = orderReadOnlyRepository;}}

ReadOnlyRepository 层

公共类 OrderReadOnlyRepository : ReadOnlyRepositoryBase, IOrderReadOnlyRepository{[导入构造函数]public OrderReadOnlyRepository (IUnitOfWork uow) : base(uow){}}

ReadOnlyRepositoryBase

公共抽象类 ReadOnlyRepositoryBase: IReadOnlyRepository其中 TEntity :类,IEntity其中 TContext : DbContext{受保护的只读 TContext _context;受保护的 ReadOnlyRepositoryBase(IUnitOfWork uow){_context = uow.Context;}受保护的 DbSet数据库集{得到 { return _context.Set();}公共虚拟 IEnumerableGetAll(System.Linq.Expressions.Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = ""){IQueryable查询 = DbSet.AsNoTracking();如果(过滤器!= null){查询 = query.Where(filter);}foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)){查询 = query.Include(includeProperty);}如果(orderBy != null){返回 orderBy(query).ToList();}返回 query.ToList();}公共虚拟 IQueryable全部(){返回 DbSet.AsNoTracking();}公共虚拟 IQueryableAllWhere(表达式<Func<TEntity, bool>>谓词){返回 DbSet.Where(predicate).AsNoTracking();}public virtual TEntity Get(Expression<Func<TEntity, bool>> predicate){返回 DbSet.Where(predicate).AsNoTracking().FirstOrDefault();}公共虚拟 TEntity GetById(int id){TEntity find = DbSet.Find(id);_context.Entry(find).State = System.Data.EntityState.Detached;返回 DbSet.Find(id);}

我们可以看到context是在构造函数中给repository的.Select 方法使用AsNoTracking()"方法来不缓存实体.这是一个临时解决方案,从长远来看显然不可行.

工作单位

公共类 UnitOfWork : IUnitOfWork{私有 DataModelContainer _context;公共工作单元():这个(新的数据模型容器()){}公共 UnitOfWork(DataModelContainer 上下文){_context = 上下文;}公共数据模型容器上下文{得到{返回_上下文;}}公共 int Save(){返回 _context.SaveChanges();}}

在第一次使用 MEF 组合服务时,UnitOfWork 将使用实例化上下文的默认构造函数进行实例化.

备注

为了便于阅读,省略了一些代码.

目标实现

上下文的生命周期显然是一个问题.知道同一服务方法内的所有调用必须共享相同的上下文.

我们如何考虑修改架构以避免具有单一上下文?

欢迎提问!如果需要,我可以附上一个突出问题的测试项目.

解决方案

在您的应用程序中,只有一个工作单元,但这并不是一个工作单元的目的.相反,您每次使用数据库"时都需要创建一个工作单元.在您的情况下, UnitOfWork 不应该是 MEF 容器的一部分,但您可以创建一个 UnitOfWorkFactory 并从容器中注入它.然后服务可以在每次必须完成工作"时创建一个 UnitOfWork与数据库:

using (var unitOfWork = unitOfWorkFactory.Create()) {//做工作 ...unitOfWork.Save();}

我修改了 UnitOfWork,因此它实现了 IDisposable.这将允许您处理 EF 上下文,并且如果 Save 未被调用,还可能回滚事务.如果您不需要额外的事务处理,您甚至可以去掉 UnitOfWork 类,因为它只是包装了 EF 上下文,而您可以直接将 EF 上下文用作工作单元.>

此更改将迫使您修改服务和存储库的结构方式,但您确实必须这样做,因为您的问题是您有一个在整个应用程序期间都存在的工作单元.

Issue

We are currently having a problem of architecture on a WPF application. It concerns EntityFramework context management, it’s instantiated once and used during the entire life of the application. So we end up with a cache issue, entities are not updated when they were loaded once. Our entities are obsolete when using the application.

Technical specification

  • Wpf project
  • .Net Framework 4 client Profile
  • MEF (Include in Framework 4.0 System.ComponentModel.Composition)
  • Design pattern MVVM
  • Multi users application

Architecture

This is a schema of the current architecture.

Service layer

  • Manage calls to business rules (business layer)
  • Save the context (through UnitOfWork) after business rules done
  • Can be called only by a ViewModel

Business layer

  • Define business rules
  • Can be called only by service layer

Repository layer

  • Execute methods which change context datas (insert, update , delete)
  • Inherit ReadOnlyRepository
  • Can be called only by business layer

ReadOnlyRepository layer

  • Execute method which return datas (select)
  • Can be called everywhere (ViewModel, Service layer, Business layer)

UnitOfWork

  • Manage context instanciation
  • Save context
  • Context available only for repositories

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 layer

public class OrderManagementService : IOrderManagementService
{
   private readonly IUnitOfWork _unitOfWork;
   private readonly IOrderManagementBusiness _orderManagementBusiness;

   [ImportingConstructor]
   public OrderManagementService (IUnitOfWork unitOfWork, IOrderManagementBusiness orderManagementBusiness)
   {
      _unitOfWork= unitOfWork;
      _orderManagementBusiness = orderManagementBusiness;
   }
}

Business layer

public class OrderManagementBusiness : IOrderManagementBusiness
{
   private readonly IOrderReadOnlyRepository _orderReadOnlyRepository;

   [ImportingConstructor]
   public OrderManagementBusiness (IOrderReadOnlyRepository orderReadOnlyRepository)
   {
      _orderReadOnlyRepository = orderReadOnlyRepository;
   }
}

ReadOnlyRepository layer

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 can see that the context is given to the repository in the constructor. Select methods use the "AsNoTracking ()" method to not cache entities. It's a temporary solution which is obviously not viable in long term.

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 a service with MEF, UnitOfWork will be instantiated with the default constructor which instantiate the context.

Remarks

Some pieces of code have been omitted for readability.

Goal to achieve

The lifetime of the context is clearly an issue. Knowing that all calls within the same service method must share the same context.

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

Feel free to ask questions ! If needed, I can attach a test project which highlight the issue.

解决方案

In your application there is only single unit of work but that is not the purpose of a unit a work. Instead, you need to create a unit of work each time "you work with the database". In your case the UnitOfWork should not be part of the MEF container but you can create a UnitOfWorkFactory and inject it from the container. Then the services can create a UnitOfWork each time "work has to be done" with the database:

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

  unitOfWork.Save();
}

I have modified UnitOfWork so it implements IDisposable. This will allow you to dispose the EF context and also perhaps rollback a transaction if Save was not called. If you have no need for the extra transaction handling you can even get rid of the UnitOfWork class because it simply wraps the EF context and instead you can used the EF context as a unit of work directly.

This change will force you to modify how the service and the repositories are structured but you really have to because your issue is that you have a single unit of work that exists for the entire duration of the application.

这篇关于WPF/EntityFramework 上下文生命周期的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆