wake-up-neo.net

MVC, EF - DataContext-Singleton-Instanz Pro-Web-Anforderung in Unity

Ich habe eine MVC 3-Webanwendung, bei der ich das Entity Framework für den Datenzugriff verwende. Außerdem habe ich das Repository-Muster auf einfache Weise verwendet, wobei z. Alle produktbezogenen Inhalte werden im "ProductRepository" und alle benutzerbezogenen Daten werden im "UserRepository" behandelt. 

Daher verwende ich den UNITY-Container, um eine Einzelinstanz des DataContext zu erstellen, die ich in jedes der Repositorys einfüge. Eine schnelle Suche bei Google, und jeder empfiehlt Ihnen, KEINE Singleton-Instanz von DataContext zu verwenden, da dies in der Zukunft zu Speicherverlusten führen kann. 

Inspiriert durch diesen Post ist es also die Antwort, eine einzelne Instanz des DataContext für jede Webanfrage zu erstellen (bitte korrigieren Sie mich, wenn ich falsch liege!)

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

UNITY unterstützt jedoch nicht den Lebenszeit-Manager "Per-Web-Anforderung". Es ist jedoch möglich, einen eigenen benutzerdefinierten Lifetime Manager zu implementieren, der dies für Sie erledigt. Eigentlich wird das in diesem Beitrag diskutiert: 

Singleton-Pro-Call-Kontext (Web-Anfrage) in Unity

Die Frage ist, ich habe jetzt den benutzerdefinierten Lifetime-Manager wie im obigen Beitrag beschrieben implementiert, aber ich bin nicht sicher, ob dies der richtige Weg ist. Ich frage mich auch, wo die Datacontext-Instanz in der bereitgestellten Lösung enthalten ist. Verpasse ich etwas?

Gibt es tatsächlich einen besseren Weg, mein "Problem" zu lösen?

Vielen Dank!

** Informationen zu meiner Implementierung hinzugefügt **

Das Folgende ist ein Ausschnitt aus meinem Global.asax, Controller und Repository. Dies gibt ein klares Bild meiner Implementierung. 

Global.asax

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

Controller 

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

Produktablage

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

}

Zusatzinformation Hallo, ich werde zusätzliche Links zu diesem Thema und zu Lösungsvorschlägen posten: 

  1. http://cgeers.wordpress.com/2009/02/21/entity-framework-objectcontext/#objectcontext
  2. http://dotnetslackers.com/articles/ado_net/Management-Entity-Framework-ObjectContext-lifespan-and-scope-in-n-layered-ASP-NET-applications.aspx
  3. linq an sql-datacontext an httpcontext in der Business-Schicht anhängen
  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/de-de/library/bb738470.aspx
49
Nima

Ja Kontext nicht teilen und pro Kontext einen Kontext verwenden. Sie können auch verknüpfte Fragen in diesem Beitrag überprüfen, um alle Probleme zu sehen, die ein freigegebener Kontext verursacht hat.

Nun zu Einheit. Idee von PerCallContextLifetimeManager funktioniert, aber ich denke, dass die Implementierung nur für ein Objekt funktionieren wird. Sie sollten PerHttpRequestLifetimeManager direkt verwenden:

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

Beachten Sie, dass Unity Ihnen keinen Kontext zur Verfügung stellt. Beachten Sie auch, dass die Standardimplementierung UnityContainer niemals die Methode RemoveValue aufrufen wird. 

Wenn Ihre Implementierung alle Repositorys in einem einzigen Aufruf von Resolve auflöst (wenn beispielsweise Ihre Controller Instanzen von Repositorys im Konstruktor erhalten und Sie Controller auflösen), benötigen Sie diesen Lifetime-Manager nicht. Verwenden Sie in einem solchen Fall das Build-In (Unity 2.0) PerResolveLifetimeManager

Bearbeiten:

Ich sehe ein ziemlich großes Problem in Ihrer bereitgestellten Konfiguration von UnityContainer. Sie registrieren beide Repositorys mit ContainerControllerLifetimeManager. Dieser Lebensdauermanager bedeutet die Singleton-Instanz pro Containerlebensdauer. Dies bedeutet, dass beide Repositorys nur einmal instanziiert werden und die Instanz gespeichert und für nachfolgende Aufrufe wiederverwendet wird. Daher spielt es keine Rolle, welche Lebensdauer Sie MyEntities zugewiesen haben. Es wird den Konstruktoren von Repositories injiziert, die nur einmal aufgerufen werden. Beide Repositorys verwenden immer noch die einzelne Instanz von MyEntities, die während ihrer Erstellung erstellt wurde = sie verwenden die einzige Instanz für die gesamte Lebensdauer Ihrer AppDomain. Das ist das schlimmste Szenario, das Sie erreichen können.

Schreiben Sie Ihre Konfiguration folgendermaßen um:

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

Warum ist das genug? Sie lösen einen Controller auf, der von Repositorys abhängig ist. Eine Repository-Instanz wird jedoch nur einmal benötigt, sodass Sie die Standardvariable TransientLifetimeManager verwenden können, die für jeden Aufruf eine neue Instanz erstellt. Aufgrund dessen wird das Repository-Konstruktor aufgerufen und die MyEntities-Instanz muss aufgelöst werden. Sie wissen jedoch, dass mehrere Repositorys diese Instanz benötigen können, so dass Sie sie mit PerResolveLifetimeManager => festlegen. Jede Auflösung des Controllers erzeugt nur eine Instanz von MyEntities.

38
Ladislav Mrnka

Ab Unity 3 gibt es bereits einen integrierten Lifetime-Manager pro HTTP-Anforderung. 

PerRequestLifetimeManager

Ein LifetimeManager, der sich auf die Instanz bezieht, die ihm während der Lebensdauer einer einzelnen HTTP-Anforderung zugewiesen wurde. Mit diesem Lifetime-Manager können Sie Instanzen registrierter Typen erstellen, die sich im Rahmen einer HTTP-Anforderung wie Singletons verhalten. Wichtige Hinweise zur Verwendung finden Sie in den Hinweisen.

Anmerkungen von MSDN

Obwohl der Lebenszeit-Manager von PerRequestLifetimeManager ordnungsgemäß funktioniert und bei Zuständen mit Zustandsabhängigkeit oder Thread-unsicherem Verhalten innerhalb einer HTTP-Anforderung helfen kann, ist es ist im Allgemeinen keine gute Idee, es zu verwenden, wenn es vermieden werden kann, as Dies kann bei fehlerhafter Verwendung oft zu unlauteren Vorgehensweisen oder schwer zu findenden Fehlern im Anwendungscode des Endbenutzers führen. 

Es wird empfohlen, dass die von Ihnen registrierten Abhängigkeiten zustandslos sind. Wenn während der Lebensdauer einer HTTP-Anforderung der gemeinsame Status von mehreren Objekten gemeinsam genutzt werden muss, können Sie über einen zustandslosen Dienst verfügen, der diesen Status mithilfe der Items-Auflistung von explizit speichert und abruft das aktuelle Objekt.

In den Anmerkungen heißt es, dass selbst Sie gezwungen sind, einen einzigen Kontext pro Dienst (Fassadendienst) zu verwenden, Sie sollten Ihre Dienstanrufe jedoch statusfrei halten.

Unity 3 ist übrigens für .NET 4.5.

8
Yorro

Ich glaube, dass der Beispielcode auf NerdDinner: DI in MVC mit Unity für seine HttpContextLifetimeManager Ihren Anforderungen entsprechen sollte.

5
neontapir

Ich möchte Sie nicht unnötig entmutigen und auf jeden Fall experimentieren, aber wenn Sie fortfahren und Singleton-Instanzen von DataContext stellen Sie sicher verwenden, dann machen Sie das richtig.

In Ihrer Entwicklungsumgebung scheint das einwandfrei zu funktionieren, aber möglicherweise werden die Verbindungen nicht ordnungsgemäß geschlossen. Ohne die Last einer Produktionsumgebung ist dies schwer zu erkennen. In einer Produktionsumgebung mit hoher Auslastung verursachen ungenutzte Verbindungen große Speicherverluste und dann eine hohe CPU, die versucht, neuen Speicher zuzuweisen.

Haben Sie überlegt, was Sie von einer Verbindung pro Anfragemuster erhalten? Wie viel Leistung kann durch das Öffnen/Schließen einer Verbindung erzielt werden, wenn in einer Anforderung drei bis vier Mal gesprochen wird? Den Aufwand wert? Dies führt auch dazu, dass das langsame Laden (Lesen von Datenbankabfragen in Ihrer Ansicht) erheblich einfacher ist.

Entschuldigung, wenn dies entmutigend war. Machen Sie sich bereit, wenn Sie den Nutzen wirklich sehen. Ich warne Sie nur davor, dass es ernsthaft zurückschlagen könnte, wenn Sie es falsch verstehen, seien Sie also gewarnt. Etwas wie entity profiler ist von unschätzbarem Wert, um es richtig zu machen - es zeigt Ihnen die Anzahl der geöffneten und geschlossenen Verbindungen an - unter anderem sehr nützliche Dinge.

2

Ich habe vor ein paar Mal Fragen und Antworten gesehen. Es ist datiert. Unity.MVC3 hat einen Lebenszeit-Manager als HierarchicalLifetimeManager.

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

und es funktioniert schön.

2
Nuri YILMAZ

Ich würde vorschlagen, es folgendermaßen zu lösen: http://forums.asp.net/t/1644386.aspx/1

Freundliche Grüße

1
Dzenan

In Unity3, wenn Sie verwenden möchten 

PerRequestLifetimeManager

Sie müssen sich registrieren UnityPerRequestHttpModule

Ich mache dies mit WebActivatorEx, der Code ist wie folgt:

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

Ich habe dies mit Castle.DynamicProxy gelöst. Ich musste bestimmte Abhängigkeiten "On Demand" hinzufügen, was bedeutet, dass sie zum Zeitpunkt der Verwendung aufgelöst werden mussten, nicht zum Zeitpunkt des "Depender" -Aufbaus.

Dazu konfiguriere ich meinen Container so:

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

Die Idee ist, dass ich eine Methode zum Abrufen der Instanz "auf Anforderung" bereitstelle. Das Lambda wird immer dann aufgerufen, wenn eine der Methoden der Instanz verwendet wird. Das abhängige Objekt enthält tatsächlich einen Verweis auf ein Proxyobjekt, nicht das Objekt selbst.

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 und UnityPerRequestHttpModule Klassen sind im Unity.Mvc-Paket , das von ASP.NET MVC abhängig ist. Wenn Sie diese Abhängigkeit nicht wünschen (z. B. Sie verwenden die Web-API), müssen Sie sie in Ihre App kopieren und einfügen.

Wenn Sie dies tun, vergessen Sie nicht, das HttpModule zu registrieren.

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

Edit: Ich füge die Klassen hier ein, bevor CodePlex herunterfährt:

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