Подтвердить что ты не робот

MVC, EF - экземпляр SingleContextContext Per-Web-Request в Unity

У меня есть веб-приложение MVC 3, где я использую Entity Framework для доступа к данным. Кроме того, я сделал простое использование шаблона репозитория, где, например, все связанные с продуктом материалы обрабатываются в "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 не поддерживает менеджер жизненного цикла "Per-web-request". Но вы можете реализовать собственный менеджер времени жизни, который обрабатывает это для вас. Собственно, об этом говорится в этом сообщении:

Контекст Singleton Per Call (веб-запрос) в 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();
}

Контроллер 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;
    }

}

Дополнительная информация Привет, я опубликую дополнительные ссылки, которые мне встречаются, относительно соответствующих вопросов и решений:

4b9b3361

Ответ 1

Да не обмениваться контекстом и использовать один контекст для запроса. Вы также можете проверить связанные вопросы в этом сообщении, чтобы увидеть все проблемы, вызванные общим контекстом.

Теперь о Единстве. Идея 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);
    }
}

Помните, что Unity не будет использовать контекст для вас. Также имейте в виду, что реализация по умолчанию UnityContainer никогда не вызовет метод RemoveValue.

Если ваша реализация разрешает все репозитории в одном вызове Resolve (например, если ваши контроллеры получают экземпляры репозиториев в конструкторе и вы разрешаете контроллеры), вам не нужен этот менеджер времени жизни. В этом случае используйте встроенный (Unity 2.0) PerResolveLifetimeManager.

Edit:

Я вижу довольно большую проблему в вашей конфигурации UnityContainer. Вы регистрируете оба хранилища с помощью ContainerControllerLifetimeManager. Этот менеджер времени жизни означает экземпляр Singleton на время жизни контейнера. Это означает, что оба репозитория будут создаваться только один раз, и экземпляр будет сохранен и повторно использован для последующих вызовов. Из-за этого не имеет значения, какое время жизни вы назначили MyEntities. Он вводится в конструкторы репозиториев, которые будут вызываться только один раз. Оба хранилища будут использовать еще один единственный экземпляр MyEntities, созданный во время их построения = они будут использовать один экземпляр для всего срока службы вашего AppDomain. Это худший вариант, который вы можете достичь.

Перепишите конфигурацию следующим образом:

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

Почему этого достаточно? Вы разрешаете контроллер, который зависит от репитеров, но экземпляр репозитория не требуется более одного раза, поэтому вы можете использовать default TransientLifetimeManager, который будет создавать новый экземпляр для каждого вызова. Из-за этого создается конструктор репозитория и экземпляр MyEntities должен быть разрешен. Но вы знаете, что нескольким репозиториям может понадобиться этот экземпляр, поэтому вы установите его с помощью PerResolveLifetimeManager = > при каждом разрешении контроллера будет создан только один экземпляр MyEntities.

Ответ 2

Как и Unity 3, для каждого HTTP-запроса уже есть встроенный менеджер времени жизни.

PerRequestLifetimeManager

A LifetimeManager, который хранится в экземпляре, предоставленном ему в течение всего жизненного цикла одного HTTP-запроса. Этот менеджер времени жизни позволяет создавать экземпляры зарегистрированных типов, которые ведут себя как одиночные точки в рамках HTTP-запроса. См. Примечания для важной информации об использовании.

Замечания по MSDN

Хотя менеджер жизненного цикла PerRequestLifetimeManager работает правильно и может помочь в работе с зависимыми от состояния или потоками небезопасными в рамках HTTP-запроса, , как правило, не рекомендуется использовать его, когда его можно избежать, так как это часто приводит к плохой практике или трудно найти ошибки в коде приложения конечного пользователя при неправильном использовании.

Рекомендуется, чтобы зависящие от вас зависимости были безстоящими и если существует необходимость совместного использования общего состояния между несколькими объектами в течение всего жизненного цикла HTTP-запроса, тогда у вас может быть служба без состояния, которая явно сохраняет и извлекает это состояние, используя Коллекция элементов текущего объекта.

В комментариях говорится, что даже вы вынуждены использовать один контекст для службы (услугу фасада), вы должны поддерживать, чтобы ваши вызовы службы были безстоящими.

Unity 3 для .NET 4.5, кстати.

Ответ 4

Я не хочу излишне отговаривать вас и, во что бы то ни стало, экспериментировать, но если вы пойдете вперед и используете экземпляры Singleton DataContext, убедитесь, что вы прибиваете его.

Может показаться, что он отлично работает в среде вашего разработчика, но может быть неправильно закрыть соединения. Это будет трудно увидеть без нагрузки производственной среды. В производственной среде с высокой нагрузкой неразделенные соединения вызовут огромные утечки памяти, а затем высокий процессор, пытаясь выделить новую память.

Считаете ли вы, что вы получаете от соединения на шаблон запроса? Сколько вы хотите получить от открытия/закрытия соединения один раз, скажем, 3-4 раза в запросе? Стоит хлопот? Кроме того, это делает ленивую загрузку неудачной (чтение запросов к базе данных в вашем представлении) намного легче сделать.

Извините, если это натолкнулось на обескураживание. Пойдите для этого, если вы действительно видите выгоду. Я просто предупреждаю вас, что это может серьезно повлиять на вас, если вы ошибетесь, поэтому будьте осторожны. Что-то вроде entity-профайлер будет бесценным для правильного выбора - он сообщает вам, что количество подключений открыто и закрыто - среди других очень полезных вещей.

Ответ 5

Я видел вопрос и ответ несколько раз назад. Это датировано. Unity.MVC3 имеет менеджер времени жизни как HierarchicalLifetimeManager.

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

и он работает хорошо.

Ответ 7

Я решил это, используя 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 фактически содержит ссылку на прокси-объект, без самого объекта.

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

Ответ 8

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

Ответ 9

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