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




asp.net-mvc-3 entity-framework (6)

لدي تطبيق 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



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

PerRequestLifetimeManager

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

ملاحظات MSDN

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

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

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

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


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

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

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


لا أريد أن أثنيك عن غير قصد ، وبكل الوسائل ، ولكن إذا قمت بالمضي قدمًا في استخدام الأمثلة الفردية لـ 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 .





ioc-container