WPF / EntityFramework上下文生命周期

6

问题

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

技术规范

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

架构

这是当前架构的示意图。

architecture schema

服务层

  • 管理对业务规则(业务层)的调用
  • 在业务规则执行完成后保存上下文(通过UnitOfWork)
  • 仅可由ViewModel调用

业务层

  • 定义业务规则
  • 仅可由服务层调用

仓储层

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

只读仓储层

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

工作单元

  • 管理上下文实例化
  • 保存上下文
  • 仅对仓库开放上下文

代码

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

服务层

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

只读存储库层

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

我们可以看到,上下文被传递给仓储在构造函数中。选择方法使用"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方法的对象时,它永远不会被处理,直到其容器被处理。(http://mef.codeplex.com/wikipage?title=Parts%20Lifetime) - Rajnikant
2个回答

3

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

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

  unitOfWork.Save();
}

我已经修改了 UnitOfWork,使其实现了 IDisposable。这将允许您处理 EF 上下文的释放,以及如果没有调用 Save,也许回滚事务。如果您不需要额外的事务处理,甚至可以摆脱 UnitOfWork 类,因为它只是包装了 EF 上下文,您可以直接使用 EF 上下文作为工作单元。
这个变化将迫使您修改服务和存储库的结构,但您确实需要这样做,因为您的问题在于整个应用程序存在一个单一的工作单元。

抱歉回复晚了。我认为你让我找对了方向。我从你的代码开始,成功解决了我的问题。每个服务方法都通过unitofworkfactory实例化unitofwork。这样一来,我的上下文范围就是服务方法。 - toast

2

清晰地勾画出区分使用情况的用例,这有助于维护自身生命周期范围。这也可以帮助防止其他资源泄漏(当使用WPF时很常见)。

考虑通用算法:

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

问题在于你的范围目前是你的应用程序

现在想象一下,你在视图级别管理作用域。您分配、显示视图,获取用户输入,保存更改,然后整个对象树立即被释放。

显然,你应该对作用域灵活使用。有时在视图层面使用它可能很有用(例如,“编辑项目”),有时它可能跨越多个视图(例如向导)。您甚至可以维护数据驱动的范围(想象一下,您在Visual Studio中打开一个项目;开始生命周期范围来管理所有应该在项目“存活”期间可用的资源)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接