MVC,EF - DataContext单例实例在Unity中的每个Web请求 [英] MVC, EF - DataContext singleton instance Per-Web-Request in Unity

查看:611
本文介绍了MVC,EF - DataContext单例实例在Unity中的每个Web请求的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个MVC 3的Web应用程序,我正在使用Entity Framework进行数据访问。此外,我简单地使用了存储库模式,所有与Product相关的内容都在ProductRepository中处理,所有与User相关的内容都在UserRepository中处理。



因此,我正在使用UNITY容器,将DataContext的单例实例注入每个存储库。在Google上快速搜索,每个人都建议您不要使用DataContext的单例实例,因为它可能会在以后给您一些内存泄漏。



所以,灵感来自于这篇文章,为每个Web请求创建一个DataContext的单例实例是答案(如果我错了,请纠正我的错误!)



http://blogs.microsoft.co.il/blogs/gilf/archive/2010/05/18/how-to-manage-objectcontext-per -request-in-asp-net.aspx



但是,UNITY不支持每个网络请求生命周期管理器。但是,可以实现您自己的定制生命周期管理器,为您处理此事。其实这个在这篇文章中讨论过:



Unity中的单一呼叫上下文(Web请求)



问题是,我现在已经实现了自定义生命周期管理器在上面的帖子中描述,但我不确定是否这样做。我也想知道在提供的解决方案中处理datacontext实例的位置?我错过了什么吗?



有没有更好的方法来解决我的问题?



谢谢!



**添加了有关我的实现的信息**



以下是我的Global.asax的片段,控制器和存储库。这清楚地描绘了我的实现。



Global.asax

  var container = new UnityContainer(); 
container
.RegisterType< ProductsRepository>(new ContainerControlledLifetimeManager())
.RegisterType< CategoryRepository>(new ContainerControlledLifetimeManager())
.RegisterType&MyEntities>(new PerResolveLifetimeManager() dbConnectionString)

控制器

  private ProductsRepository _productsRepository; 
private CategoryRepository _categoryRepository;

public ProductsController(ProductsRepository productsRepository,CategoryRepository categoryRepository)
{
_productsRepository = productsRepository;
_categoryRepository = categoryRepository;
}

public ActionResult Index()
{
ProductCategory category = _categoryRepository.GetProductCategory(categoryId);



}

protected override void Dispose(bool disposal)
{
base.Dispose(disposal);
_productsRepository.Dispose();
_categoryRepository.Dispose();
}

产品资料库

  public class ProductsRepository:IDisposable 
{

private MyEntities _db;

public ProductsRepository(MyEntities db)
{
_db = db;
}

public Product GetProduct(Guid productId)
{
return _db.Product.Where(x => x.ID == productId).FirstOrDefault );
}

public void Dispose()
{
this._db.Dispose();
}

控制器工厂

  public class UnityControllerFactory:DefaultControllerFactory 
{
IUnityContainer _container;

public UnityControllerFactory(IUnityContainer container)
{
_container = container;


protected override IController GetControllerInstance(RequestContext requestContext,Type controllerType)
{
if(controllerType == null)
{
throw新的HttpException(404,String.Format(控制器的路径{0}找不到+
或它不实现IController。,
requestContext.HttpContext.Request.Path ));
}

return _container.Resolve(controllerType)as IController;
}

}

添加信息
我会发布我遇到的其他链接,关于相关问题和解决方案建议:


  1. http://cgeers.wordpress.com/2009/02/21 / entity-framework-objectcontext /#objectcontext

  2. http://dotnetslackers.com/articles/ado_net/Managing-Entity-Framework-ObjectContext-lifespan-and- scope-in​​-n-layered-ASP-NET-applications.aspx

  3. 将linq添加到sql datacontext到业务层中的httpcontext

  4. http://weblogs.asp.net/shijuvarghese/archive/2008/10/24/asp-net-mvc-tip-dependency-injection-with-unity-application-block.aspx

  5. http:// msdn .microsoft.com / en-us / library / bb738470.aspx


解决方案

不共享上下文,并且每个请求使用一个上下文。您还可以检查该帖子中的链接问题,以查看共享上下文导致的所有问题。



现在关于Unity。想法 PerCallContextLifetimeManager 的作品,但我认为提供的实现将不会适用于多个对象。您应该直接使用 PerHttpRequestLifetimeManager

  public class PerHttpRequestLifetime:LifetimeManager 
{
//这是非常重要的部分,为什么我相信提到
// PerCallContext实现是错误的。
private readonly Guid _key = Guid.NewGuid();

public override object GetValue()
{
return HttpContext.Current.Items [_key];
}

public override void SetValue(object newValue)
{
HttpContext.Current.Items [_key] = newValue;
}

public override void RemoveValue()
{
var obj = GetValue();
HttpContext.Current.Items.Remove(obj);
}
}

请注意,Unity不会为您处理上下文。还要注意,默认的 UnityContainer 实现将永远不会调用 RemoveValue 方法。



如果您的实现解决了单个 Resolve 调用中的所有存储库(例如,如果您的控制器在构造函数中接收到存储库的实例你是解决控制器)你不需要这个终身经理。在这种情况下,使用内置(Unity 2.0) PerResolveLifetimeManager



编辑



我提供的 UnityContainer 。您正在使用 ContainerControllerLifetimeManager 注册两个存储库。这个终身经理是指每个容器生命周期的单例实例。这意味着这两个存储库将仅被实例化一次,实例将被存储并重新用于后续调用。因为你分配给 MyEntities 的生命周期并不重要。它被注入到只会调用一次的存储库的构造函数中。这两个存储库将仍然使用在构建期间创建的 MyEntities 的单个实例=他们将在 AppDomain 。这是您可以实现的最糟糕的情况。



以这种方式重写您的配置:

  var container = new UnityContainer(); 
容器
.RegisterType< ProductsRepository>()
.RegisterType< CategoryRepository>()
.RegisterType&MyEntities>(new PerResolveLifetimeManager(),dbConnectionString);

为什么这么够?您正在解析依赖代理的控制器,但不需要更多的存储库实例,因此您可以使用默认的 TransientLifetimeManager ,这将为每个调用创建新的实例。因为调用了该库构造函数,而且必须解析 MyEntities 实例。但是,您知道多个存储库可能需要此实例,因此您将使用 PerResolveLifetimeManager =>进行设置,每个解析控制器将仅生成一个 MyEntities


I have a MVC 3 web application, where I am using the Entity Framework for the data access. Furthermore, I have made a simple use of the repository pattern, where e.g. all Product related stuff is handled in the "ProductRepository" and all User related stuff is handled in the "UserRepository".

Thus, I am using the UNITY container, to make a singleton instance of the DataContext, which I inject into each of the repositories. A quick search on Google, and everyone recommends you to NOT use a singleton instance of the DataContext, as it might give you some memory leaks in the future.

So, inspired by this post, making a singleton instance of the DataContext for each web request is the answer (please correct me if I am wrong!)

http://blogs.microsoft.co.il/blogs/gilf/archive/2010/05/18/how-to-manage-objectcontext-per-request-in-asp-net.aspx

However, UNITY does not support the "Per-web-request" lifetime manager. But, it is possible to implement your own custom lifetime manager, which handles this for you. Actually, this is discussed in this post :

Singleton Per Call Context (Web Request) in Unity

The question is, I have now implemented the custom lifetime manager as described in the above post, but I am unsure if this is the way to do it. I am also wondering about where the datacontext instance is disposed in the provided solution? Am I missing out something?

Is there actually a better way of solving my "issue"?

Thanks!

** Added information about my implementation **

The following is snippets from my Global.asax, Controller and Repository. This gives a clear picture of my implementation.

Global.asax

  var container = new UnityContainer();
            container
                .RegisterType<ProductsRepository>(new ContainerControlledLifetimeManager())
                .RegisterType<CategoryRepository>(new ContainerControlledLifetimeManager())
                .RegisterType<MyEntities>(new PerResolveLifetimeManager(), dbConnectionString)

Controller

private ProductsRepository _productsRepository;
private CategoryRepository _categoryRepository;

public ProductsController(ProductsRepository productsRepository, CategoryRepository categoryRepository)
{
   _productsRepository = productsRepository;
   _categoryRepository = categoryRepository;
}

public ActionResult Index()
{
   ProductCategory category = _categoryRepository.GetProductCategory(categoryId);
   . 
   . 
   . 
}

protected override void Dispose(bool disposing)
{
    base.Dispose(disposing);
    _productsRepository.Dispose();
    _categoryRepository.Dispose();
}

Product Repository

public class ProductsRepository : IDisposable
{

private MyEntities _db;

public ProductsRepository(MyEntities db)
{
    _db = db;
}

public Product GetProduct(Guid productId)
{
    return _db.Product.Where(x => x.ID == productId).FirstOrDefault();
}

public void Dispose()
{
    this._db.Dispose();
}

Controller Factory

public class UnityControllerFactory : DefaultControllerFactory
{
    IUnityContainer _container;

    public UnityControllerFactory(IUnityContainer container)
    {
        _container = container;
    }

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            throw new HttpException(404, String.Format("The controller for path '{0}' could not be found" +
                "or it does not implement IController.",
                 requestContext.HttpContext.Request.Path));
        }

        return _container.Resolve(controllerType) as IController;
    }

}

Addition information Hi, I will post additional links that I come across, concerning the related issue and solution suggestions:

  1. http://cgeers.wordpress.com/2009/02/21/entity-framework-objectcontext/#objectcontext
  2. http://dotnetslackers.com/articles/ado_net/Managing-Entity-Framework-ObjectContext-lifespan-and-scope-in-n-layered-ASP-NET-applications.aspx
  3. attaching linq to sql datacontext to httpcontext in business layer
  4. http://weblogs.asp.net/shijuvarghese/archive/2008/10/24/asp-net-mvc-tip-dependency-injection-with-unity-application-block.aspx
  5. http://msdn.microsoft.com/en-us/library/bb738470.aspx

解决方案

Yes do not share context and use one context per request. You can also check linked questions in that post to see all problems which a shared context caused.

Now about Unity. Idea of PerCallContextLifetimeManager works but I think provided implementation will not work for more than one object. You should use PerHttpRequestLifetimeManager directly:

public class PerHttpRequestLifetime : LifetimeManager
{
    // This is very important part and the reason why I believe mentioned
    // PerCallContext implementation is wrong.
    private readonly Guid _key = Guid.NewGuid();

    public override object GetValue()
    {
        return HttpContext.Current.Items[_key];
    }

    public override void SetValue(object newValue)
    {
        HttpContext.Current.Items[_key] = newValue;
    }

    public override void RemoveValue()
    {
        var obj = GetValue();
        HttpContext.Current.Items.Remove(obj);
    }
}

Be aware that Unity will not dispose context for you. Also be aware that default UnityContainer implementation will never call RemoveValue method.

If your implementation resolves all repositories in single Resolve call (for example if your controllers receives instances of repositories in constructor and you are resolving controllers) you don't need this lifetime manager. In such case use build-in (Unity 2.0) PerResolveLifetimeManager.

Edit:

I see pretty big problem in your provided configuration of UnityContainer. You are registering both repositories with ContainerControllerLifetimeManager. This lifetime manager means Singleton instance per container lifetime. It means that both repositories will be instantiated only once and instance will be stored and reused for subsequent calls. Because of that it doesn't matter what lifetime did you assign to MyEntities. It is injected to repositories' constructors which will be called only once. Both repositories will use still that single instance of MyEntities created during their construction = they will use single instance for whole lifetime of your AppDomain. That is the worst scenario you can achieve.

Rewrite your configuration this way:

var container = new UnityContainer();
container
  .RegisterType<ProductsRepository>()
  .RegisterType<CategoryRepository>()
  .RegisterType<MyEntities>(new PerResolveLifetimeManager(), dbConnectionString);

Why this is enough? You are resolving controller which is dependent on repsitories but no repository instance is needed more then once so you can use default TransientLifetimeManager which will create new instance for each call. Because of that repository constructor is called and MyEntities instance must be resolved. But you know that multiple repositories can need this instance so you will set it with PerResolveLifetimeManager => each resolving of controller will produce only one instance of MyEntities.

这篇关于MVC,EF - DataContext单例实例在Unity中的每个Web请求的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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