MVC, EF-DataContext instance unique Per-Web-Request in Unity
j'ai une application web MVC 3, où j'utilise le cadre entité pour l'accès aux données. En outre, j'ai fait une utilisation simple du modèle de dépôt, où par exemple toutes les choses liées au produit sont traitées dans le "ProductRepository" et toutes les choses liées à L'utilisateur sont traitées dans le "UserRepository".
ainsi, j'utilise le conteneur UNITY, pour faire une instance unique du DataContext, que j'injecte dans chacun des dépôts. Une recherche rapide sur Google, et tout le monde vous recommande de ne pas utiliser une seule instance du DataContext, car cela pourrait vous donner des fuites de mémoire dans le futur.
donc, inspiré par ce post, faire une instance unique du DataContext pour chaque requête web est la réponse (s'il vous plaît corriger moi si je me trompe!)
cependant, UNITY ne supporte pas le gestionnaire de durée de vie" par demande sur le web". Mais, il est possible de mettre en œuvre votre propre gestionnaire de vie personnalisé, qui gère cela pour vous. En fait, cela est discuté dans ce post:
contexte unique par appel (Web Request) dans Unity
la question Est, j'ai maintenant mis en œuvre le custom lifetime manager comme décrit dans le post ci-dessus, mais je ne suis pas sûr si c'est la façon de faire il. Je me demande aussi où se trouve l'instance datacontext dans la solution fournie? Ai-je raté quelque chose?
Est-il une meilleure manière de résoudre mon "problème"?
Merci!
* * informations supplémentaires sur ma mise en œuvre * *
voici quelques extraits de mon Global.asax, contrôleur et dépôt. Cela donne une image claire de mon œuvre.
Global.asax
var container = new UnityContainer();
container
.RegisterType<ProductsRepository>(new ContainerControlledLifetimeManager())
.RegisterType<CategoryRepository>(new ContainerControlledLifetimeManager())
.RegisterType<MyEntities>(new PerResolveLifetimeManager(), dbConnectionString)
contrôleur
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 Produit
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;
}
}
information supplémentaire Bonjour, je vais poster des liens supplémentaires que je rencontre, concernant la question connexe et des suggestions de solutions:
- http://cgeers.wordpress.com/2009/02/21/entity-framework-objectcontext/#objectcontext
- http://dotnetslackers.com/articles/ado_net/Managing-Entity-Framework-ObjectContext-lifespan-and-scope-in-n-layered-ASP-NET-applications.aspx
- fixation de linq to sql datacontext de httpcontext dans la couche de gestion
- http://weblogs.asp.net/shijuvarghese/archive/2008/10/24/asp-net-mvc-tip-dependency-injection-with-unity-application-block.aspx
- http://msdn.microsoft.com/en-us/library/bb738470.aspx
9 réponses
Oui ne pas partager contexte et l'utilisation d'un contexte par demande. Vous pouvez également vérifier les questions liées dans ce post pour voir tous les problèmes qu'un contexte partagé a causé.
maintenant à propos de L'Unité. L'idée de PerCallContextLifetimeManager
fonctionne mais je pense que l'implémentation ne fonctionnera pas pour plus d'un objet. Vous devez 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);
}
}
soyez conscient que L'unité ne dispose pas de contexte pour vous. Aussi conscient que l'implémentation par défaut UnityContainer
n'appellera jamais la méthode RemoveValue
.
si votre implémentation résout tous les dépôts en un seul appel Resolve
(par exemple si vos contrôleurs reçoivent des instances de dépôts dans le constructeur et que vous résolvez des contrôleurs), vous n'avez pas besoin de ce gestionnaire de vie. Dans de tels cas, utilisez construire-dans (l'Unité 2.0) PerResolveLifetimeManager
.
Edit:
I voir problème assez grand dans votre configuration fournie de UnityContainer
. Vous enregistrez les deux dépôts avec ContainerControllerLifetimeManager
. Ce gestionnaire de durée de vie signifie une seule instance par durée de vie du conteneur. Cela signifie que les deux dépôts ne seront instanciés qu'une seule fois et que l'instance sera stockée et réutilisée pour des appels ultérieurs. À cause de cela, peu importe ce que lifetime a attribué à MyEntities
. Il est injecté aux constructeurs de dépôts qui ne seront appelés qu'une seule fois. Les deux les dépôts utiliseront toujours cette instance unique de MyEntities
créée pendant leur construction = ils utiliseront une instance unique pendant toute la durée de vie de votre AppDomain
. C'est le pire scénario, vous pouvez atteindre.
réécrivez votre configuration de cette façon:
var container = new UnityContainer();
container
.RegisterType<ProductsRepository>()
.RegisterType<CategoryRepository>()
.RegisterType<MyEntities>(new PerResolveLifetimeManager(), dbConnectionString);
pourquoi cela suffit-il? Vous résolvez controller qui est dépendant des répertoires, mais aucune instance de dépôt n'est nécessaire plus d'une fois de sorte que vous pouvez utiliser la valeur par défaut TransientLifetimeManager
qui créera une nouvelle instance pour chaque appel. À cause de cela, le constructeur du dépôt est appelé et l'instance MyEntities
doit être résolue. Mais vous savez que plusieurs dépôts peuvent avoir besoin de cette instance alors vous la définirez avec PerResolveLifetimeManager
=> chaque résolution de controller ne produira qu'une seule instance de MyEntities
.
à partir de Unity 3, Il y a déjà un gestionnaire lifetime intégré par requête http.
un LifetimeManager qui s'accroche à l'instance qui lui est donnée pendant la durée de vie d'une seule requête HTTP. Ce gestionnaire lifetime vous permet de créer des instances de types enregistrés qui se comportent comme des singletons dans le cadre D'une requête HTTP. Voir les remarques pour d'importantes informations sur l'utilisation.
remarques de MSDN
bien que le gestionnaire perrequestlifetimemanager lifetime manager fonctionne correctement et puisse aider à travailler avec des dépendances stateful ou thread-unsafe dans le cadre D'une requête HTTP, ce n'est généralement pas une bonne idée de l'utiliser quand il peut être évité , car il peut souvent conduire à de mauvaises pratiques ou difficile de trouver des bogues dans le code d'application de l'utilisateur final lorsqu'il est mal utilisé.
il est recommandé que les dépendances que vous enregistrez soient apatrides et s'il y a un besoin de partager un État commun entre plusieurs objets pendant la durée de vie D'une requête HTTP, alors vous pouvez avoir un service apatride qui stocke et récupère explicitement cet état en utilisant la collection D'Articles de l'objet courant.
les remarques disent que même vous êtes forcé d'utiliser un seul contexte par service (service de façade), vous devriez garder vos appels de service apatrides.
Unity 3 est pour .NET 4.5 soit dit en passant.
je crois que l'exemple de code indiqué sur NerdDinner: DI dans MVC en utilisant un pour son HttpContextLifetimeManager
devrait répondre à vos besoins.
Je ne veux pas vous décourager inutilement et par tous les moyens expérimenter mais si vous allez de l'avant et utilisez des instances de singleton de DataContext assurez-vous que vous clouez.
il peut sembler fonctionner correctement sur votre environnement dev mais il pourrait ne 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, les connexions non disputées provoquent d'énormes fuites de mémoire et puis haut CPU essayant d'allouer une nouvelle mémoire.
avez-vous considéré ce que vous gagnez d'une connexion par motif de requête? Combien de performances y a-t-il à gagner à ouvrir/fermer une connexion une fois par exemple 3-4 fois dans une requête? En valeur la dispute? En outre, cela rend le chargement paresseux échoue (lire des requêtes de base de données dans votre vue) beaucoup plus faciles infractions à faire.
désolé si cela s'est avéré décourageant. Vas-y si tu vois vraiment le bénéfice. Je suis je vous préviens que ça pourrait se retourner contre vous si vous vous trompez, alors soyez prévenus. Quelque chose comme " entity profiler sera inestimable pour l'obtenir correctement - il vous indique le nombre de connexions ouvertes et fermées - entre autres choses très utiles.
j'ai vu question et réponse il y a quelques fois. Il est daté. Unité.MVC3 a life time manager comme Hiérarchicallifetimemanager.
container.RegisterType<OwnDbContext>(
"",
new HierarchicalLifetimeManager(),
new InjectionConstructor(connectionString)
);
et ça marche bien.
je proposerais de le résoudre comme ceci: http://forums.asp.net/t/1644386.aspx/1
meilleures salutations
j'ai résolu ceci en utilisant Castle.DynamicProxy. Il fallait que certaines dépendances soient injectées "à la demande", c'est-à-dire qu'elles devaient être résolues au moment de l'utilisation, et non au moment de l'accumulation de "Depender".
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 "sur demande."La lambda est invoquée chaque fois que l'une des méthodes de l'instance est utilisée. L'objet Dépendant est effectivement une référence à un objet proxy, pas de 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);
}
}
dans Unity3, si vous voulez utiliser
PerRequestLifetimeManager
vous devez enregistrer 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();
}
}
}
PerRequestLifetimeManager et UnityPerRequestHttpModule les classes sont dans Unité.MVC paquet qui a une dépendance sur 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 le registre le HttpModule.
Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
Edit: Je vais inclure le classes ici avant CodePlex 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);
}
}
}