web-dev-qa-db-fra.com

MVC, EF - Instance de singleton DataContext par requête Web par unité

J'ai une application Web MVC 3, où j'utilise Entity Framework pour l'accès aux données. De plus, j’ai fait un usage simple du modèle de référentiel, par exemple tous les éléments liés au produit sont gérés dans le "ProductRepository" et tous les éléments liés à l'utilisateur sont gérés dans le "UserRepository". 

Ainsi, j'utilise le conteneur UNITY pour créer une instance singleton du DataContext, que j'injecte dans chacun des référentiels. Une recherche rapide sur Google et tout le monde vous recommande de ne PAS utiliser une instance singleton du DataContext, car cela pourrait vous donner des fuites de mémoire à l'avenir. 

Donc, inspiré par cet article, créer une instance singleton du DataContext pour chaque requête Web constitue la réponse (corrigez-moi si je me trompe!)

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

Cependant, UNITY ne prend pas en charge le gestionnaire de durée de vie "Par requête Web". Mais il est possible d'implémenter votre propre gestionnaire de durée de vie personnalisé, qui gère cela pour vous. En fait, cela est discuté dans ce post: 

_ { Contexte de singleton par appel (demande Web) dans Unity } _

La question est, j'ai maintenant mis en application le gestionnaire de durée de vie personnalisé comme décrit dans le post ci-dessus, mais je ne suis pas sûr si c'est la façon de le faire. Je me demande également où l'instance de datacontext est disposée dans la solution fournie? Est-ce que je manque quelque chose?

Existe-t-il réellement une meilleure façon de résoudre mon "problème"?

Merci!

** Ajout d'informations sur ma mise en œuvre **

Voici des extraits de mon fichier Global.asax, contrôleur et référentiel. Cela donne une image claire de ma mise en œuvre. 

Global.asax

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

Manette 

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

Référentiel de produits

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

Contrôleur d'usine

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

}

Informations additionnelles Bonjour, je publierai des liens supplémentaires sur le problème et des solutions suggérées: 

  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. attacher linq à sql datacontext à httpcontext dans la couche de gestion
  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
49
Nima

Oui ne partage pas le contexte et utilise un contexte par demande. Vous pouvez également vérifier les questions liées dans cet article pour voir tous les problèmes causés par un contexte partagé.

Maintenant à propos de l'unité. L'idée de PerCallContextLifetimeManager fonctionne mais je pense que l'implémentation fournie ne fonctionnera pas pour plus d'un objet. Vous devriez utiliser PerHttpRequestLifetimeManager directement:

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

Sachez que Unity ne disposera pas du contexte pour vous. Sachez également que l'implémentation par défaut de UnityContainer n'appelle jamais la méthode RemoveValue

Si votre implémentation résout tous les référentiels en un seul appel Resolve (par exemple, si vos contrôleurs reçoivent des instances de référentiels dans le constructeur et que vous résolvez des contrôleurs), vous n'avez pas besoin de ce gestionnaire de durée de vie. Dans ce cas, utilisez la version (Unity 2.0) PerResolveLifetimeManager

Modifier:

Je vois un très gros problème dans votre configuration fournie de UnityContainer. Vous enregistrez les deux référentiels avec ContainerControllerLifetimeManager. Ce gestionnaire de durée de vie signifie instance Singleton par durée de vie du conteneur. Cela signifie que les deux référentiels seront instanciés une seule fois et que l'instance sera stockée et réutilisée pour les appels suivants. Pour cette raison, quelle que soit la durée de vie que vous avez attribuée à MyEntities. Il est injecté aux constructeurs des référentiels qui ne seront appelés qu'une seule fois. Les deux référentiels utiliseront toujours cette instance unique de MyEntities créée lors de leur construction = ils utiliseront une instance unique pendant toute la durée de vie de votre AppDomain. C'est le pire scénario que vous puissiez réaliser.

Réécrivez votre configuration de cette façon:

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

Pourquoi c'est assez? Vous résolvez un contrôleur dépendant de repsitories mais aucune instance de référentiel n’est nécessaire plus d’une fois. Vous pouvez donc utiliser la valeur par défaut TransientLifetimeManager qui créera une nouvelle instance pour chaque appel. A cause de cela, le constructeur du référentiel est appelé et l'instance MyEntities doit être résolue. Mais vous savez que plusieurs référentiels peuvent avoir besoin de cette instance, vous devez donc la définir avec PerResolveLifetimeManager => chaque résolution de contrôleur produira une seule instance de MyEntities.

38
Ladislav Mrnka

Depuis Unity 3, il existe déjà un gestionnaire de durée de vie intégré par requête http. 

PerRequestLifetimeManager

LifetimeManager qui conserve l'instance qui lui est donnée pendant la durée de vie d'une seule requête HTTP. Ce gestionnaire de durée de vie vous permet de créer des instances de types enregistrés se comportant comme des singletons dans l'étendue d'une requête HTTP. Voir les remarques pour des informations d'utilisation importantes.

_ {Remarques par MSDN} _

Bien que le gestionnaire de durée de vie PerRequestLifetimeManager fonctionne correctement et puisse vous aider à gérer les dépendances avec état ou insensibles aux threads dans le cadre d'une requête HTTP, ce n'est généralement pas une bonne idée de l'utiliser lorsqu'il peut être évité}, cela peut souvent conduire à de mauvaises pratiques ou à des difficultés pour trouver des bogues dans le code de l'application de l'utilisateur final lorsqu'il est utilisé de manière incorrecte. 

Il est recommandé que les dépendances que vous enregistrez soient sans état. S'il est nécessaire de partager un état commun entre plusieurs objets pendant la durée d'une requête HTTP, vous pouvez disposer d'un service sans état qui stocke et récupère explicitement cet état à l'aide de la collection Items de l'objet actuel.

Les remarques indiquent que même si vous êtes obligé d'utiliser un seul contexte par service (service de façade), vous devez garder vos appels de service sans état.

En passant, Unity 3 est destiné à .NET 4.5.

8
Yorro

Je crois que l'exemple de code présenté sur NerdDinner: DI dans MVC utilisant Unity pour sa HttpContextLifetimeManager devrait répondre à vos besoins.

5
neontapir

Je ne veux pas vous décourager inutilement et par tous les moyens, expérimentez, mais si vous continuez et utilisez des instances uniques de DataContext assurez-vous vous réussissez.

Cela peut sembler fonctionner correctement sur votre environnement de développement, mais il se peut qu’il ne parvienne pas à fermer les connexions correctement. Ce sera difficile à voir sans la charge d'un environnement de production. Dans un environnement de production avec une charge élevée, des connexions non dissociées provoqueront des fuites de mémoire considérables, puis un processeur élevé tentant d'allouer une nouvelle mémoire.

Avez-vous pensé à ce que vous gagnez d'une connexion par modèle de requête? Combien de performances y at-il à gagner en ouvrant/fermant une connexion une fois sur 3-4 fois dans une requête? Ça vaut le coup? Cela empêche également le chargement différé (lire les requêtes de base de données dans votre vue) de manière beaucoup plus simple.

Désolé si cela est décourageant. Allez-y si vous voyez vraiment l'avantage. Je vous préviens simplement que cela pourrait se retourner très sérieusement si vous vous trompez, alors soyez prévenus. Quelque chose comme profileur d'entité sera d'une aide précieuse pour bien faire les choses - il vous indique le nombre de connexions ouvertes et fermées - entre autres choses très utiles.

2
BritishDeveloper

J'ai vu question et réponse il y a quelques fois. C'est daté. Unity.MVC3 a le gestionnaire de durée de vie en tant que HierarchicalLifetimeManager.

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

et ça marche bien.

2
Nuri YILMAZ

Je proposerais de le résoudre comme ceci: http://forums.asp.net/t/1644386.aspx/1

Meilleures salutations

1
Dzenan

Dans Unity3, si vous souhaitez utiliser 

PerRequestLifetimeManager

Vous devez vous inscrire UnityPerRequestHttpModule

Je le fais en utilisant WebActivatorEx, le code est comme ci-dessous:

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
Yang Zhang

J'ai résolu ce problème en utilisant Castle.DynamicProxy. Il fallait que certaines dépendances soient injectées "à la demande", ce qui signifie qu'elles devaient être résolues au moment de l'utilisation, pas au moment de la création du "Dépendeur".

Pour ce faire, je configure mon conteneur comme suit:

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

L'idée étant que je fournis une méthode pour récupérer l'instance "à la demande". Le lambda est invoqué chaque fois que l'une des méthodes de l'instance est utilisée. L'objet Dépendant contient en réalité une référence à un objet traité par proxy, pas à l'objet lui-même.

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
Ben Grabkowitz

PerRequestLifetimeManager et UnityPerRequestHttpModule les classes sont dans Unity.Mvc package qui dépend de ASP.NET MVC. Si vous ne voulez pas avoir cette dépendance (par exemple, vous utilisez l'API Web), vous devrez les copier-coller dans votre application.

Si vous faites cela, n'oubliez pas d'inscrire le HttpModule.

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

Edit: Je vais inclure les classes ici avant que CodePlex ne ferme:

// 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
Ufuk Hacıoğulları