asp.net-mvc-3 - MVC، EF-DataContext singleton instance Per-Web-Request in Unity




entity-framework unity-container ioc-container (9)

لدي تطبيق MVC 3 على الويب ، حيث أستخدم إطار الكيان للوصول إلى البيانات. علاوة على ذلك ، لقد قمت باستخدام بسيط لنمط المستودع ، حيث يتم التعامل مع كل الأشياء المتعلقة بالمنتج في "ProductRepository" ويتم التعامل مع جميع الأشياء المتعلقة بالمستخدم في "UserRepository".

وبالتالي ، أستخدم حاوية UNITY ، لإنشاء مثيل مفرد لـ DataContext ، والذي أحقن به في كل مستودعات التخزين. بحث سريع على Google ، ويوصي الجميع بعدم استخدام مثيل مفرد لـ DataContext ، حيث قد يعطيك بعض التسريبات في الذاكرة في المستقبل.

لذلك ، مستوحاة من هذا المنشور ، فإن إنشاء نسخة مفردة من DataContext لكل طلب على الويب هو الحل (يرجى تصحيح ذلك إذا كنت مخطئًا!)

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

ومع ذلك ، لا تدعم UNITY مدير مدى "طلب الويب حسب الطلب". ولكن ، من الممكن تنفيذ مدير المدة المخصصة لك ، والذي يعالج ذلك لك. في الواقع ، تمت مناقشة هذا في هذا المنصب:

سينجلتون لكل اتصال السياق (طلب الويب) في الوحدة

السؤال هو ، لقد قمت الآن بتنفيذ مدير عمر مخصص كما هو موضح في المشاركة أعلاه ، ولكن أنا غير متأكد إذا كان هذا هو الطريق للقيام بذلك. أنا أيضا أتساءل عن حيث يتم التخلص من نسخة datacontext في الحل المقدم؟ هل أفتقد شيء؟

هل هناك بالفعل طريقة أفضل لحل "مشكلتي"؟

شكر!

** معلومات إضافية حول تنفيذي **

فيما يلي مقتطفات من Global.asax و Controller و Repository. هذا يعطي صورة واضحة عن التنفيذ.

Global.asax

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

مراقب

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

مستودع المنتجات

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

مصنع تحكم

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

}

معلومات إضافية مرحبًا ، سأقوم بنشر روابط إضافية أجدها ، فيما يتعلق بالمشكلات ذات الصلة والحلول ذات الصلة:

  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. إرفاق linq إلى sql datacontext إلى httpcontext في طبقة الأعمال
  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

Answers

PerRequestLifetimeManager فئات UnityPerRequestHttpModule و UnityPerRequestHttpModule في حزمة Unity.Mvc التي لها تبعية على ASP.NET MVC. إذا كنت لا ترغب في الحصول على تلك التبعية (على سبيل المثال أنك تستخدم Web API) ، فسيتعين عليك نسخها ولصقها في تطبيقك.

إذا قمت بذلك ، لا تنسى تسجيل HttpModule.

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

تعديل: سأدرج الفئات هنا قبل إيقاف CodePlex:

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


أنا حل هذا باستخدام Castle.DynamicProxy. كنت بحاجة إلى أن يتم حقن بعض التبعيات "عند الطلب" بمعنى أنها بحاجة إلى حلها في وقت الاستخدام ، وليس في وقت بناء "Depender".

للقيام بذلك أقوم بتكوين الحاوية الخاصة بي مثل:

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

الفكرة هي أنني أقدم طريقة لاسترداد المثيل "عند الطلب". يتم استدعاء لامدا كلما تم استخدام أي من أساليب المثال. الكائن Dependent يحمل بالفعل مرجع إلى كائن proxied ، لا الكائن نفسه.

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

اعتبارًا من Unity 3 ، يوجد بالفعل مدير عمر مضمن لكل طلب http.

PerRequestLifetimeManager

A LifetimeManager الذي يحتفظ بالمثيل المعطى له خلال فترة عمر طلب HTTP واحد. يمكّنك هذا المدير مدى الحياة من إنشاء مثيلات للأنواع المسجلة التي تتصرف مثل مفردات ضمن نطاق طلب HTTP. انظر الملاحظات لمعلومات الاستخدام الهامة.

ملاحظات MSDN

على الرغم من أن مدير عمر PerRequestLifetimeManager يعمل بشكل صحيح ويمكن أن يساعد في العمل مع تبعيات stateful أو non-unsafe ضمن نطاق طلب HTTP ، فإنه عادة ليس فكرة جيدة لاستخدامه عندما يمكن تجنبه ، لأنه غالباً ما يؤدي إلى سيئة الممارسات أو يصعب العثور على الأخطاء في رمز تطبيق المستخدم النهائي عند استخدامها بشكل غير صحيح.

من المستحسن أن تكون الاعتمادات التي تسجلها عديمة الحالة ، وإذا كانت هناك حاجة لمشاركة حالة مشتركة بين كائنات متعددة خلال فترة عمر طلب HTTP ، فيمكنك حينئذٍ الحصول على خدمة بلا حالة تخزن هذه الحالة بشكل صريح وتستردها باستخدام مجموعة عناصر الكائن الحالي.

تقول هذه الملاحظات أنه حتى إذا أجبرت على استخدام سياق واحد لكل خدمة (خدمة واجهة) ، فيجب عليك أن تجعل اتصالاتك عديمة الجدوى.

الوحدة 3 هي ل. NET 4.5 بالمناسبة.


لا أريد أن أثنيك عن غير قصد ، وبكل الوسائل ، ولكن إذا قمت بالمضي قدمًا في استخدام الأمثلة الفردية لـ DataContext ، فتأكد من أنك قمت بتثبيتها.

يمكن أن يبدو أنه يعمل بشكل جيد في بيئة التطوير الخاصة بك ولكن قد يكون فشل في إغلاق الاتصالات بشكل صحيح. سيكون من الصعب رؤية هذا دون تحميل بيئة الإنتاج. في بيئة إنتاج ذات حمولة عالية ، سوف تتسبب الاتصالات غير المرئية في حدوث تسرب للذاكرة كبير ثم وحدة CPU عالية تحاول تخصيص ذاكرة جديدة.

هل فكرت في ما تربحه من اتصال لكل نمط طلب؟ ما هو مقدار الأداء الذي يمكن الحصول عليه من فتح / إغلاق الاتصال مرة واحدة على 3-4 مرات في الطلب؟ يستحق الازعاج؟ كما أن هذا يجعل عملية التحميل البطيئة تفشل (اقرأ استعلامات قاعدة البيانات في العرض الخاص بك) من المخالفات الأسهل بكثير.

عذرا إذا جاء هذا عبر تثبيط. الذهاب لذلك إذا كنت حقا ترى فائدة. أنا فقط أحذرك من أنه يمكن أن يأتي بنتائج عكسية على محمل الجد إذا كنت أخطأت حتى يتم تحذيرك. شيء من هذا القبيل كمنشور كيان سيكون لا يقدر بثمن للحصول على الحق في ذلك - أنه يخبرك عدد الاتصالات المفتوحة والمغلقة - من بين أمور أخرى مفيدة للغاية.


نعم لا تقم بمشاركة السياق واستخدام سياق واحد لكل طلب. يمكنك أيضًا التحقق من الأسئلة المرتبطة في تلك المشاركة لمشاهدة جميع المشكلات التي تسبب فيها سياق مشترك.

الآن عن الوحدة. تعمل فكرة PerCallContextLifetimeManager ولكن أعتقد أن التنفيذ المقدم لن يعمل لأكثر من كائن واحد. يجب عليك استخدام PerHttpRequestLifetimeManager مباشرة:

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

كن على علم بأن الوحدة لن تتخلص من السياق بالنسبة لك. UnityContainer أيضًا أن تطبيق UnityContainer الافتراضي لن يطلق أبدًا طريقة RemoveValue .

إذا كان التنفيذ الخاص بك يحل جميع مستودعات التخزين في استدعاء Resolve واحد (على سبيل المثال ، إذا كانت وحدات التحكم الخاصة بك تتلقى مثيلات لمستودعات التخزين في وحدة التحكم وكنت تقوم بحل وحدات التحكم) ، فإنك لا تحتاج إلى هذا المدير مدى الحياة. في مثل هذه الحالة استخدام البناء في (الوحدة 2.0) PerResolveLifetimeManager .

تصحيح:

أرى مشكلة كبيرة جدًا في التهيئة المقدمة من UnityContainer . تقوم بتسجيل كلا المستودعات مع ContainerControllerLifetimeManager . يعني هذا المدير مدى الحياة مثيل Singleton لكل حاوية عمر. ويعني ذلك أنه سيتم إنشاء المستودعين مرة واحدة فقط وسيتم تخزين المثيل وإعادة استخدامه للمكالمات اللاحقة. وبسبب ذلك لا يهم أي عمر قمت بتعيينه إلى MyEntities . يتم حقنها لمصانع المستودعات التي سيتم استدعاؤها مرة واحدة فقط. سيستخدم كلا المستودعين تلك الحالة الوحيدة من MyEntities تم إنشاؤها أثناء الإنشاء = سوف يستخدمون مثيل واحد طوال عمر AppDomain الخاص بك. هذا أسوأ سيناريو يمكنك تحقيقه.

أعد كتابة التهيئة بهذه الطريقة:

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

لماذا هذا كافي؟ تقوم بحل وحدة التحكم التي تعتمد على وحدات تخزين ولكن لا يوجد حاجة إلى مثيل مستودع أكثر من مرة واحدة حتى تتمكن من استخدام الافتراضي TransientLifetimeManager الذي سيخلق مثيل جديد لكل مكالمة. وبسبب ذلك يتم استدعاء منشئ مستودع ويجب أن يتم حل مثيل MyEntities . ولكنك تعلم أن المستودعات المتعددة قد تحتاج إلى هذا المثيل لذا ستقوم بتعيينه مع PerResolveLifetimeManager => كل حل من أجهزة التحكم سوف ينتج عنه مثيل واحد فقط من MyEntities .


رأيت سؤال وجواب منذ عدة مرات. إنه مؤرخ. يحتوي Unity.MVC3 على مدير وقت الحياة مثل HierarchicalLifetimeManager.

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

وانها تعمل لطيفة.


في Unity3 ، إذا كنت تريد استخدامه

PerRequestLifetimeManager

تحتاج إلى تسجيل UnityPerRequestHttpModule

أفعل هذا عن طريق استخدام WebActivatorEx ، الكود على النحو التالي:

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

أنا أتفق مع الآراء السابقة. من الجيد أن تقول ، إذا كنت ستشارك DbContext في تطبيق واحد ، فسوف تحتاج إلى المزيد من الذاكرة. على سبيل المثال ، يحتاج تطبيق الويب الخاص بي على Azure (مثيل صغير إضافي) إلى 150 ميغابايت أخرى من الذاكرة ولدي حوالي 30 مستخدمًا في الساعة.

هنا مثال حقيقي للصورة: تم نشر التطبيق في 12PM





asp.net-mvc-3 entity-framework unity-container ioc-container