MVC, EF - экземпляр Singleton DataContext для каждого веб-запроса в 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 не поддерживает менеджер времени жизни для каждого веб-запроса. Но можно реализовать свой собственный менеджер времени жизни, который обрабатывает это для вас. На самом деле, это обсуждается в этом посте:

Синглтон на контекст вызова (веб-запрос) в 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/
  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

9 ответов

Решение

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

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

Редактировать:

Я вижу довольно большую проблему в предоставленной вами конфигурации 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 3, для http-запроса уже есть встроенный менеджер времени жизни.

PerRequestLifetimeManager

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

Замечания MSDN

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

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

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

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

Я считаю, что пример кода, показанного на NerdDinner: DI в MVC, используя Unity для его HttpContextLifetimeManager должен соответствовать вашим потребностям.

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

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

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

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

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

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

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

Я бы предложил решить эту проблему так: http://forums.asp.net/t/1644386.aspx/1

С наилучшими пожеланиями

Я решил это с помощью Castle.DynamicProxy. Мне нужно было вводить определенные зависимости "по требованию", то есть их нужно было разрешать во время использования, а не во время создания "зависимого устройства".

Для этого я настраиваю свой контейнер так:

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

Идея в том, что я предоставляю метод для извлечения экземпляра "по требованию". Лямбда вызывается всякий раз, когда используется какой-либо из методов экземпляра. Зависимый объект фактически содержит ссылку на прокси-объект, а не сам объект.

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

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

Классы 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'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);
        }
    }
}
Другие вопросы по тегам