MVC,EF - Unity中的DataContext单例实例Per-Web-Request

时间:2011-03-03 22:44:56

标签: asp.net-mvc-3 entity-framework unity-container ioc-container

我有一个MVC 3 Web应用程序,我使用Entity Framework进行数据访问。此外,我简单地使用了存储库模式,例如,所有与产品相关的东西都在“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不支持“Per-web-request”终身经理。但是,可以实现自己的自定义生命周期管理器,它可以为您处理此问题。实际上,这篇文章对此进行了讨论:

Singleton Per Call Context (Web Request) in Unity

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

实际上有更好的解决方法吗?

谢谢!

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

以下是我的Global.asax,Controller和Repository的片段。这清楚地说明了我的实施情况。

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 disposing)
{
    base.Dispose(disposing);
    _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 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;
    }

}

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

  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

9 个答案:

答案 0 :(得分:38)

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

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

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

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

如果您的实现在单Resolve次调用中解析所有存储库(例如,如果您的控制器在构造函数中接收存储库实例并且您正在解析控制器),则不需要此生命周期管理器。在这种情况下使用内置(Unity 2.0)PerResolveLifetimeManager

修改

我在您提供的UnityContainer配置中发现了相当大的问题。您正在使用ContainerControllerLifetimeManager注册这两个存储库。此生命周期管理器表示每个容器生存期的Singleton实例这意味着两个存储库只会被实例化一次,实例将被存储并重新用于后续调用。因此,您分配给MyEntities的生命周期并不重要。它被注入到存储库的构造函数中,这些构造函数只会被调用一次。两个存储库仍将使用在构造期间创建的MyEntities的单个实例=他们将在AppDomain的整个生命周期内使用单个实例。这是你可以实现的最糟糕的情况。

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

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

为什么这就足够了?您正在解析依赖于repsitories的控制器,但是不需要存储库实例,因此您可以使用默认的TransientLifetimeManager,这将为每个调用创建新实例。由于该存储库构造函数被调用,因此必须解析MyEntities实例。但是您知道多个存储库可能需要此实例,因此您将使用PerResolveLifetimeManager =&gt;设置它。控制器的每个解析只会产生一个MyEntities的实例。

答案 1 :(得分:8)

从Unity 3开始,每个http请求都有一个内置的生命周期管理器。

<强> PerRequestLifetimeManager

  

LifetimeManager,它保留在单个HTTP请求的生命周期内为其提供的实例。通过此生命周期管理器,您可以创建在HTTP请求范围内行为类似于单例的已注册类型的实例。有关重要的使用信息,请参阅备注。

MSDN备注

  

虽然PerRequestLifetimeManager生命周期管理器可以正常工作,并且可以帮助处理HTTP请求范围内的有状态或线程不安全的依赖项,但在可以避免的情况下使用它通常不是一个好主意,因为它可能经常导致不良做法或在最终用户的应用程序代码中使用不当时很难发现错误。

     

建议您注册的依赖项是无状态的,如果需要在HTTP请求的生命周期内共享多个对象之间的公共状态,那么您可以拥有一个无状态服务,该服务使用以下方式显式存储和检索此状态。 Current对象的项集合。

评论说即使你被迫使用每个服务(门面服务)的单个上下文,你应该让你的服务调用无状态。

Unity 3适用于.NET 4.5。

答案 2 :(得分:5)

我认为HttpContextLifetimeManager上{{1}}显示的示例代码应该符合您的需求。

答案 3 :(得分:2)

我不想不必要地劝阻你,并且无论如何都要试验,但是如果你继续使用DataContext的单例实例,请确保你钉它。

它似乎可以在您的开发环境中正常工作,但可能无法正确关闭连接。如果没有生产环境的负载,这将很难看到。在高负载的生产环境中,未连接的连接将导致巨大的内存泄漏,然后高CPU尝试分配新内存。

您是否考虑过每个请求模式的连接获得了什么?在请求中打开/关闭连接一次超过3-4次可获得多少性能?值得麻烦吗?这也使得延迟加载失败(在您的视图中读取数据库查询)更容易犯罪。

很抱歉,如果这令人沮丧。如果你真的看到了好处,那就去吧。我只是警告你,如果你弄错了,它会非常严重地适得其反,所以请注意。类似entity profiler之类的东西对于正确实现它是非常宝贵的 - 它会告诉你打开和关闭的连接数量 - 以及其他非常有用的东西。

答案 4 :(得分:2)

我几次见过问答。它已过时了。 Unity.MVC3将生命周期管理器作为HierarchicalLifetimeManager。

    container.RegisterType<OwnDbContext>(
                "",
                new HierarchicalLifetimeManager(),
                new InjectionConstructor(connectionString)
                );

并且效果很好。

答案 5 :(得分:1)

我建议像这样解决它: http://forums.asp.net/t/1644386.aspx/1

祝你好运

答案 6 :(得分:1)

我通过使用Castle.DynamicProxy解决了这个问题。我需要将某些依赖项注入“按需”,这意味着它们需要在使用时解决,而不是在“Depender”构建时解决。

为此,我按如下方式配置容器:

 private void UnityRegister(IUnityContainer container)
 {
    container.RegisterType<HttpContextBase>(new OnDemandInjectionFactory<HttpContextBase>(c => new HttpContextWrapper(HttpContext.Current)));
    container.RegisterType<HttpRequestBase>(new OnDemandInjectionFactory<HttpRequestBase>(c => new HttpRequestWrapper(HttpContext.Current.Request)));
    container.RegisterType<HttpSessionStateBase>(new OnDemandInjectionFactory<HttpSessionStateBase>(c => new HttpSessionStateWrapper(HttpContext.Current.Session)));
    container.RegisterType<HttpServerUtilityBase>(new OnDemandInjectionFactory<HttpServerUtilityBase>(c => new HttpServerUtilityWrapper(HttpContext.Current.Server)));
 }

我的想法是提供一种方法来“按需”检索实例。只要使用实例的任何方法,就会调用lambda。 Dependent对象实际上是对代理对象的引用,而不是对象本身。

<强> OnDemandInjectionFactory:

internal class OnDemandInjectionFactory<T> : InjectionFactory
{
    public OnDemandInjectionFactory(Func<IUnityContainer, T> proxiedObjectFactory) : base((container, type, name) => FactoryFunction(container, type, name, proxiedObjectFactory))
    {
    }

    private static object FactoryFunction(IUnityContainer container, Type type, string name, Func<IUnityContainer, T> proxiedObjectFactory)
    {
        var interceptor = new OnDemandInterceptor<T>(container, proxiedObjectFactory);
        var proxyGenerator = new ProxyGenerator();
        var proxy = proxyGenerator.CreateClassProxy(type, interceptor);
        return proxy;
    }
}

<强> OnDemandInterceptor:

internal class OnDemandInterceptor<T> : IInterceptor
{
    private readonly Func<IUnityContainer, T> _proxiedInstanceFactory;
    private readonly IUnityContainer _container;

    public OnDemandInterceptor(IUnityContainer container, Func<IUnityContainer, T> proxiedInstanceFactory)
    {
        _proxiedInstanceFactory = proxiedInstanceFactory;
        _container = container;
    }

    public void Intercept(IInvocation invocation)
    {
        var proxiedInstance = _proxiedInstanceFactory.Invoke(_container);

        var types = invocation.Arguments.Select(arg => arg.GetType()).ToArray();

        var method = typeof(T).GetMethod(invocation.Method.Name, types);

        invocation.ReturnValue = method.Invoke(proxiedInstance, invocation.Arguments);
    }
}

答案 7 :(得分:1)

在Unity3中,如果你想使用

PerRequestLifetimeManager

您需要注册UnityPerRequestHttpModule

我是通过使用WebActivatorEx来完成的,代码如下:

using System.Linq;
using System.Web.Mvc;
using Microsoft.Practices.Unity.Mvc;
using MyNamespace;

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(UnityWebActivator), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(UnityWebActivator), "Shutdown")]

namespace MyNamespace
{
    /// <summary>Provides the bootstrapping for integrating Unity with ASP.NET MVC.</summary>
    public static class UnityWebActivator
    {
        /// <summary>Integrates Unity when the application starts.</summary>
        public static void Start() 
        {
            var container = UnityConfig.GetConfiguredContainer();

            FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
            FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container));

            DependencyResolver.SetResolver(new UnityDependencyResolver(container));

            // TODO: Uncomment if you want to use PerRequestLifetimeManager
            Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
        }

        /// <summary>Disposes the Unity container when the application is shut down.</summary>
        public static void Shutdown()
        {
            var container = UnityConfig.GetConfiguredContainer();
            container.Dispose();
        }
    }
}

答案 8 :(得分:0)

PerRequestLifetimeManagerUnityPerRequestHttpModule类位于Unity.Mvc package中,它依赖于ASP.NET MVC。如果您不想拥有该依赖项(例如,您使用的是Web API),则必须将它们复制粘贴到您的应用程序中。

如果你这样做,不要忘记注册HttpModule。

Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));

编辑: 在CodePlex关闭之前,我会在这里包括这些类:

// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Web;
using Microsoft.Practices.Unity.Mvc.Properties;
using Microsoft.Practices.Unity.Utility;

namespace Microsoft.Practices.Unity.Mvc
{
    /// <summary>
    /// Implementation of the <see cref="IHttpModule"/> interface that provides support for using the
    /// <see cref="PerRequestLifetimeManager"/> lifetime manager, and enables it to
    /// dispose the instances after the HTTP request ends.
    /// </summary>
    public class UnityPerRequestHttpModule : IHttpModule
    {
        private static readonly object ModuleKey = new object();

        internal static object GetValue(object lifetimeManagerKey)
        {
            var dict = GetDictionary(HttpContext.Current);

            if (dict != null)
            {
                object obj = null;

                if (dict.TryGetValue(lifetimeManagerKey, out obj))
                {
                    return obj;
                }
            }

            return null;
        }

        internal static void SetValue(object lifetimeManagerKey, object value)
        {
            var dict = GetDictionary(HttpContext.Current);

            if (dict == null)
            {
                dict = new Dictionary<object, object>();

                HttpContext.Current.Items[ModuleKey] = dict;
            }

            dict[lifetimeManagerKey] = value;
        }

        /// <summary>
        /// Disposes the resources used by this module.
        /// </summary>
        public void Dispose()
        {
        }

        /// <summary>
        /// Initializes a module and prepares it to handle requests.
        /// </summary>
        /// <param name="context">An <see cref="HttpApplication"/> that provides access to the methods, properties,
        /// and events common to all application objects within an ASP.NET application.</param>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Validated with Guard class")]
        public void Init(HttpApplication context)
        {
            Guard.ArgumentNotNull(context, "context");
            context.EndRequest += OnEndRequest;
        }

        private void OnEndRequest(object sender, EventArgs e)
        {
            var app = (HttpApplication)sender;

            var dict = GetDictionary(app.Context);

            if (dict != null)
            {
                foreach (var disposable in dict.Values.OfType<IDisposable>())
                {
                    disposable.Dispose();
                }
            }
        }

        private static Dictionary<object, object> GetDictionary(HttpContext context)
        {
            if (context == null)
            {
                throw new InvalidOperationException(Resources.ErrorHttpContextNotAvailable);
            }

            var dict = (Dictionary<object, object>)context.Items[ModuleKey];

            return dict;
        }
    }
}
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.

using System;
using Microsoft.Practices.Unity.Mvc;

namespace Microsoft.Practices.Unity
{
    /// <summary>
    /// A <see cref="LifetimeManager"/> that holds onto the instance given to it during
    /// the lifetime of a single HTTP request.
    /// This lifetime manager enables you to create instances of registered types that behave like
    /// singletons within the scope of an HTTP request.
    /// See remarks for important usage information.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Although the <see cref="PerRequestLifetimeManager"/> lifetime manager works correctly and can help
    /// in working with stateful or thread-unsafe dependencies within the scope of an HTTP request, it is
    /// generally not a good idea to use it when it can be avoided, as it can often lead to bad practices or
    /// hard to find bugs in the end-user's application code when used incorrectly. 
    /// It is recommended that the dependencies you register are stateless and if there is a need to share
    /// common state between several objects during the lifetime of an HTTP request, then you can
    /// have a stateless service that explicitly stores and retrieves this state using the
    /// <see cref="System.Web.HttpContext.Items"/> collection of the <see cref="System.Web.HttpContext.Current"/> object.
    /// </para>
    /// <para>
    /// For the instance of the registered type to be disposed automatically when the HTTP request completes,
    /// make sure to register the <see cref="UnityPerRequestHttpModule"/> with the web application.
    /// To do this, invoke the following in the Unity bootstrapping class (typically UnityMvcActivator.cs):
    /// <code>DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));</code>
    /// </para>
    /// </remarks>
    public class PerRequestLifetimeManager : LifetimeManager
    {
        private readonly object lifetimeKey = new object();

        /// <summary>
        /// Retrieves a value from the backing store associated with this lifetime policy.
        /// </summary>
        /// <returns>The desired object, or null if no such object is currently stored.</returns>
        public override object GetValue()
        {
            return UnityPerRequestHttpModule.GetValue(this.lifetimeKey);
        }

        /// <summary>
        /// Stores the given value into the backing store for retrieval later.
        /// </summary>
        /// <param name="newValue">The object being stored.</param>
        public override void SetValue(object newValue)
        {
            UnityPerRequestHttpModule.SetValue(this.lifetimeKey, newValue);
        }

        /// <summary>
        /// Removes the given object from the backing store.
        /// </summary>
        public override void RemoveValue()
        {
            var disposable = this.GetValue() as IDisposable;

            if (disposable != null)
            {
                disposable.Dispose();
            }

            UnityPerRequestHttpModule.SetValue(this.lifetimeKey, null);
        }
    }
}