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