MVC, EF - 在Unity中每个Web请求的DataContext单例实例

50
我有一个MVC 3网站应用程序,其中使用实体框架进行数据访问。此外,我简单地使用了存储库模式,例如,“ProductRepository”处理所有与产品相关的内容,“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”生命周期管理器。但是,您可以实现自己的自定义生命周期管理器来处理此问题。实际上,这在以下帖子中讨论: Unity中的Singleton Per Call Context(Web Request) 问题是,我现在已经按照上述帖子中描述的方式实现了自定义生命周期管理器,但我不确定这是否是正确的方法。我也想知道提供的解决方案中数据上下文实例是在哪里被处理的。我有遗漏的内容吗?
实际上,是否有更好的方法来解决我的“问题”?
谢谢!
**添加关于我的实现的信息**
以下是来自我的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. https://github.com/geersch/EntityFrameworkObjectContext
  2. http://dotnetslackers.com/articles/ado_net/Managing-Entity-Framework-ObjectContext-lifespan-and-scope-in-n-layered-ASP-NET-applications.aspx
  3. 将linq附加到业务层中的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

1
吉尔是正确的;每个请求一个实例是正确的方法。不过我无法帮你解决Unity的问题。 - Craig Stuntz
@Craig,感谢您的评论。正如您所看到的,关于在每个http请求中使用共享的ObjectContext存在许多不同的观点。尽管如此,我还没有完全被说服。 - Nima
不要使用单例DataContexts:http://j.mp/fi83NV - BritishDeveloper
9个回答

38

是的,不要共享上下文,每个请求使用一个上下文。您还可以查看该帖子中链接的问题,了解共享上下文引起的所有问题。

现在谈谈 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);

为什么这样就足够了呢?你正在解决依赖于存储库的控制器,但不需要多次使用存储库实例,因此可以使用默认的TransientLifetimeManager,它将为每个调用创建新实例。因此,必须调用存储库构造函数并解析MyEntities实例。但是你知道多个存储库可能需要此实例,因此你将其设置为PerResolveLifetimeManager => 每次解析控制器时将仅生成MyEntities的一个实例。


@Nima:每个请求使用单个 ObjectContext 是正确的方法。CallContextHttpContext 实现之间不应该有太大的区别,因为它们很可能存储在 CallContext 中。释放上下文是棘手的。我在控制器工厂中处理它。在 ReleaseController 中,我会处理控制器并释放其依赖项。 - Ladislav Mrnka
@Ladislav:非常感谢您编辑的答案,我真的很感激!这是否意味着我不再需要使用PerHttpRequestLifetime?那么MyEntities ObjectContext的处理呢?我应该在控制器上重写Dispose,并调用存储库上的dispose,然后释放_db对象吗? - Nima
@Ladislav:请检查已编辑的代码片段,现在是否正确?因此,仓库现在正在使用同一实例的 ObjectContext(MyEntities),它是每个 http 请求的新实例?并且 Object Context 在使用后被正确清除了吗? - Nima
@Ladislav:太好了! 我想我们成功了:) 我头脑中唯一不清楚的事情是,我们没有使用PerHttpRequestLifetime,但我们仍然成功地实现了我们的意图:每个http请求管理单个ObjectContext实例。你的话让我想知道:“如果你只需要在每个请求中调用解决一次”。在我的情况下,什么时候使用自定义的PerHttpRequestLifetime才有意义呢? - Nima
@Ladislav,为什么需要GUID密钥?HttpContext.Current不已经确保您正在处理单个唯一请求了吗?如果我想要每个请求一个DataContext,为什么键名不能只是像“DataContext”这样的名称?为什么需要GUID呢? - devuxer
显示剩余11条评论

8
自Unity 3以来,每个http请求已经内置了一个生命周期管理器。
PerRequestLifetimeManager 一个持有给定实例的LifetimeManager,在单个HTTP请求的生命周期内保留该实例。此生命周期管理器使您能够创建已注册类型的实例,在HTTP请求范围内表现为单例。有关重要的使用信息,请参见备注。
MSDN的备注
尽管PerRequestLifetimeManager生命周期管理器可以正确工作,并且可以在处理状态或线程不安全的依赖项时有所帮助,但通常情况下应避免使用它,因为在使用不正确时,它经常会导致用户应用程序代码中的糟糕实践或难以找到的错误。
建议您注册的依赖项是无状态的,并且如果需要在HTTP请求的生命周期内共享公共状态,则可以具有显式存储和检索此状态的无状态服务,使用Current对象的Items集合。

这段话的意思是,即使你被迫在一个服务中使用单个上下文(门面服务),你也应该保持你的服务调用无状态。

顺便提一下,Unity 3适用于.NET 4.5。


5

谢谢您的回复。我猜这是和@ladislav在他的回答中所写的方法是一样的? - Nima

2

我之前看到了一个问题和答案,但是它已经过时了。Unity.MVC3有一个生命周期管理器,名为HierarchicalLifetimeManager。

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

它表现得很好。


我也想说同样的话:)Unity对此使用子容器,因此可能要考虑其他DI包之一(而且使用起来更容易,例如使用Autofac-支持请求生命周期的内置支持)。 - Adam Tuliper
我使用这个,但每个HTTP请求都得到相同的实例。不起作用。 - Thanasis Ioannidis
@ThanasisIoannidis 这是针对非常非常旧的版本(MVC 3)的。 - Nuri YILMAZ

2

我不想不必要地打击你的积极性,但请务必实验一下,如果你决定使用DataContext的单例实例,请确保你做得对。

在你的开发环境中看起来可能很正常,但是它可能无法正确关闭连接。这在没有生产环境的负载下很难发现。在高负载的生产环境中,未释放的连接会导致巨大的内存泄漏,然后高CPU尝试分配新内存。

你考虑过从每个请求一个连接模式中获得了什么吗?在请求中一次打开/关闭连接与在3-4次请求中打开/关闭连接相比,有多少性能可以提升?值得麻烦吗?此外,这使得懒加载失败(在视图中读取数据库查询)更容易出现。

如果我的话让你感到泄气,我很抱歉。如果你真的看到了好处,请去尝试。我只是警告你,如果你弄错了,它可能会严重反弹,所以请小心。像entity profiler这样的工具将对你做得对与否非常有帮助——它会告诉你打开和关闭的连接数——以及其他非常有用的信息。


谢谢您的回答。实际上,您的帖子让我有点困惑(再一次),让我再次搜索谷歌。问题不在于性能提升,而是拥有多个ObjectContext实例所带来的问题,正如此处所述链接。我将会尝试使用Entity Profiler!谢谢。 - Nima

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

1

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

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

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