MVC, EF-DataContext instancia única Por Solicitud Web en Unity


Tengo una aplicación web MVC 3, donde estoy usando Entity Framework para el acceso a los datos. Además, he hecho un uso simple del patrón de repositorio, donde por ejemplo, todas las cosas relacionadas con el Producto se manejan en el "ProductRepository" y todas las cosas relacionadas con el Usuario se manejan en el "UserRepository".

Por lo tanto, estoy usando el contenedor UNITY, para hacer una instancia singleton del DataContext, que inyecto en cada uno de los repositorios. Una búsqueda rápida en Google, y todo el mundo recomienda usted no debe usar una instancia singleton del DataContext, ya que podría darle algunas fugas de memoria en el futuro.

Así que, inspirado por este post, hacer una instancia única del DataContext para cada solicitud web es la respuesta (por favor corríjame si me equivoco!)

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

Sin embargo, UNITY no admite el lifetime manager "Por solicitud web". Pero, lo es es posible implementar su propio gestor personalizado de por vida, que se encarga de esto por usted. En realidad, esto se discute en este post :

Contexto Singleton Por Llamada (Solicitud Web) en Unity

La pregunta es, ahora he implementado el custom lifetime manager como se describe en el post anterior, pero no estoy seguro de si esta es la manera de hacerlo. También me pregunto dónde está dispuesta la instancia de datacontext en la solución proporcionada. ¿Me estoy perdiendo algo?

Es ¿hay realmente una mejor manera de resolver mi "problema"?

Gracias!

* * Se agregó información sobre mi implementación * *

Los siguientes son fragmentos de mi Global.asax, Controlador y Repositorio. Esto da una imagen clara de mi implementación.

Global.asax

  var container = new UnityContainer();
            container
                .RegisterType<ProductsRepository>(new ContainerControlledLifetimeManager())
                .RegisterType<CategoryRepository>(new ContainerControlledLifetimeManager())
                .RegisterType<MyEntities>(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();
}

Repositorio de productos

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

}

Información adicional Hola, yo publicará enlaces adicionales que me encuentro, en relación con el tema relacionado y sugerencias de solución:

  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. adjuntar linq a sql datacontext a httpcontext en 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
Author: Community, 2011-03-04

9 answers

no comparta contexto y use un contexto por solicitud. También puedes revisar las preguntas vinculadas en esa publicación para ver todos los problemas causados por un contexto compartido.

Ahora acerca de la Unidad. La idea de PerCallContextLifetimeManager funciona, pero creo que la implementación provista no funcionará para más de un objeto. Debes usar PerHttpRequestLifetimeManager directamente:

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

Ten en cuenta que Unity no dispondrá de contexto para ti. También tenga en cuenta que la implementación predeterminada UnityContainer nunca llamará al método RemoveValue.

Si su implementación resuelve todos los repositorios en una sola llamada Resolve (por ejemplo, si sus controllers reciben instancias de repositorios en constructor y usted está resolviendo controllers) no necesita este lifetime manager. En tal caso use build-in (Unity 2.0) PerResolveLifetimeManager.

Editar:

Veo un problema bastante grande en la configuración proporcionada de UnityContainer. Está registrando ambos repositorios con ContainerControllerLifetimeManager. Este lifetime manager significa Singleton instance per vida útil del contenedor. Esto significa que ambos repositorios serán instanciados solo una vez y la instancia será almacenada y reutilizada para llamadas posteriores. Por eso no importa a qué vida le asignaste MyEntities. Se inyecta a los constructores de repositorios que se llamarán solo una vez. Ambos repositorios usarán todavía esa instancia única de MyEntities creada durante su construcción = usarán una sola instancia durante toda la vida de su AppDomain. Ese es el peor escenario que puedes lograr.

Reescriba su configuración de esta manera:

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

¿Por qué esto es suficiente? Está resolviendo el controlador que depende de los repsitorios, pero no se necesita ninguna instancia de repositorio más de una vez, por lo que puede usar default TransientLifetimeManager que creará una nueva instancia para cada llamada. Debido a que se llama al constructor del repositorio y se debe resolver la instancia MyEntities. Pero sabes que varios repositorios pueden necesitar esta instancia, por lo que la configurarás con PerResolveLifetimeManager = > cada resolución de el controlador producirá solo una instancia de MyEntities.

 38
Author: Ladislav Mrnka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:40

A partir de Unity 3, ya hay un lifetime manager integrado por solicitud http.

PerRequestLifetimeManager

Un LifetimeManager que conserva la instancia que se le ha dado durante la vida útil de una sola solicitud HTTP. Este lifetime manager le permite crear instancias de tipos registrados que se comportan como singletons dentro del ámbito de una solicitud HTTP. Consulte las observaciones para obtener información de uso importante.

Observaciones de MSDN

Aunque PerRequestLifetimeManager lifetime manager funciona correctamente y puede ayudar a trabajar con dependencias con estado o inseguras dentro del alcance de una solicitud HTTP, generalmente no es una buena idea usarlo cuando se puede evitar, ya que a menudo puede conducir a malas prácticas o difícil encontrar errores en el código de la aplicación del usuario final cuando se usa incorrectamente.

Se recomienda que las dependencias que registre sean apátridas y si hay una necesidad de compartir el estado común entre varios objetos durante la vida útil de una solicitud HTTP, entonces puede tener un servicio sin estado que almacena explícitamente y recupera este estado utilizando la colección de elementos del objeto actual.

Las observaciones dicen que incluso si se ve obligado a usar un único contexto por servicio (servicio de fachada), debe mantener sus llamadas de servicio sin estado.

Unity 3 es para.NET 4.5 por cierto.

 8
Author: Yorro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-20 07:18:40

Creo que el código de ejemplo que se muestra en NerdDinner: DI en MVC usando Unity para su HttpContextLifetimeManager debería satisfacer sus necesidades.

 5
Author: neontapir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-03 22:53:42

No quiero desalentarte innecesariamente y por todos los medios experimentar, pero si sigues adelante y utilizar instancias singleton de DataContext asegúrese de lo clava.

Puede parecer que funciona bien en su entorno de desarrollo, pero podría estar fallando al cerrar las conexiones correctamente. Esto será difícil de ver sin la carga de un entorno de producción. En un entorno de producción con alta carga, las conexiones no expuestas causarán enormes fugas de memoria y luego una alta CPU tratando de asignar nueva memoria.

¿Ha considerado lo que está obteniendo de un patrón de conexión por solicitud? ¿Cuánto rendimiento hay que ganar al abrir / cerrar una conexión una vez más, digamos, 3-4 veces en una solicitud? Vale la pena la molestia? También esto hace que la carga perezosa falle (lea las consultas de la base de datos en su vista) ofensas mucho más fáciles de hacer.

Lo siento si esto resultó desalentador. Ve por ello si realmente ves el beneficio. Sólo te advierto que podría ser contraproducente si consigues está mal, así que te advierto. Algo como entity profiler será invaluable para hacerlo bien - te dice el número de conexiones abiertas y cerradas - entre otras cosas muy útiles.

 2
Author: BritishDeveloper,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-03 23:51:17

Vi preguntas y respuestas hace unas cuantas veces. Está fechado. Unidad.MVC3 tiene life time manager como HierarchicalLifetimeManager.

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

Y funciona bien.

 2
Author: Nuri YILMAZ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-14 21:05:37

Yo propondría resolverlo así: http://forums.asp.net/t/1644386.aspx/1

Saludos cordiales

 1
Author: Dzenan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-16 09:29:22

Resolví esto usando Castle.DynamicProxy. Necesitaba que ciertas dependencias se inyectaran "Bajo demanda", lo que significa que debían resolverse en el momento del uso, no en el momento de la acumulación de" Dependencia".

Para hacer esto, configuro mi contenedor de la siguiente manera:

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

La idea es que proveo un método para recuperar la instancia "bajo demanda"."La lambda se invoca cada vez que se utiliza cualquiera de los métodos de la instancia. El objeto Dependiente en realidad contiene una referencia a un proxy objeto, no el objeto en sí.

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
Author: Ben Grabkowitz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-30 18:53:43

En Unity3, si desea utilizar

PerRequestLifetimeManager

Necesita registrarse UnityPerRequestHttpModule

Hago esto usando WebActivatorEx, el código es el siguiente:

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();
        }
    }
}
 1
Author: Yang Zhang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-17 23:58:54

Las clases PerRequestLifetimeManager y UnityPerRequestHttpModule están en Unity.Mvc package que tiene una dependencia de ASP.NET MVC. Si no desea tener esa dependencia (por ejemplo, está utilizando Web API), tendrá que copiarlas y pegarlas en su aplicación.

Si haces eso, no olvides registrar el HttpModule.

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

Editar: Incluiré las clases aquí antes de que CodePlex se apague:

// 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);
        }
    }
}
 0
Author: Ufuk Hacıoğulları,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-24 12:31:34