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;
}
}
Дополнительная информация
Привет, я опубликую дополнительные ссылки, которые мне встречаются, относительно соответствующих вопросов и решений:
Ответы
Ответ 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, кстати.
Ответ 3
Я считаю, что примерный код, показанный на NerdDinner: DI в MVC с использованием Unity для своего HttpContextLifetimeManager
должен отвечать вашим потребностям.
Ответ 4
Я не хочу излишне отговаривать вас и, во что бы то ни стало, экспериментировать, но если вы пойдете вперед и используете экземпляры Singleton DataContext, убедитесь, что вы прибиваете его.
Может показаться, что он отлично работает в среде вашего разработчика, но может быть неправильно закрыть соединения. Это будет трудно увидеть без нагрузки производственной среды. В производственной среде с высокой нагрузкой неразделенные соединения вызовут огромные утечки памяти, а затем высокий процессор, пытаясь выделить новую память.
Считаете ли вы, что вы получаете от соединения на шаблон запроса? Сколько вы хотите получить от открытия/закрытия соединения один раз, скажем, 3-4 раза в запросе? Стоит хлопот? Кроме того, это делает ленивую загрузку неудачной (чтение запросов к базе данных в вашем представлении) намного легче сделать.
Извините, если это натолкнулось на обескураживание. Пойдите для этого, если вы действительно видите выгоду. Я просто предупреждаю вас, что это может серьезно повлиять на вас, если вы ошибетесь, поэтому будьте осторожны. Что-то вроде entity-профайлер будет бесценным для правильного выбора - он сообщает вам, что количество подключений открыто и закрыто - среди других очень полезных вещей.
Ответ 5
Я видел вопрос и ответ несколько раз назад. Это датировано. Unity.MVC3 имеет менеджер времени жизни как HierarchicalLifetimeManager.
container.RegisterType<OwnDbContext>(
"",
new HierarchicalLifetimeManager(),
new InjectionConstructor(connectionString)
);
и он работает хорошо.
Ответ 6
Я бы предложил решить его вот так:
http://forums.asp.net/t/1644386.aspx/1
С наилучшими пожеланиями
Ответ 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);
}
}
}