[英]Entity framework with generic repository and Unit of Work store showing old data
我正忙着定制CMS和網站。 CMS和網站共享相同的數據層,數據層使用通用存儲庫模式與工作單元存儲。
當我正在查看網站上有簡單文本行的頁面時,就會出現問題。 此行文本在CMS中編輯。 編輯此行文本並將其保存在CMS中時,我看到正在修改的實體,我也看到了數據庫中的更改。 但是,在通過CMS更改這一小段文本然后刷新網站頁面后,我仍然會看到舊文本。
我能夠顯示新的,更改的文本的唯一方法是重新啟動IIS,在web.config中添加一個空格並保存它,以便回收應用程序池。 所以在我看來它與實體上下文的緩存有關。
下面是我的通用存儲庫代碼和我的工作單元代碼。 我認為問題在於其中一個問題,並且某種程度上需要更新實體上下文的緩存或者需要重新加載實體。 我可以做些什么來解決這個問題?
信息:工作單位是基於此文章。 並且網站和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:通過通用服務使用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:添加了通用服務的代碼
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);
//}
}
}
問題當然發現,在這里:
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
類在其構造函數中從UnitOfWorkStore
獲取其DbSets
並且從不再更新它們,相應的“靜態實例”將繼續使用與它們獲得的第一個相同的內容,並且這些實例將在關聯的IIS應用程序池的整個生命周期中持續使用。
這意味着每次收到新的HTTP請求時,您的Datacontext
及其相關的DbSets
實例都不會更新,因為它們應該用於靜態GenericServices
/ GenericRepositories
。 實際上,使用HttpContext.Current.Items
來管理DataContext
生命周期表明您要做的是為每個新的HTTP請求創建一個新的。 (這就是你指出的文章的目的)。
讓我們試着總結一下:
GenericServices
/ GenericRespositories
引用的DataContext
實例僅在重新啟動關聯的IIS應用程序池時更新(就像您報告的那樣)。 GenericRepository
“靜態實例”始終在HTTP請求中使用相同的DbSets
和DataContexts
而不是新請求,這會導致實體刷新問題。 GenericRepositories
“靜態實例”獲取舊實體和GenericRepositories
“新實例”(來自您的控制器)獲取最新數據(因為它們的_dbSet字段填充了UnitOfWorkStore
提供的新DataContext
) 要解決這個問題,有很多解決方案,這里有幾個:
DBSet
引用保存到GenericRepositories
GenericServices
或GenericRepositories
用作靜態字段/屬性 希望所有這些假設都有助於朝着正確的方向前進。
嘗試context.Entry(<!-- Name -->).Reload();
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.