MVC, EF - экземпляр Singleton DataContext для каждого веб-запроса в Unity - PullRequest
49 голосов
/ 04 марта 2011

У меня есть веб-приложение 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/#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 ]

38 голосов
/ 04 марта 2011

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

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

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

.
8 голосов
/ 20 декабря 2013

Начиная с Unity 3, для http-запроса уже есть встроенный менеджер времени жизни.

PerRequestLifetimeManager

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

Замечания MSDN

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

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

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

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

5 голосов
/ 04 марта 2011

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

2 голосов
/ 15 декабря 2011

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

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

, и он прекрасно работает.

2 голосов
/ 04 марта 2011

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

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

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

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

1 голос
/ 18 августа 2015

В 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();
        }
    }
}
1 голос
/ 30 марта 2015

Я решил это с помощью 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)));
 }

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

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 голос
/ 16 января 2013

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

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

0 голосов
/ 24 февраля 2017

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