0

I'm busy working on a custom made CMS and website. The CMS and website both share the same datalayer and the datalayer is using an generic repository pattern combined with a Unit of Work store.

The problem occurs when i'm viewing a page of the website having a simple line of text on it. This line of text is edited in the CMS. When editing this line of text and saving it in the CMS I see the entity being modified and I also see the changes in the database. However after changing this small piece of text through the CMS and then refreshing the website page i'm still being presented with the old text.

The only way for me to be able to show the new, changed text is to restart IIS of add an space in the web.config and save it so the application pool is recycled. So it seems to me it has something to do with the caching of the entity context.

Below is my generic repository code and my unit-of-work-store code. I think the problem is in one of these and that somehow the cache of the entity context needs to be renewed or that entities needs to be reloaded. What can I possibly do to solve this issue?

Info: The Unit of work is based on this article. And the website and CMS are running in an seperate IIS application pool on my local development pc

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

UPDATE - 1: Usage of GenericRepository through genericservice

The usage in the service of the CMS:

private readonly IGenericService<TextBlock> _textBlockService;

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

The usage of the service frontend, through an HTML helper

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*/)
}

UPDATE - 2: Added code for 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

Problem certainly spotted, in here :

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*/)
}

Assuming GenericService is GenericRepository or holds GenericRepository :

Using somes static fields to reference your GenericService instances keep alive GenericRepository instances which in turn, keep alive DBSets contained fields (called _dbSet) and their related DataContexts.

As GenericRepositories classes get their DbSets in their constructor from the UnitOfWorkStore and never update them one again, the corresponding "static instances" will keep using the sames as the first ones they get and these will last during the entire lifetime of the associated IIS Application Pool.

Which means your Datacontext and its related DbSets instances are not renewed each time a new HTTP request is received as they should be for static GenericServices/GenericRepositories. Indeed, using HttpContext.Current.Items to manage DataContext lifetime suggests that what you want to do is creating a new one for each new HTTP request. (and that is the purpose of the article you pointed out).

Let's try to summarize :

  • DataContext instances referenced by static GenericServices/GenericRespositories will be renewed only when the associated IIS Application Pool is restarted (just as you reported).
  • GenericRepository "static instances" always uses the same DbSets and DataContexts across HTTP requests instead of new ones, which cause your entities refresh issue.
  • You end up by using GenericRepositories "static instances" fetching old entities and GenericRepositories "fresh instances" (coming from your controllers) fetching up to date data (because their _dbSet fields are filled with a new DataContext provided by your UnitOfWorkStore)

To fix the problem there are many solutions, here are a few :

  • Do not save DBSet references into your GenericRepositories
  • Do not use GenericServices or GenericRepositories as static field/properties
  • ... and lots of others, as far as static fields are simply removed or used knowingly

Hope all these assumptions will help move in the right direction.

于 2013-10-26T16:26:13.910 回答
0

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

于 2013-10-26T15:18:12.823 回答