0

私はカスタムメイドの CMS と Web サイトで忙しく働いています。CMS と Web サイトは両方とも同じデータレイヤーを共有し、データレイヤーは作業単位ストアと組み合わせた汎用リポジトリ パターンを使用しています。

この問題は、単純なテキスト行を含む Web サイトのページを表示しているときに発生します。このテキスト行は CMS で編集されます。このテキスト行を編集して CMS に保存すると、エンティティが変更されていることがわかり、データベースの変更もわかります。ただし、CMS を介してこの小さなテキストを変更し、Web サイトのページを更新した後も、古いテキストが表示されます。

新しい変更されたテキストを表示できるようにする唯一の方法は、IIS を再起動して web.config にスペースを追加し、アプリケーション プールがリサイクルされるように保存することです。したがって、エンティティコンテキストのキャッシュと関係があるように思えます。

以下は、私の汎用リポジトリ コードと作業単位ストア コードです。問題はこれらのいずれかにあり、何らかの形でエンティティ コンテキストのキャッシュを更新する必要があるか、エンティティをリロードする必要があると思います。この問題を解決するにはどうすればよいですか?

情報: 作業単位はこの記事に基づいています。そして、Web サイトと CMS は、ローカルの開発用 PC の別の IIS アプリケーション プールで実行されています。

GenericRepository.cs

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Validation;
using System.Data.Objects;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using CORE.Model;
using CORE.RepositoryInterfaces;

namespace CORE.Repositories
{

    public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
    {
        #region Implementation of IRepository<TEntity>

        //private readonly myContext _context;
        private readonly DbSet<TEntity> _dbSet;

        public GenericRepository()
        {
            //_context = new myContext();
            //_dbSet = _context.Set<TEntity>();

            _dbSet = DataLayer.Instance.Context.Set<TEntity>();
        }

        /// <summary>
        /// Inserts a new object into the database
        /// </summary>
        /// <param name="entity">The entity to insert</param>
        public void Insert(TEntity entity)
        {
            _dbSet.Add(entity);
        }

        /// <summary>
        /// Deletes the specified entity from the database
        /// </summary>
        /// <param name="entity">The object to delete</param>
        public void Delete(TEntity entity)
        {
            if (DataLayer.Instance.Context.Entry(entity).State == System.Data.EntityState.Detached)
            {
                _dbSet.Attach(entity);
            }

            _dbSet.Remove(entity);
        }

        /// <summary>
        /// Saves all pending chances to the database
        /// </summary>
        public void Save()
        {
            try
            {
                DataLayer.Instance.Context.SaveChanges();
            }
            catch (DbEntityValidationException e)
            {
                //foreach (var eve in e.EntityValidationErrors)
                //{
                //    Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
                //        eve.Entry.Entity.GetType().Name, eve.Entry.State);
                //    foreach (var ve in eve.ValidationErrors)
                //    {
                //        Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
                //            ve.PropertyName, ve.ErrorMessage);
                //    }
                //}
                //throw;

                //Log all errors to an temp file.
                //TODO: Implement NLog
                var outputLines = new List<string>();
                foreach (var eve in e.EntityValidationErrors)
                {
                    outputLines.Add(string.Format(
                        "{0}: Entity of type \"{1}\" in state \"{2}\" has the following validation errors:",
                        DateTime.Now, eve.Entry.Entity.GetType().Name, eve.Entry.State));

                    foreach (var ve in eve.ValidationErrors)
                    {
                        outputLines.Add(string.Format(
                            "- Property: \"{0}\", Error: \"{1}\"",
                            ve.PropertyName, ve.ErrorMessage));
                    }
                }
                File.AppendAllLines(@"c:\temp\errors.txt", outputLines);

                throw;
            }
        }

        /// <summary>
        /// Retrieves the first object matching the specified query.
        /// </summary>
        /// <param name="where">The where condition to use</param>
        /// <returns>The first matching object, null of none found</returns>
        public TEntity First(Expression<Func<TEntity, bool>> @where)
        {
            return _dbSet.FirstOrDefault(where);
        }

        /// <summary>
        /// Gets a list of all objects
        /// </summary>
        /// <returns>An strong typed list of objects</returns>
        public IEnumerable<TEntity> GetAll()
        {
            return _dbSet.AsEnumerable();
        }

        /// <summary>
        /// Returns ans iQueryable of the matching type
        /// </summary>
        /// <returns>iQueryable</returns>
        public IQueryable<TEntity> AsQueryable()
        {
            return _dbSet.AsQueryable();
        }

        //public void Dispose()
        //{
        //    DataLayer.Instance.Context.Dispose();
        //    GC.SuppressFinalize(this);
        //}
        #endregion
    }
}

UnitOfWorkStore.CS

using System.Runtime.Remoting.Messaging;
using System.Web;

namespace CORE
{
    /// <summary>
    /// Utility class for storing objects pertinent to a unit of work.
    /// </summary>
    public static class UnitOfWorkStore
    {
        /// <summary>
        /// Retrieve an object from this store via unique key.
        /// Will return null if it doesn't exist in the store.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static object GetData(string key)
        {
            if (HttpContext.Current != null)
                return HttpContext.Current.Items[key];
            return CallContext.GetData(key);
        }


        /// <summary>
        /// Put an item in this store, by unique key.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="data"></param>
        public static void SetData(string key, object data)
        {
            if (HttpContext.Current != null)
                HttpContext.Current.Items[key] = data;
            else
                CallContext.SetData(key, data);
        }

    }
}

DataLayer.cs

using System;
using CORE.Model;

namespace CORE
{
    /// <summary>
    /// This is the data access layer management class.
    /// See 
    /// </summary>
    public sealed class DataLayer : IDisposable
    {
        /// <summary>
        /// This is our key to store an instance of this class in the <see cref="UnitOfWorkStore" />.
        /// This is used in the <see cref="Instance" /> property.
        /// </summary>
        private const string UowInstanceKey = "MyContext_Instance";

        /// <summary>
        /// This is used for thread-safety when creating the instance of this class to be stored in
        /// the UnitOfWorkStore.
        /// </summary>
        private static readonly object SObjSync = new object();

        // The DataContext object
        private readonly MyContext _context;



        // ********************************************************************************
        // *** Constructor(s) *************************************************************
        // ********************************************************************************

        /// <summary>
        /// Default constructor.  Creates a new MyEntities DataContext object.
        /// This is hidden (private) because the instance creation is managed as a "unit-of-work", via the
        /// <see cref="Instance" /> property.
        /// </summary>
        private DataLayer()
        {
            _context = new MyContext();
        }



        // ********************************************************************************
        // *** Public properties **********************************************************
        // ********************************************************************************

        /// <summary>
        /// The ObjectContext object that gives us access to our business entities.
        /// Note that this is NOT static.
        /// </summary>
        public BorloContext Context
        {
            get { return _context; }
        }


        /// <summary>
        /// This will get the "one-and-only" instance of the DataLayer that exists for the lifetime of the current "unit of work",
        /// which might be the lifetime of the currently running console application, a Request/Response iteration of an asp.net web app,
        /// an async postback to a web service, etc.
        /// 
        /// This will never return null.  If an instance hasn't been created yet, accessing this property will create one (thread-safe).
        /// This uses the <see cref="UnitOfWorkStore" /> class to store the "one-and-only" instance.
        /// 
        /// This is the instance that is used by all of the DAL's partial entity classes, when they need a reference to a MyEntities context
        /// (DataLayer.Instance.Context).
        /// </summary>
        public static DataLayer Instance
        {
            get
            {
                object instance = UnitOfWorkStore.GetData(UowInstanceKey);

                // Dirty, non-thread safe check
                if (instance == null)
                {
                    lock (SObjSync)
                    {
                        // Thread-safe check, now that we're locked
                        // ReSharper disable ConditionIsAlwaysTrueOrFalse
                        if (instance == null) // Ignore resharper warning that "expression is always true". It's not considering thread-safety.
                        // ReSharper restore ConditionIsAlwaysTrueOrFalse
                        {
                            // Create a new instance of the DataLayer management class, and store it in the UnitOfWorkStore,
                            // using the string literal key defined in this class.
                            instance = new DataLayer();
                            UnitOfWorkStore.SetData(UowInstanceKey, instance);
                        }
                    }
                }

                return (DataLayer)instance;
            }
        }

        public void Dispose()
        {
            _context.Dispose();
            GC.SuppressFinalize(this);
        }
    }
}

更新 - 1: genericservice による GenericRepository の使用

CMS のサービスでの使用法:

private readonly IGenericService<TextBlock> _textBlockService;

public TextBlockController(): base(new GenericService<ComponentType>(), new GenericService<Blog>())
{
    if (_textBlockService == null)
    {
        _textBlockService = new GenericService<TextBlock>();
    }
}

HTML ヘルパーによるサービス フロントエンドの使用

public static class RenderEngine
{
    private static readonly IGenericService<Component> ComponentService;
    private static readonly IGenericService<TextBlock> TextBlockService;

    /// <summary>
    /// Constructor for this class.
    /// </summary>
    static RenderEngine()
    {
        if (ComponentService == null)
        {
            ComponentService = new GenericService<Component>();
        }
        if (TextBlockService == null)
        {
            TextBlockService = new GenericService<TextBlock>();
        }
    }

    //Html helper method does something like TekstBlokService.First(/*linq statement*/)
}

更新 - 2: genericservice のコードを追加

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using CORE.Repositories;
using CORE.RepositoryInterfaces;
using BLL.ServiceInterfaces;

namespace BLL.Services
{

    public class GenericService<T> : IGenericService<T> where T : class
    {
        #region properties
        protected readonly IGenericRepository<T> MyRepository;
        #endregion

        #region constructor
        //[Inject]
        public GenericService(IGenericRepository<T> repository)
        {
            MyRepository = repository;
        }

        //public GenericService()
        //{
        //    if (Repository == null)
        //    {
        //        Repository = new Repository<T>();
        //    }

        //}

        ////todo: uitzoeken!
        public GenericService()
            : this(new GenericRepository<T>())
        {

        }

        #endregion

        #region Implementation of IService<T>

        /// <summary>
        /// Inserts a new object into the database
        /// </summary>
        /// <param name="entity">The entity to insert</param>
        public void Insert(T entity)
        {
            MyRepository.Insert(entity);
        }

        /// <summary>
        /// Retrieves the first object matching the specified query.
        /// </summary>
        /// <param name="where">The where condition to use</param>
        /// <returns>The first matching object, null of none found</returns>
        public T First(Expression<Func<T, bool>> @where)
        {
            return MyRepository.First(where);
        }

        /// <summary>
        /// Gets a list of all objects
        /// </summary>
        /// <returns>An strong typed list of objects</returns>
        public IEnumerable<T> GetAll()
        {
            return MyRepository.GetAll();
        }

        /// <summary>
        /// Returns ans iQueryable of the matching type
        /// </summary>
        /// <returns>iQueryable</returns>
        public IQueryable<T> AsQueryable()
        {
            return MyRepository.AsQueryable();
        }

        /// <summary>
        /// Deletes the specified entity from the database
        /// </summary>
        /// <param name="entity">The object to delete</param>
        public void Delete(T entity)
        {
            MyRepository.Delete(entity);
        }

        /// <summary>
        /// Saves all pending chances to the database
        /// </summary>
        public void Save()
        {
            MyRepository.Save();
        }

        //protected override void Dispose(bool disposing)
        //{
        //    _movieService.Dispose();
        //    base.Dispose(disposing);
        //}

        #endregion

        //public void Dispose()
        //{
        //    MyRepository.Dispose();
        //    GC.SuppressFinalize(this);
        //}
    }
}
4

2 に答える 2

2

ここで問題は確かに発見されました:

public static class RenderEngine
{
    private static readonly IGenericService<Component> ComponentService;
    private static readonly IGenericService<TextBlock> TextBlockService;

    /// <summary>
    /// Constructor for this class.
    /// </summary>
    static RenderEngine()
    {
        if (ComponentService == null)
        {
            ComponentService = new GenericService<Component>();
        }
        if (TextBlockService == null)
        {
            TextBlockService = new GenericService<TextBlock>();
        }
    }

    //Html helper method does something like TekstBlokService.First(/*linq statement*/)
}

であると仮定するGenericServiceと、次のようにGenericRepositoryなりますGenericRepository

いくつかの静的フィールドを使用してインスタンスを参照し、GenericServiceインスタンスを維持GenericRepositoryします。これは、DBSets含まれているフィールド ( と呼ばれる_dbSet) とそれに関連するを維持しDataContextsます。

GenericRepositoriesクラスはDbSetsからコンストラクターで取得し、二度と更新しないためUnitOfWorkStore、対応する「静的インスタンス」は最初に取得したものと同じものを使用し続け、関連付けられた IIS アプリケーション プールの有効期間全体にわたって持続します。

これは、新しい HTTP リクエストを受信するたびに、静的/の場合と同様に、あなたDatacontextとその関連インスタンスが更新されないことを意味します。実際、ライフタイムを管理するために使用すると、新しい HTTP リクエストごとに新しいライフタイムを作成する必要があることがわかります。(そして、それがあなたが指摘した記事の目的です)。DbSetsGenericServicesGenericRepositoriesHttpContext.Current.ItemsDataContext

要約してみましょう:

  • DataContextstatic GenericServices/によって参照されるインスタンスGenericRespositoriesは、関連する IIS アプリケーション プールが再起動されたときにのみ更新されます (報告したとおり)。
  • GenericRepository「静的インスタンス」は、新しいものではなく常に同じ HTTP 要求を使用するため、エンティティの更新の問題が発生DbSetsDataContextsます。
  • 古いエンティティをフェッチするGenericRepositories「静的インスタンス」とGenericRepositories、最新のデータをフェッチする「新しいインスタンス」(コントローラーから取得) を使用することになります (それらの _dbSet フィールドは、DataContextによって提供される新しいもので満たされているためUnitOfWorkStore)

この問題を解決するには、多くの解決策があります。いくつかを次に示します。

  • 参照DBSetGenericRepositories
  • GenericServicesまたはGenericRepositoriesを静的フィールド/プロパティとして使用しないでください
  • ...そして、静的フィールドが単純に削除されるか、故意に使用される限り、他の多くの

これらすべての仮定が正しい方向に進むのに役立つことを願っています.

于 2013-10-26T16:26:13.910 に答える
0

試すcontext.Entry(<!-- Name -->).Reload();

于 2013-10-26T15:18:12.823 に答える