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

我有一个MVC 3的Web应用程序,我正在使用entity framework的数据访问。 此外,我已经简单地使用了存储库模式,例如所有与产品相关的东西都在“ProductRepository”中处理,所有与User相关的东西都在“UserRepository”中处理。

因此,我使用UNITY容器来创build一个DataContext的单例实例,我将其注入到每个存储库中。 在Google上快速search,每个人都build议你不要使用DataContext的单例实例,因为它可能会在未来给你一些内存泄漏。

所以,受这篇文章的启发,为每个Web请求创build一个DataContext的单例实例就是答案(如果我错了,请纠正我的错误!)

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

但是,UNITY不支持“每个networking请求”终身pipe理器。 但是,可以实现自己的自定义生命期pipe理器,它可以为您处理。 实际上,这是在这篇文章中讨论:

Singleton每呼叫上下文(Web请求)在Unity

问题是,现在我已经实现了上述职位描述的自定义生存期pipe理器,但是我不确定这是否可以这样做。 我也想知道datacontext实例在提供的解决scheme中的位置? 我错过了什么吗?

有没有更好的办法来解决我的“问题”?

谢谢!

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

以下是我的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; } } 

补充信息嗨,我会发布我碰到的其他链接,关于相关的问题和解决scheme的build议:

  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

是的,不要共享上下文 ,每个请求使用一个上下文。 您还可以检查该post中的链接问题,查看共享环境导致的所有问题。

现在关于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调用中parsing所有存储库(例如,如果您的控制器在构造函数中接收到存储库的实例,并且正在parsing控制器),则不需要此生存期pipe理器。 在这种情况下使用内build(Unity 2.0) PerResolveLifetimeManager

编辑:

在您提供的UnityContainerconfiguration中,我看到很大的问题。 您正在使用ContainerControllerLifetimeManager注册这两个存储库。 这个生命期pipe理器意味着每个容器生命周期的单例实例 这意味着这两个存储库只会被实例化一次,实例将被存储并重用于后续调用。 正因为如此,您分配给MyEntities时间并不重要。 它被注入到只会被调用一次的存储库的构造函数中。 这两个存储库将仍然使用在构build期间创build的MyEntities单个实例=它们将在AppDomain整个生命周期中使用单个实例。 这是你可以达到的最糟糕的情况。

用这种方法重写你的configuration:

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

为什么这足够了? 您正在parsing依赖于replactors的控制器,但是不再需要更多的存储库实例,因此您可以使用默认的TransientLifetimeManager ,它将为每个调用创build新的实例。 由于该库构造函数被调用, MyEntities实例必须被parsing。 但是您知道多个存储库可能需要此实例,因此您将使用PerResolveLifetimeManager设置=>每个parsing控制器将只生成一个MyEntities实例。

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

PerRequestLifetimeManager

一个LifetimeManager,在单个HTTP请求的生命周期中保存给它的实例。 这个终身pipe理器使您可以创build在HTTP请求范围内performance为类似于单例的注册types的实例。 请参阅重要使用信息的备注。

MSDN的备注

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

build议您注册的依赖关系是无状态的,如果在HTTP请求的生命周期中需要共享多个对象之间的通用状态,那么可以使用无状态服务来显式存储和检索这个状态,当前对象。

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

顺便说一句,Unity 3是针对.NET 4.5的。

我相信NerdDinner上显示的示例代码:使用Unity的MVC中的DI为其HttpContextLifetimeManager应符合您的需求。

我不想不必要地劝阻你,通过一切手段实验,但如果你继续使用DataContext的单例实例,请确保你指定它。

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

你有没有考虑你从一个连接获得每个请求模式? 在一次请求中,如果打开/closures一次连接超过3-4次,性能会有多大? 值得麻烦? 这也使得延迟加载失败(在你的视图中读取数据库查询)很容易犯的错误。

对不起,如果这令人沮丧。 如果你真的看到了好处,那就去吧。 我只是在警告你,如果你弄错了,可能会适得其反。 类似于实体分析器的东西将是无价的正确 – 它告诉你连接数量打开和closures – 其他非常有用的东西。

我几次看到问题和答案。 它过时了。 Unity.MVC3拥有生命时间pipe理器作为HierarchicalLifetimeManager。

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

它工作很好。

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

最好的祝福

我通过使用Castle.DynamicProxy解决了这个问题。 我需要将某些dependency injection“按需”,这意味着他们需要在使用时解决,而不是在“Depender”build立时。

要做到这一点,我configuration我的容器是这样的:

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

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

PerRequestLifetimeManager和UnityPerRequestHttpModule类在Unity.Mvc包中 ,它依赖于ASP.NET MVC。 如果你不想拥有这种依赖性(比如你正在使用Web API),你将不得不将它们复制粘贴到你的应用程序中。

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

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

编辑:我将在CodePlexclosures之前在这里包括这些类:

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