WPF / EntityFramework 上下文生命周期

Posted

技术标签:

【中文标题】WPF / EntityFramework 上下文生命周期【英文标题】:WPF / EntityFramework Context Lifetime 【发布时间】:2014-11-28 15:59:19 【问题描述】:

问题

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

技术规格

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

架构

这是当前架构的架构。

服务层

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

业务层

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

存储层

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

ReadOnlyRepository 层

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

工作单位

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

代码

视图模型

[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;
   

服务层

public class OrderManagementService : IOrderManagementService

   private readonly IUnitOfWork _unitOfWork;
   private readonly IOrderManagementBusiness _orderManagementBusiness;

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

业务层

public class OrderManagementBusiness : IOrderManagementBusiness

   private readonly IOrderReadOnlyRepository _orderReadOnlyRepository;

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

ReadOnlyRepository 层

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);
   

我们可以看到在构造函数中给存储库提供了上下文。 Select 方法使用“AsNoTracking()”方法不缓存实体。这是一个临时解决方案,从长远来看显然不可行。

工作单位

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();
   
   

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

备注

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

要实现的目标

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

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

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

【问题讨论】:

在不维护上下文的情况下如何实现工作单元的目的?一件事是,当您在 MEF 中创建依赖于 IDisposable 的 Dispose 方法的任何 onbject 时,它永远不会被释放,直到其容器被释放 (mef.codeplex.com/wikipage?title=Parts%20Lifetime)。 【参考方案1】:

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

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

  unitOfWork.Save();

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

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

【讨论】:

抱歉回复晚了。我认为你让我走对了路。我从你的代码开始,我设法解决我的问题。每个服务方法都通过一个 unitofworkfactory 实例化该 unitofwork。像这样我的上下文范围是服务方法【参考方案2】:

概述清楚区分的用例,这些用例将保持自己的生命周期范围。这也有助于防止其他资源泄漏(在使用 WPF 时非常常见)。

考虑通用算法:

初始化生命周期范围。 使用范围: 分配视图和其他 WPF 资源、分配业务层、数据访问(UoW、上下文、repo)。 从 db 加载数据并将其显示给用户。 等待用户操作 (1)。 进行一些更改或从数据库加载更多数据。 为用户更新数据表示。 转到 (1) 直到场景完成。 处置范围,取消分配资源。

问题是您的范围当前是您的应用程序

现在假设您在视图级别管理范围。您分配、显示视图、获取用户输入、保存更改,然后一次性处理整个对象树。

显然,您应该灵活使用范围。有时在视图级别使用它可能很有用(例如“编辑项目”),有时它可能会分布在多个视图中(例如向导)。您甚至可以维护数据驱动的范围(假设您在 Visual Studio 中打开一个项目;开始生命周期范围以管理所有资源,这些资源应该在项目“存在”时可用)。

【讨论】:

以上是关于WPF / EntityFramework 上下文生命周期的主要内容,如果未能解决你的问题,请参考以下文章

Prism+MaterialDesign+EntityFramework Core+Postgresql WPF开发总结 之 中级篇

Prism+MaterialDesign+EntityFramework Core+Postgresql WPF开发总结 之 基础篇

.NET 5 WPF EntityFramework 和 SQL 精简版

EntityFramework 6.x多个上下文迁移实现分布式事务

元素“entityFramework”具有无效的子元素“提供者”。预期的可能元素列表:“上下文”

MVC5 Identity + EntityFramework 中的多个上下文