web-dev-qa-db-ja.com

MVC、EF-UnityのWebリクエストごとのDataContextシングルトンインスタンス

データアクセスにEntity Frameworkを使用しているMVC 3 Webアプリケーションがあります。さらに、リポジトリパターンを簡単に使用しました。すべての製品関連のものは「ProductRepository」で処理され、すべてのユーザー関連のものは「UserRepository」で処理されます。

したがって、UNITYコンテナを使用して、DataContextのシングルトンインスタンスを作成し、それを各リポジトリに注入します。 Googleで簡単に検索すると、DataContextのシングルトンインスタンスを使用しないことをお勧めします。将来的にメモリリークが発生する可能性があるためです。

したがって、この投稿に触発され、各Webリクエストに対して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」ライフタイムマネージャーをサポートしていません。ただし、これを処理する独自のカスタムライフタイムマネージャーを実装することは可能です。実際、これはこの投稿で説明されています:

nityでの呼び出しコンテキスト(Web要求)ごとのシングルトン

質問は、上記の投稿で説明したように、カスタムライフタイムマネージャーを実装しましたが、これがその方法かどうかはわかりません。また、提供されたソリューションのデータコンテキストインスタンスがどこに配置されているのか疑問に思っていますか?私は何かを逃していますか?

実際に私の「問題」を解決するより良い方法はありますか?

ありがとう!

**実装に関する情報を追加**

以下は、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
49
Nima

はい コンテキストを共有しない およびリクエストごとに1つのコンテキストを使用します。また、その投稿のリンクされた質問をチェックして、共有コンテキストが引き起こしたすべての問題を確認することもできます。

さて、Unityについて。 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で登録しています。このライフタイムマネージャは、コンテナのライフタイムごとにシングルトンインスタンスを意味します。これは、両方のリポジトリが1回だけインスタンス化され、インスタンスが保存され、以降の呼び出しで再利用されることを意味します。そのため、MyEntitiesに割り当てた有効期間は関係ありません。 1回だけ呼び出されるリポジトリのコンストラクタに注入されます。両方のリポジトリは、構築中に作成されたMyEntitiesの単一インスタンスを引き続き使用します= AppDomainのライフタイム全体にわたって単一インスタンスを使用します。それはあなたが達成できる最悪のシナリオです。

設定を次のように書き換えます。

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

なぜこれで十分ですか?リポジトリに依存しているコントローラーを解決していますが、リポジトリインスタンスは必要ないため、呼び出しごとに新しいインスタンスを作成するデフォルトのTransientLifetimeManagerを使用できます。そのため、リポジトリコンストラクターが呼び出され、MyEntitiesインスタンスを解決する必要があります。しかし、あなたは複数のリポジトリがこのインスタンスを必要とすることを知っているので、PerResolveLifetimeManager =>で設定し、コントローラーの各解決はMyEntitiesの1つのインスタンスのみを生成します。

38
Ladislav Mrnka

Unity 3の時点で、httpリクエストごとにライフタイムマネージャーが既に組み込まれています。

PerRequestLifetimeManager

単一のHTTP要求の存続期間中に与えられたインスタンスを保持するLifetimeManager。このライフタイムマネージャを使用すると、HTTPリクエストのスコープ内でシングルトンのように動作する登録済みタイプのインスタンスを作成できます。重要な使用情報については備考をご覧ください。

MSDNによるコメント

PerRequestLifetimeManagerライフタイムマネージャーは正常に動作し、HTTPリクエストのスコープ内でステートフルまたはスレッドセーフでない依存関係を処理するのに役立ちますが、一般的には、可能な場合に使用することはお勧めできません誤った使い方をすると、エンドユーザーのアプリケーションコードのバグの発見やバグの発見が困難になることが多いため、回避

登録する依存関係はステートレスであり、HTTPリクエストのライフタイム中に複数のオブジェクト間で共通の状態を共有する必要がある場合、Itemsコレクションを使用してこの状態を明示的に保存および取得するステートレスサービスを使用することをお勧めします現在のオブジェクト。

サービス(ファサードサービス)ごとに1つのコンテキストを使用することを余儀なくされている場合でも、サービスコールをステートレスに保つ必要があると述べています。

ちなみにUnity 3は.NET 4.5用です。

8
Yorro

NerdDinner:Unityを使用したMVCのDI に示されているサンプルコードは、HttpContextLifetimeManagerがあなたのニーズを満たすはずです。

5
neontapir

私は不必要にあなたを落胆させたくはありませんし、どうしても実験してはいけませんが、先に進んでDataContextのシングルトンインスタンスを使用する場合確かめてください.

開発環境では正常に機能しているように見えますが、接続を適切に閉じることができない場合があります。これは、実稼働環境の負荷なしでは見にくいでしょう。高負荷の本番環境では、接続されていない接続により、大量のメモリリークが発生し、CPUが高くなり、新しいメモリを割り当てようとします。

リクエストごとの接続パターンから得られるものを考慮しましたか?リクエストで3〜4回以上、接続を開いたり閉じたりすることで得られるパフォーマンスはどれくらいですか?面倒な価値はありますか?また、これにより、遅延読み込みが失敗します(ビューでデータベースクエリを読み取ります)。

がっかりした場合は申し訳ありません。本当に利益が見られたら、それを選んでください。私はあなたがそれを間違えたら非常に深刻な裏目に出る可能性があることを警告しているので、警告されます。 エンティティプロファイラ のようなものは、それを正しく取得するために非常に貴重です-それは、他の非常に有用なものの中で、開かれた接続と閉じられた接続の数を示します。

2

数回前に質問と回答を見ました。日付が付けられています。 Unity.MVC3には、HierarchicalLifetimeManagerとしてライフタイムマネージャーがあります。

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

うまくいきます。

2
Nuri YILMAZ

私はこのように解決することを提案します: http://forums.asp.net/t/1644386.aspx/1

宜しくお願いします

1
Dzenan

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
Yang Zhang

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

インスタンスを「オンデマンドで」取得するメソッドを提供するという考え方です。インスタンスのメソッドのいずれかが使用されるたびに、ラムダが呼び出されます。 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);
    }
}
1
Ben Grabkowitz

PerRequestLifetimeManager および nityPerRequestHttpModule クラスは nity.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);
        }
    }
}