MVC, EF – Instância de singleton DataContext Per-Web-Request no Unity

Eu tenho um aplicativo da web MVC 3, onde estou usando o Entity Framework para o access a dados. Além disso, fiz um uso simples do padrão de repository, onde, por exemplo, todo o material relacionado ao Produto é tratado no “ProductRepository” e todo o material relacionado ao Usuário é tratado no “UserRepository”.

Assim, estou usando o contêiner UNITY, para criar uma instância singleton do DataContext, que eu informo em cada um dos repositorys. Uma pesquisa rápida no Google, e todos recomendam que você NÃO use uma instância singleton do DataContext, pois isso pode fornecer alguns vazamentos de memory no futuro.

Então, inspirado por este post, fazer uma instância singleton do DataContext para cada solicitação da web é a resposta (por favor, corrija-me se eu estiver errado!)

http://www.google.com/support

No entanto, a UNITY não suporta o gerenciador de vida útil “Solicitação por Web”. Mas, é possível implementar seu próprio gerenciador de vida personalizado, que lida com isso para você. Na verdade, isso é discutido neste post:

Contexto Singleton por chamada (solicitação da Web) no Unity

A questão é, agora implementei o gerenciador de tempo de vida personalizado, conforme descrito no post acima, mas não tenho certeza se essa é a maneira de fazê-lo. Também estou querendo saber onde a instância datacontext é descartada na solução fornecida? Estou perdendo alguma coisa?

Existe realmente uma maneira melhor de resolver o meu “problema”?

Obrigado!

** Adicionadas informações sobre minha implementação **

A seguir, trechos do meu Global.asax, Controller e Repository. Isso dá uma imagem clara da minha implementação.

Global.asax

var container = new UnityContainer(); container .RegisterType(new ContainerControlledLifetimeManager()) .RegisterType(new ContainerControlledLifetimeManager()) .RegisterType(new PerResolveLifetimeManager(), dbConnectionString) 

Controlador

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

Repositório de produtos

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

Fábrica de Controladores

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

Informações sobre adição Olá, postarei links adicionais com os quais me deparo, com relação ao problema relacionado e sugestões de solução:

  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. anexando linq ao sql datacontext ao httpcontext na camada de negócios
  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/pt-br/library/bb738470.aspx

    Sim , não compartilhe o contexto e use um contexto por solicitação. Você também pode verificar as perguntas vinculadas nessa postagem para ver todos os problemas causados ​​por um contexto compartilhado.

    Agora sobre a unidade. A idéia de PerCallContextLifetimeManager funciona, mas acho que a implementação fornecida não funcionará para mais de um object. Você deve usar o PerHttpRequestLifetimeManager diretamente:

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

    Esteja ciente de que a Unidade não irá dispor contexto para você. Também esteja ciente de que a implementação padrão do UnityContainer nunca chamará o método RemoveValue .

    Se sua implementação resolver todos os repositorys em uma única chamada Resolve (por exemplo, se seus controladores receberem instâncias de repositorys no construtor e você estiver resolvendo controladores), não será necessário esse gerenciador de tempo de vida. Nesse caso, use o build-in (Unity 2.0) PerResolveLifetimeManager .

    Editar:

    Eu vejo um grande problema em sua configuração fornecida do UnityContainer . Você está registrando os dois repositorys com o ContainerControllerLifetimeManager . Este gerenciador vitalício significa instância de Singleton por tempo de vida do contêiner. Isso significa que os dois repositorys serão instanciados apenas uma vez e a instância será armazenada e reutilizada para chamadas subsequentes. Por causa disso, não importa o tempo que você atribuiu a MyEntities . É injetado nos construtores dos repositorys, que serão chamados apenas uma vez. Ambos os repositorys usarão ainda aquela instância única de MyEntities criada durante sua construção = usarão instância única para toda a vida útil do seu AppDomain . Esse é o pior cenário que você pode alcançar.

    Reescreva sua configuração desta maneira:

     var container = new UnityContainer(); container .RegisterType() .RegisterType() .RegisterType(new PerResolveLifetimeManager(), dbConnectionString); 

    Por que isso é suficiente? Você está resolvendo o controlador que é dependente dos representantes, mas nenhuma instância do repository é necessária mais de uma vez para que você possa usar o TransientLifetimeManager padrão, que criará uma nova instância para cada chamada. Por causa desse construtor de repository é chamado e a instância MyEntities deve ser resolvida. Mas você sabe que vários repositorys podem precisar dessa instância, portanto, você configurará com PerResolveLifetimeManager => cada resolução do controlador produzirá apenas uma instância de MyEntities .

    A partir do Unity 3, já existe um gerenciador de tempo de vida integrado por solicitação http.

    PerRequestLifetimeManager

    Um LifetimeManager que mantém a instância dada a ele durante o tempo de vida de uma única solicitação HTTP. Esse gerenciador de tempo de vida permite criar instâncias de tipos registrados que se comportam como singletons no escopo de uma solicitação HTTP. Veja observações para informações importantes sobre o uso.

    Comentários por MSDN

    Embora o gerenciador de tempo de vida PerRequestLifetimeManager funcione corretamente e possa ajudar a trabalhar com dependencies com estado ou sem segurança de thread dentro do escopo de uma solicitação HTTP, geralmente não é uma boa ideia usá-lo quando puder ser evitado , pois pode levar a problemas práticas ou difícil encontrar erros no código do aplicativo do usuário final quando usado incorretamente.

    Recomenda-se que as dependencies que você registra sejam sem estado e se houver necessidade de compartilhar o estado comum entre vários objects durante a existência de uma solicitação HTTP, então você pode ter um serviço sem estado que armazene e recupere explicitamente esse estado usando a coleção Items de o object atual.

    As observações dizem que mesmo você é forçado a usar um único contexto por serviço (serviço de fachada), você deve manter suas chamadas de serviço sem estado.

    Unity 3 é para o .NET 4.5 pelo caminho.

    Eu acredito que o código de exemplo mostrado no NerdDinner: DI no MVC usando Unity para seu HttpContextLifetimeManager deve atender às suas necessidades.

    Eu não quero desanimá-lo desnecessariamente e, por todos os meios, experimente, mas se você for em frente e usar instâncias singleton do DataContext, certifique-se de usá-lo.

    Ele pode parecer funcionar bem no seu ambiente de desenvolvimento, mas pode não conseguir fechar as conexões corretamente. Isso será difícil de ver sem a carga de um ambiente de produção. Em um ambiente de produção com carga alta, as conexões não-ocultas causarão grandes vazamentos de memory e, em seguida, a alta CPU tentando alocar nova memory.

    Você já considerou o que está ganhando de uma conexão por padrão de solicitação? Quanto desempenho há a ganhar ao abrir / fechar uma conexão uma vez, digamos, de 3 a 4 vezes em uma solicitação? Vale o incômodo? Além disso, isso faz com que o carregamento ocioso falhe (consulte as consultas do database na sua opinião), ofensas muito mais fáceis de serem feitas.

    Desculpe se isso foi desanimador. Vá em frente se você realmente ver o benefício. Estou apenas avisando que pode sair pela culatra seriamente se você errar, seja avisado. Algo como o profiler de entidade será inestimável para acertar – ele diz a você o número de conexões abertas e fechadas – entre outras coisas muito úteis.

    Eu vi perguntas e respostas algumas vezes atrás. É datado. O Unity.MVC3 possui o gerenciador de tempo de vida como HierarchicalLifetimeManager.

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

    e funciona bem.

    Eu proporia resolvê-lo assim: http://forums.asp.net/t/1644386.aspx/1

    Cumprimentos

    Eu resolvi isso usando Castle.DynamicProxy. Eu precisava que certas dependencies fossem injetadas “On Demand”, o que significa que elas precisavam ser resolvidas no momento do uso, e não no momento da configuração “Depender”.

    Para fazer isso, configurei meu contêiner da seguinte maneira:

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

    A ideia é que eu forneça um método para recuperar a instância “on demand”. O lambda é invocado sempre que qualquer um dos methods da instância é usado. O object Dependente está, na verdade, mantendo uma referência a um object em proxy, não o object em si.

    OnDemandInjectionFactory:

     internal class OnDemandInjectionFactory : InjectionFactory { public OnDemandInjectionFactory(Func proxiedObjectFactory) : base((container, type, name) => FactoryFunction(container, type, name, proxiedObjectFactory)) { } private static object FactoryFunction(IUnityContainer container, Type type, string name, Func proxiedObjectFactory) { var interceptor = new OnDemandInterceptor(container, proxiedObjectFactory); var proxyGenerator = new ProxyGenerator(); var proxy = proxyGenerator.CreateClassProxy(type, interceptor); return proxy; } } 

    OnDemandInterceptor:

     internal class OnDemandInterceptor : IInterceptor { private readonly Func _proxiedInstanceFactory; private readonly IUnityContainer _container; public OnDemandInterceptor(IUnityContainer container, Func 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); } } 

    No Unity3, se você quiser usar

     PerRequestLifetimeManager 

    Você precisa registrar UnityPerRequestHttpModule

    Eu faço isso usando o WebActivatorEx, o código é como abaixo:

     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 { /// Provides the bootstrapping for integrating Unity with ASP.NET MVC. public static class UnityWebActivator { /// Integrates Unity when the application starts. public static void Start() { var container = UnityConfig.GetConfiguredContainer(); FilterProviders.Providers.Remove(FilterProviders.Providers.OfType().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)); } /// Disposes the Unity container when the application is shut down. public static void Shutdown() { var container = UnityConfig.GetConfiguredContainer(); container.Dispose(); } } } 

    As classs PerRequestLifetimeManager e UnityPerRequestHttpModule estão no pacote Unity.Mvc que tem uma dependência no ASP.NET MVC. Se você não quiser ter essa dependência (por exemplo, você está usando a API da Web), será necessário copiá-los e colá-los no aplicativo.

    Se você fizer isso, não se esqueça de registrar o HttpModule.

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

    Edit: Vou include as classs aqui antes do CodePlex ser encerrado:

     // 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 { ///  /// Implementation of the  interface that provides support for using the ///  lifetime manager, and enables it to /// dispose the instances after the HTTP request ends. ///  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(); HttpContext.Current.Items[ModuleKey] = dict; } dict[lifetimeManagerKey] = value; } ///  /// Disposes the resources used by this module. ///  public void Dispose() { } ///  /// Initializes a module and prepares it to handle requests. ///  /// An  that provides access to the methods, properties, /// and events common to all application objects within an ASP.NET application. [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()) { disposable.Dispose(); } } } private static Dictionary GetDictionary(HttpContext context) { if (context == null) { throw new InvalidOperationException(Resources.ErrorHttpContextNotAvailable); } var dict = (Dictionary)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 { ///  /// A  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. ///  ///  ///  /// Although the  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 ///  collection of the  object. ///  ///  /// For the instance of the registered type to be disposed automatically when the HTTP request completes, /// make sure to register the  with the web application. /// To do this, invoke the following in the Unity bootstrapping class (typically UnityMvcActivator.cs): /// DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule)); ///  ///  public class PerRequestLifetimeManager : LifetimeManager { private readonly object lifetimeKey = new object(); ///  /// Retrieves a value from the backing store associated with this lifetime policy. ///  /// The desired object, or null if no such object is currently stored. public override object GetValue() { return UnityPerRequestHttpModule.GetValue(this.lifetimeKey); } ///  /// Stores the given value into the backing store for retrieval later. ///  /// The object being stored. public override void SetValue(object newValue) { UnityPerRequestHttpModule.SetValue(this.lifetimeKey, newValue); } ///  /// Removes the given object from the backing store. ///  public override void RemoveValue() { var disposable = this.GetValue() as IDisposable; if (disposable != null) { disposable.Dispose(); } UnityPerRequestHttpModule.SetValue(this.lifetimeKey, null); } } }