在MVC 4应用程序中,使用Unity和Entity Framework的最佳实践是什么?

4
我在使用Unity实现依赖注入和Automapper自动映射对象到DTO的MVC 4应用程序中遇到了Entityframework的问题。我一直在处理各种问题,有时候EF会返回旧数据,因此我认为我的设计不够好。
我拥有以下内容:
在我的Application_Start中配置Unity:
var UnityContainer = UnityConfig.GetConfiguredContainer();
Mapper.Initialize(cfg => cfg.ConstructServicesUsing(type => UnityContainer.Resolve(type)));
UnityContainer.Resolve<AutoMapperConfig>().MapAutoMapper();
...

在UnityConfig.RegisterTypes中:
container.RegisterType<IMyContext, MyContext>(new ContainerControlledLifetimeManager())
...

我的代码库使用构造函数依赖注入:

public class MSSQLTenantRepository : IDalTenantRepository
{
   private readonly IMyContext _Db;
   public MSSQLTenantRepository(IMyContext db)
   {
      Db = db;
   }
...

我的控制器也使用构造函数依赖注入:

public class TenantController : Controller
{
   private readonly ITenantRepository _TenantRepository;
   public TenantController(ITenantRepository tenantRepository,
   {
      _TenantRepository = tenantRepository;
   }
...

Automapper配置:

public class AutoMapperConfig
{
    private readonly ITenantRepository _TenantRepository;
    public AutoMapperConfig(ITenantRepository tenantRepository)
    {
        _TenantRepository = tenantRepository;
    }
...

问题:
有时我得到旧数据,来自第一个请求。
当我手动更新 SQL 服务器中的数据时,EF 返回的对象不反映更改。
当我尝试不同的选项时,也会出现关于多个上下文的错误(由于Automapper)。
我的问题:
1. 使用 Unity、MVC4、EF6、存储库和 Automapper 的最佳实践是什么? 2. 在哪里放置代码(例如在 global.asax.c 或 UnityWebApiActivator 的 UnitiConfig.cs 中)? 3. 我需要显式处理dbcontext吗?如果需要的话,在哪里处理?
这方面已经讲了很多东西,但没有一篇文章涵盖所有内容。

看起来你可能正在共享一个“非线程安全”的上下文……除非你想完全管理上下文,否则你应该使用工作单元方法,通常在Using语句中。 - Paul Zahra
当我的应用程序具有PartialViews时,这是否有效?我担心控制器在另一个控制器需要它时处置上下文,或者两个上下文跟踪相同的实体。我已查看http://www.asp.net/mvc/tutorials/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application以进行可能的实现。 - RHAD
2个回答

6
 container.RegisterType<IMyContext, MyContext>(
     new ContainerControlledLifetimeManager())

这样做非常糟糕,因为它会使上下文成为单例模式。这种方法不仅会导致多个请求共享相同的上下文并且存在并发问题,而且此类共享上下文的内存消耗会无限增长。
相反,您希望每个请求都有一个“每个请求”生命周期,在每个单独的请求中建立一个新的上下文: http://www.wiktorzychla.com/2013/03/unity-and-http-per-request-lifetime.html
public class PerRequestLifetimeManager : LifetimeManager
{
  private readonly object key = new object();

  public override object GetValue()
  {
    if (HttpContext.Current != null && 
        HttpContext.Current.Items.Contains(key))
        return HttpContext.Current.Items[key];
    else
        return null;
  } 

  public override void RemoveValue()
  {
    if (HttpContext.Current != null)
        HttpContext.Current.Items.Remove(key);
  }

  public override void SetValue(object newValue)
  {
    if (HttpContext.Current != null)
        HttpContext.Current.Items[key] = newValue;
  }
}

并且

container.RegisterType<IMyContext, MyContext>(new PerRequestLifetimeManager())

我不确定您的AutoMapperConfig类是做什么的,以及为什么需要在其中注入一个存储库。这可能涉及到另一个生命周期问题,但我需要对此进行澄清。


1
当实现PerRequestLifetimeManager时(顺便说一句,这是Unity引导程序的一部分,现在可以在http://www.nuget.org/packages/Unity.Mvc/上找到),我在保存实体时遇到了错误:“无法定义两个对象之间的关系,因为它们附加到不同的ObjectContext对象”。Automapper添加存储库的原因是尝试解决不同ObjectContext问题。 - RHAD
那是另外一个故事了。现在你的上下文生命周期管理已经正确工作,但是你却混淆了来自不同上下文(不同请求)的实体。我猜测 AutoMapperConfig,无论它是什么,仍然错误地处理了生命周期。看看我在这里的回答:http://stackoverflow.com/questions/21312428/how-to-identify-dbcontext-in-entity-framework/21312658#comment32122647_21312658 - Wiktor Zychla
也许Unity的LifetimeManager已经更新了,但我无法使用上面的PerRequest类。重写方法没有找到,也没有定义GetValue和OnCreateLifetimeManager方法。 - fix
我有一个愚蠢的问题:我可以使用PerRequestLifetimeManager(),并将DbContext注入到调用数据库的长时间运行的后台线程中吗?请记住,这个长时间运行的线程在我的应用程序开始时被生成,并且只要我的应用程序存在,它就会一直存在。 - Vin Shahrdar

3

在Wiktor的帮助下,我找到了答案。

首先:我只需要使用PerRequestLifeTimeManager(正如Wiktor Zychla所说,非常感谢),它在Unity for MVC启动程序中可用。

其次:这行代码:

UnityContainer.Resolve<AutoMapperConfig>().MapAutoMapper();

必须在Application_BeginRequest(Globas.asax.cs)中。 我将其放在Application_Start中,因此仅在启动时解决了一次。 一个传入的请求创建了一个新的上下文,所以它与Automapper使用的上下文不同。 将其放在BeginRequest中,每个请求都会执行解决,并且使用与存储库相同的上下文。


你最终是否仍然使用了EF dbcontext的'Using'语句?还是因为'PerRequestLifeTimeManager'而不需要它? - duyn9uyen
我没有使用using语句。据我所知,上下文在请求完全处理后被销毁。 - RHAD

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