I have the following interfaces/class:
public interface IUnitOfWork : IDisposable
{
event EventHandler<EventArgs> Saved;
DbSet<T> Set<T>() where T : class;
DbEntityEntry<T> Entry<T>(T entity) where T : class;
void Commit();
}
And an implementation of a repository:
public class CachedSqlRepository<T, TKey, TContext> : ICacheRepository<T, TKey, TContext>
where T : class
where TContext : DbContext, IDisposable, new()
{
//A list of the Navigation Properties to include
private readonly Expression<Func<T, object>>[] _NavigationProperties;
public CachedSqlRepository(params Expression<Func<T, object>>[] navigationProperties)
{
_NavigationProperties = navigationProperties;
using (TContext dbContext = new TContext()) //Fetch the List of Entities
{
RefreshCache(dbContext);
}
}
/// <summary>
/// The Collection of Items in the database
/// Note this is a Cache, but should replicate whats in the DB
/// </summary>
public IList<T> Items { get; private set; }
public bool Any(Func<T, bool> predicate)
{
return Items.Any(predicate);
}
public void RefreshCache(DbContext context)
{
switch (_NavigationProperties.Length)
{
case 0:
Items = context.Set<T>().ToList();
break;
case 1:
Items = context.Set<T>().Include(_NavigationProperties[0]).ToList();
break;
//more here
}
}
/// <summary>
/// Refresh the internal cache
/// </summary>
public void RefreshCache()
{
using (TContext dbContext = new TContext())
{
RefreshCache(dbContext);
}
}
public IEnumerable<T> FilterBy(Func<T, bool> predicate)
{
return Items.Where(predicate);
}
public T Add(T entity)
{
T newEntity;
using (TContext dbContext = new TContext())
{
newEntity = dbContext.Set<T>().Add(entity);
if (dbContext.SaveChanges() == 1) //1 change was made
Items.Add(newEntity);
}
return newEntity;
}
public void Delete(TKey id)
{
using (TContext dbContext = new TContext())
{
var attachedEntry = dbContext.Set<T>().Find(id);
if (attachedEntry == null) return; //it doesnt exist anyway!
dbContext.Set<T>().Remove(attachedEntry);
dbContext.SaveChanges();
RefreshCache(dbContext);
}
}
public void Update(T entity, TKey id)
{
if (entity == null) throw new ArgumentException("Cannot update a null entity.");
using (TContext dbContext = new TContext())
{
var entry = dbContext.Entry(entity);
if (entry.State != EntityState.Detached) return;
T attachedEntity = dbContext.Set<T>().Find(id);
if (attachedEntity != null)
{
var attachedEntry = dbContext.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{
entry.State = EntityState.Modified; // This should attach entity
}
dbContext.SaveChanges();
RefreshCache(dbContext);
}
}
#region Transaction Methods
public IUnitOfWork StartTransaction()
{
return new EFUnitOfWork(new TContext());
}
public T TransactionAdd(T entity, IUnitOfWork context)
{
context.Saved += OnSave;
return context.Set<T>().Add(entity);
}
public void TransactionDelete(TKey id, IUnitOfWork context)
{
var attachedEntry = context.Set<T>().Find(id);
if (attachedEntry == null) return; //it doesnt exist anyway
context.Saved += OnSave;
context.Set<T>().Remove(attachedEntry);
}
public void TransactionUpdate(T entity, TKey id, IUnitOfWork context)
{
if (entity == null) throw new ArgumentException("Cannot update a null entity.");
var entry = context.Entry(entity);
if (entry.State != EntityState.Detached) return;
T attachedEntity = context.Set<T>().Find(id);
if (attachedEntity != null)
{
var attachedEntry = context.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{
entry.State = EntityState.Modified; // This should attach entity
}
context.Saved += OnSave;
}
private void OnSave(object sender, EventArgs e)
{
RefreshCache();
}
#endregion
}
It is adapted from various patterns on the net. I don't expect this to be useful for tables with hundreds of thousands of rows, but for lookup tables etc - I'm not always hitting the DB.
It works, but some things aren't super clean, for example where I refresh the cache - sometimes I have to pull all the data again (currently a work in progress).
Is this sound design? Or am I reinventing the wheel here?
An interesting question +1. In my view, context content caching is one that is best done properly or left well alone. And use DB caching.
Why:
Parallel WPs all have a cache
Each WP may have threads, the context is not thread safe
Should each thread have a cache?
Is your cache session persistent?
No: you reload each request
Yes: you use global caching on ASP.NET, EnterpriseLibary cache or similar?
Are you managing the cache correctly?
How do you deal with concurrency and changes
Have you considered best practice around Context lifetime? Some experts suggest short lifetime only
Is DB located on LAN near WebServer?
Have you compared response times when using DB buffer access?
Having looked into this topic in various environments, not just EF/.NET/SQL Server, I have come to the conclusion that unless the DB server has become or is tending to be the CPU bottleneck and can't be easily scaled, it is a very reasonable approach to supply memory to DB and let it cache 100sMB
before building or trying to cache entries.
I would rather throw GBs or RAM at SQL Server before coding myself in app knots on WebServer.
When every microsecond counts, or your DB is separated on a network span with latency/throughput issues and your data is non volatile and needs no cache expiry/concurrency management. Then go ahead and implement caching.
Consider memory use, cache construction time and memory persistency model carefully.
Look at some tools made for caching for ideas and as potential solutions. e.g. Enterprise Caching Block.
Good Luck.
Related
I have an application, connecting to some databases. After first execution the app takes 300Mb(some data is loading from databases), and it's OK. But after pushing button "Refresh" refresh-method is called, in which I reassign old collections with new ones and the problem is that they after loading new data - old data is still taking memory, so after some forced resfreshes it can take up to 2Gb.
I've tried to force GC with different options - nothing happened. GC doesn't think that my collections need to be fully updated and old data must be destroyed.
I've decided to make my own Disposable collection, which inherits from List or Collection and implements IDisposable[1]. Then I've gone throw all classes, which contain in such collections and implemented IDisposable(with and without destructors), too.
I've used using() construction to clear those collections[2], but the result was that collections have become empty, but still have taken memory.
Using profiling program, I saw that Gen0 objects amount grows each time I do refreshing. After 10 refreshes there were from 120 to 580 such objects, which GC couldn't clear.
EDIT:
DataContext.
UnitOfWork.
Repostitory.
Registration of repository and service of repository.
Method in service for getting data.
Declaring service using Bootstrapper and using it to get the data.
So 8 is in Refresh method, and data is reassignes each refresh.
[1]
public class DisposableList<T> : List<T>, IDisposable where T :
IDisposable
{
public DisposableList()
{
}
public DisposableList(T instance)
{
Add(instance);
}
public DisposableList(IEnumerable<T> instances)
{
AddRange(instances);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
foreach (T item in this)
{
item.Dispose();
}
}
}
public void Dispose()
{
Dispose(true);
}
~DisposableList()
{
}
}
[2]
using (Resources = new DisposableList<Resources>())
{
}
[3]
public partial class MyDataContext : DbContext, IMyDataContext
{
static MyDataContext()
{
Database.SetInitializer<MyDataContext>(null);
}
public MyDataContext(string connectionString)
: base(connectionString)
{
try
{
if (!Database.Exists())
{
throw new Exception($"Нет соединения с базой данных
{Database.Connection.Database}");
}
}
catch (Exception ex)
{
throw ex;
}
}
public IDbSet<Table> Table { get; set; }
}
[4]
public class UnitOfWork : IUnitOfWork
{
DbContext _dataContext;
IRepository[] _repositories;
readonly object _locked = new object();
public UnitOfWork(DbContext dataContext, params IRepository[]
repositories)
{
_dataContext = dataContext;
_repositories = repositories;
EFContextHelper.SetContext(_repositories, dataContext);
}
public virtual void SaveChanges()
{
try
{
lock (_locked)
{
_dataContext.SaveChanges();
}
}
catch (DbUpdateConcurrencyException ex)
{
throw;
}
catch (Exception ex)
{
throw;
}
}
public virtual bool HasChanges()
=> _dataContext.ChangeTracker.HasChanges();
public virtual void Rollback()
{
lock (_locked)
{
foreach (DbEntityEntry entry in
_dataContext.ChangeTracker.Entries())
{
switch (entry.State)
{
case EntityState.Detached: break;
case EntityState.Unchanged: break;
case EntityState.Added:
entry.State = EntityState.Detached;
break;
case EntityState.Deleted:
entry.State = EntityState.Unchanged;
break;
case EntityState.Modified:
entry.CurrentValues.SetValues(entry.OriginalValues);
entry.State = EntityState.Unchanged;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
#region IDispose
bool _disposed;
public virtual void Dispose()
{
if (_disposed) { return; }
foreach (var repo in _repositories)
{
repo.Dispose();
}
_dataContext?.Dispose();
_dataContext = null;
_disposed = true;
}
[5]
public interface IRepository<TEntity> : IRepository where TEntity : class
{
TEntity Create();
void Delete(TEntity entity);
void Delete(IEnumerable<TEntity> entities);
void AddOrUpdate(TEntity entity);
void Attach(TEntity entity);
void Reload(TEntity entity);
void Detach(TEntity entity);
}
[6]
builder.RegisterType<Service>().As<IService>();
builder.RegisterType<Repository>).As<Repository>;
[7]
GetData()
{
using (_UnitOfWorkFactory.Create(_repository))
{
var data = _repository.Get();
}
}
[8]
var service = Bootstrapper.Container.Resolve<IService>();
var data = service.GetData();
So I need my app to use such amount of memory, which is used after first execution. I want my old data to be destroyed and new data to take old data place.
I currently have a long-lived datacontext in my data layer like this:
public class DataRepository
{
private readonly NorthwindDatacontext db;
public DataRepository()
{
db = new NorthwindDatacontext();
}
public void insert(Order o)
{
db.Oreder.InsertOnSubmit(o);
db.SubmitChanges();
}
}
from what I understand it is preferred to have short-lived data context
but what I don't understand is when working with short-lived data context is how I handle the next example.
Some method on a client doing this:
public void AddOrderDetails(IEnumrable<OrderDetails> od, Order o)
{
DataRepository repo = new DataRepository();
o.OrderDeatils.AddRange(od);
repo.Update(o);
}
And now my DataRepository is like that:
public class DataRepository
{
public Order GetOrder(int id)
{
using ( var db = New NorthwindDataContext() )
{
db.ObjectTrackingEnabled = false;
var order = db.Oreder.Where(o => o.id == id ).SingleOrDefault();
return order;
}
}
public void Update (Order o)
{
using ( var db = New NorthwindDataContext() )
{
db.Order.Attach(o,true);
db.SubmitChanges();
}
}
}
Will it update the relations ? what if some of the OrderDeatils are new (no id yet) and some are just updated. How should I handle all situations ?
"Short lived" doesn't mean "it lives as long as the method is executed". It also can mean "the context lives at least as long as your repository".
And this seems to be the option preferred in most implementations:
public class DataRepository()
{
private NorthwindContext _context;
public DataRepository( NorthwindContext context )
{
this._context = context;
}
public Order GetOrder( int id )
{
return this._context.....
}
Note that by injecting the context into the repository, rather than creating a context instance per repository, you not only have the same context in all repository methods but also you can share the same instance of the context between different repositories.
I have read lots of information about page caching and partial page caching in a MVC application. However, I would like to know how you would cache data.
In my scenario I will be using LINQ to Entities (entity framework). On the first call to GetNames (or whatever the method is) I want to grab the data from the database. I want to save the results in cache and on the second call to use the cached version if it exists.
Can anyone show an example of how this would work, where this should be implemented (model?) and if it would work.
I have seen this done in traditional ASP.NET apps , typically for very static data.
Here's a nice and simple cache helper class/service I use:
using System.Runtime.Caching;
public class InMemoryCache: ICacheService
{
public T GetOrSet<T>(string cacheKey, Func<T> getItemCallback) where T : class
{
T item = MemoryCache.Default.Get(cacheKey) as T;
if (item == null)
{
item = getItemCallback();
MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(10));
}
return item;
}
}
interface ICacheService
{
T GetOrSet<T>(string cacheKey, Func<T> getItemCallback) where T : class;
}
Usage:
cacheProvider.GetOrSet("cache key", (delegate method if cache is empty));
Cache provider will check if there's anything by the name of "cache id" in the cache, and if there's not, it will call a delegate method to fetch data and store it in cache.
Example:
var products=cacheService.GetOrSet("catalog.products", ()=>productRepository.GetAll())
Reference the System.Web dll in your model and use System.Web.Caching.Cache
public string[] GetNames()
{
string[] names = Cache["names"] as string[];
if(names == null) //not in cache
{
names = DB.GetNames();
Cache["names"] = names;
}
return names;
}
A bit simplified but I guess that would work. This is not MVC specific and I have always used this method for caching data.
I'm referring to TT's post and suggest the following approach:
Reference the System.Web dll in your model and use System.Web.Caching.Cache
public string[] GetNames()
{
var noms = Cache["names"];
if(noms == null)
{
noms = DB.GetNames();
Cache["names"] = noms;
}
return ((string[])noms);
}
You should not return a value re-read from the cache, since you'll never know if at that specific moment it is still in the cache. Even if you inserted it in the statement before, it might already be gone or has never been added to the cache - you just don't know.
So you add the data read from the database and return it directly, not re-reading from the cache.
For .NET 4.5+ framework
add reference: System.Runtime.Caching
add using statement:
using System.Runtime.Caching;
public string[] GetNames()
{
var noms = System.Runtime.Caching.MemoryCache.Default["names"];
if(noms == null)
{
noms = DB.GetNames();
System.Runtime.Caching.MemoryCache.Default["names"] = noms;
}
return ((string[])noms);
}
In the .NET Framework 3.5 and earlier versions, ASP.NET provided an in-memory cache implementation in the System.Web.Caching namespace. In previous versions of the .NET Framework, caching was available only in the System.Web namespace and therefore required a dependency on ASP.NET classes. In the .NET Framework 4, the System.Runtime.Caching namespace contains APIs that are designed for both Web and non-Web applications.
More info:
https://msdn.microsoft.com/en-us/library/dd997357(v=vs.110).aspx
https://learn.microsoft.com/en-us/dotnet/framework/performance/caching-in-net-framework-applications
Steve Smith did two great blog posts which demonstrate how to use his CachedRepository pattern in ASP.NET MVC. It uses the repository pattern effectively and allows you to get caching without having to change your existing code.
http://ardalis.com/Introducing-the-CachedRepository-Pattern
http://ardalis.com/building-a-cachedrepository-via-strategy-pattern
In these two posts he shows you how to set up this pattern and also explains why it is useful. By using this pattern you get caching without your existing code seeing any of the caching logic. Essentially you use the cached repository as if it were any other repository.
I have used it in this way and it works for me.
https://msdn.microsoft.com/en-us/library/system.web.caching.cache.add(v=vs.110).aspx
parameters info for system.web.caching.cache.add.
public string GetInfo()
{
string name = string.Empty;
if(System.Web.HttpContext.Current.Cache["KeyName"] == null)
{
name = GetNameMethod();
System.Web.HttpContext.Current.Cache.Add("KeyName", name, null, DateTime.Noew.AddMinutes(5), Cache.NoSlidingExpiration, CacheitemPriority.AboveNormal, null);
}
else
{
name = System.Web.HttpContext.Current.Cache["KeyName"] as string;
}
return name;
}
AppFabric Caching is distributed and an in-memory caching technic that stores data in key-value pairs using physical memory across multiple servers. AppFabric provides performance and scalability improvements for .NET Framework applications. Concepts and Architecture
Extending #Hrvoje Hudo's answer...
Code:
using System;
using System.Runtime.Caching;
public class InMemoryCache : ICacheService
{
public TValue Get<TValue>(string cacheKey, int durationInMinutes, Func<TValue> getItemCallback) where TValue : class
{
TValue item = MemoryCache.Default.Get(cacheKey) as TValue;
if (item == null)
{
item = getItemCallback();
MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(durationInMinutes));
}
return item;
}
public TValue Get<TValue, TId>(string cacheKeyFormat, TId id, int durationInMinutes, Func<TId, TValue> getItemCallback) where TValue : class
{
string cacheKey = string.Format(cacheKeyFormat, id);
TValue item = MemoryCache.Default.Get(cacheKey) as TValue;
if (item == null)
{
item = getItemCallback(id);
MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(durationInMinutes));
}
return item;
}
}
interface ICacheService
{
TValue Get<TValue>(string cacheKey, Func<TValue> getItemCallback) where TValue : class;
TValue Get<TValue, TId>(string cacheKeyFormat, TId id, Func<TId, TValue> getItemCallback) where TValue : class;
}
Examples
Single item caching (when each item is cached based on its ID because caching the entire catalog for the item type would be too intensive).
Product product = cache.Get("product_{0}", productId, 10, productData.getProductById);
Caching all of something
IEnumerable<Categories> categories = cache.Get("categories", 20, categoryData.getCategories);
Why TId
The second helper is especially nice because most data keys are not composite. Additional methods could be added if you use composite keys often. In this way you avoid doing all sorts of string concatenation or string.Formats to get the key to pass to the cache helper. It also makes passing the data access method easier because you don't have to pass the ID into the wrapper method... the whole thing becomes very terse and consistant for the majority of use cases.
Here's an improvement to Hrvoje Hudo's answer. This implementation has a couple of key improvements:
Cache keys are created automatically based on the function to update data and the object passed in that specifies dependencies
Pass in time span for any cache duration
Uses a lock for thread safety
Note that this has a dependency on Newtonsoft.Json to serialize the dependsOn object, but that can be easily swapped out for any other serialization method.
ICache.cs
public interface ICache
{
T GetOrSet<T>(Func<T> getItemCallback, object dependsOn, TimeSpan duration) where T : class;
}
InMemoryCache.cs
using System;
using System.Reflection;
using System.Runtime.Caching;
using Newtonsoft.Json;
public class InMemoryCache : ICache
{
private static readonly object CacheLockObject = new object();
public T GetOrSet<T>(Func<T> getItemCallback, object dependsOn, TimeSpan duration) where T : class
{
string cacheKey = GetCacheKey(getItemCallback, dependsOn);
T item = MemoryCache.Default.Get(cacheKey) as T;
if (item == null)
{
lock (CacheLockObject)
{
item = getItemCallback();
MemoryCache.Default.Add(cacheKey, item, DateTime.Now.Add(duration));
}
}
return item;
}
private string GetCacheKey<T>(Func<T> itemCallback, object dependsOn) where T: class
{
var serializedDependants = JsonConvert.SerializeObject(dependsOn);
var methodType = itemCallback.GetType();
return methodType.FullName + serializedDependants;
}
}
Usage:
var order = _cache.GetOrSet(
() => _session.Set<Order>().SingleOrDefault(o => o.Id == orderId)
, new { id = orderId }
, new TimeSpan(0, 10, 0)
);
public sealed class CacheManager
{
private static volatile CacheManager instance;
private static object syncRoot = new Object();
private ObjectCache cache = null;
private CacheItemPolicy defaultCacheItemPolicy = null;
private CacheEntryRemovedCallback callback = null;
private bool allowCache = true;
private CacheManager()
{
cache = MemoryCache.Default;
callback = new CacheEntryRemovedCallback(this.CachedItemRemovedCallback);
defaultCacheItemPolicy = new CacheItemPolicy();
defaultCacheItemPolicy.AbsoluteExpiration = DateTime.Now.AddHours(1.0);
defaultCacheItemPolicy.RemovedCallback = callback;
allowCache = StringUtils.Str2Bool(ConfigurationManager.AppSettings["AllowCache"]); ;
}
public static CacheManager Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
instance = new CacheManager();
}
}
}
return instance;
}
}
public IEnumerable GetCache(String Key)
{
if (Key == null || !allowCache)
{
return null;
}
try
{
String Key_ = Key;
if (cache.Contains(Key_))
{
return (IEnumerable)cache.Get(Key_);
}
else
{
return null;
}
}
catch (Exception)
{
return null;
}
}
public void ClearCache(string key)
{
AddCache(key, null);
}
public bool AddCache(String Key, IEnumerable data, CacheItemPolicy cacheItemPolicy = null)
{
if (!allowCache) return true;
try
{
if (Key == null)
{
return false;
}
if (cacheItemPolicy == null)
{
cacheItemPolicy = defaultCacheItemPolicy;
}
String Key_ = Key;
lock (Key_)
{
return cache.Add(Key_, data, cacheItemPolicy);
}
}
catch (Exception)
{
return false;
}
}
private void CachedItemRemovedCallback(CacheEntryRemovedArguments arguments)
{
String strLog = String.Concat("Reason: ", arguments.RemovedReason.ToString(), " | Key-Name: ", arguments.CacheItem.Key, " | Value-Object: ", arguments.CacheItem.Value.ToString());
LogManager.Instance.Info(strLog);
}
}
I use two classes. First one the cache core object:
public class Cacher<TValue>
where TValue : class
{
#region Properties
private Func<TValue> _init;
public string Key { get; private set; }
public TValue Value
{
get
{
var item = HttpRuntime.Cache.Get(Key) as TValue;
if (item == null)
{
item = _init();
HttpContext.Current.Cache.Insert(Key, item);
}
return item;
}
}
#endregion
#region Constructor
public Cacher(string key, Func<TValue> init)
{
Key = key;
_init = init;
}
#endregion
#region Methods
public void Refresh()
{
HttpRuntime.Cache.Remove(Key);
}
#endregion
}
Second one is list of cache objects:
public static class Caches
{
static Caches()
{
Languages = new Cacher<IEnumerable<Language>>("Languages", () =>
{
using (var context = new WordsContext())
{
return context.Languages.ToList();
}
});
}
public static Cacher<IEnumerable<Language>> Languages { get; private set; }
}
I will say implementing Singleton on this persisting data issue can be a solution for this matter in case you find previous solutions much complicated
public class GPDataDictionary
{
private Dictionary<string, object> configDictionary = new Dictionary<string, object>();
/// <summary>
/// Configuration values dictionary
/// </summary>
public Dictionary<string, object> ConfigDictionary
{
get { return configDictionary; }
}
private static GPDataDictionary instance;
public static GPDataDictionary Instance
{
get
{
if (instance == null)
{
instance = new GPDataDictionary();
}
return instance;
}
}
// private constructor
private GPDataDictionary() { }
} // singleton
HttpContext.Current.Cache.Insert("subjectlist", subjectlist);
You can also try and use the caching built into ASP MVC:
Add the following attribute to the controller method you'd like to cache:
[OutputCache(Duration=10)]
In this case the ActionResult of this will be cached for 10 seconds.
More on this here
I read all the similar titles questions, but i didn't find the answer to my problem, so I open a new question:
I have two mysql tables:
tb1 (int_id, code, description, ..., my_id);
tb2 (int_id, code, description, ..., tb1_id);
I create a generic repository to manage DbContext and GetAll, GetById, GetByLambda, Insert, Update and Delete methode.
namespace Model.DataAccessLayer
{
public class Repository<T> : IRepository<T> where T : EntityObject, IEntity
{
protected DbContext dbContext = null;
public virtual DbContext DbContext
{
get { return dbContext; }
set { dbContext = value; }
}
public ObjectContext ObjectContext
{
get { return ((IObjectContextAdapter)DbContext).ObjectContext; }
}
public void Dispose()
{
ObjectContext.Dispose();
System.GC.SuppressFinalize(this);
}
public virtual IQueryable<T> GetAll()
{
return ObjectContext.CreateObjectSet<T>();
}
public virtual IEnumerable<T> GetByLambda(Func<T, bool> p)
{
return GetAll().Where(p);
}
public virtual void Save()
{
DbContext.SaveChanges();
}
...
}
}
and the inherited class which I used:
namespace Model.DataAccessLayer
{
public class RepositoryP<T> : Repository<T> where T : EntityObject, IEntity
{
public myEntities MyContext
{
get { return (myEntities)ObjectContext; }
//set { ObjectContext = value; }
}
public override DbContext DbContext
{
get
{
if (dbContext == null)
{
dbContext = new DbContext("myEntities");
}
return dbContext;
}
set
{
base.DbContext = value;
}
}
}
}
It works great when using only one table.
When I try to use my two tables and the foreign key relation between them, it doesn't work.
For example I try to get all records from table tb2 where tb1.my_id=5 with the following join tb1.int_id = tb2.tb1_id.
List<tb2> lj = new Tb2DAO().GetByLambda(l => l.tb1.my_id == 5).ToList();
(Tb2DAO inherited from my generic repository class.)
I have the following MySQL error:
"MySql.Data.MySqlClient.MySqlException: There is already an open DataReader associated with this Connection which must be closed first."
I think this comes from my DbContext which is not common to my two tables entities.
So I tried to implement the UnitOfWork Pattern to solve this problem, like this:
namespace Model.DataAccessLayer
{
public class UnitOfWork : IDisposable
{
private DbContext dbContextUnit = null; //= new DbContext();
private Tb1DAO tb1DAO;
private Tb2DAO tb2DAO;
public Tb1DAO tb1
{
get
{
if (this.tb1DAO == null)
{
if (dbContextUnit != null)
{
this.tb1DAO = new Tb1DAO { DbContext = dbContextUnit };
}
else
{
this.tb1DAO = new Tb1DAO();
dbContextUnit = this.tb1DAO.DbContext;
}
}
return tb1DAO;
}
}
public Tb2DAO tb2
{
get
{
if (this.tb2DAO == null)
{
if (dbContextUnit != null)
{
this.tb2DAO = new Tb2DAO { DbContext = dbContextUnit };
}
else
{
this.tb2DAO = new Tb2DAO();
dbContextUnit = this.tb2DAO.DbContext;
}
}
return tb2DAO;
}
}
public void Save()
{
dbContextUnit.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
dbContextUnit.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
And now in my code I tried to use the Unit of Work like this :
UnitOfWork unitOfWork = new UnitOfWork();
List<tb2> tb2List = unitOfWork.tb2.GetByLambda(l => l.index_job.job_id == job_id).ToList();
But I have always the same error message :
"MySql.Data.MySqlClient.MySqlException: There is already an open DataReader associated with this Connection which must be closed first."
is there something I am doing wrong ? Please can you help me ? This notion of repository and unit Of work are new for me
and I am confused else I read lot of think about it may be not the right one...
I also to try to add MultipleActiveResultSets=true to my connection but it is not recognized.
many thank to all & regards,
wst
Your GetByLambda() method is calling GetAll() which creates a new context using ObjectContext.CreateObjectSet<T>(). So you now have more than one context open. I would advise using the standard EF associations and the repository pattern, then you can avoid this entire mess. Here is a link that may help you get started - http://blogs.msdn.com/b/adonet/archive/2011/03/15/ef-4-1-code-first-walkthrough.aspx.
I'm not sure about this method
public virtual IQueryable<T> GetAll()
{
return ObjectContext.CreateObjectSet<T>();
}
It's create new object set about which EF context doesn't know.
You can try to pass Func<ObjectContext, ObjectResult<T>> from derived class to base. (It should return ObjectResult which are got from EF context. F.e. context => context.Entities).
I'm making a full repository in C#/ASP.NET with the Entity framework but at the moment I'm afraid I'm overlooking something like disposing my ObjectContexts. In the following lines of codes you'll see my full repository (atleast what is needed for you guys to understand my problem) and I hope someone is kind enough to look through it and tell me if I made some mistakes.
This project is very very important for me but I'm new to the repository/EF models.
Global.asax
public class Global : System.Web.HttpApplication
{
private WebObjectContextStorage _storage;
public override void Init()
{
base.Init();
_storage = new WebObjectContextStorage(this);
}
protected void Application_Start(object sender, EventArgs e)
{
}
protected void Session_Start(object sender, EventArgs e)
{
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
ObjectContextInitializer.Instance().InitializeObjectContextOnce(() =>
{
ObjectContextManager.InitStorage(_storage);
});
}
protected void Application_EndRequest(object sender, EventArgs e)
{
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
}
protected void Application_Error(object sender, EventArgs e)
{
}
protected void Session_End(object sender, EventArgs e)
{
}
protected void Application_End(object sender, EventArgs e)
{
}
}
ObjectContextManager
public static class ObjectContextManager
{
public static void InitStorage(IObjectContextStorage storage)
{
if (storage == null)
{
throw new ArgumentNullException("storage");
}
if ((Storage != null) && (Storage != storage))
{
throw new ApplicationException("A storage mechanism has already been configured for this application");
}
Storage = storage;
}
/// <summary>
/// The default connection string name used if only one database is being communicated with.
/// </summary>
public static readonly string DefaultConnectionStringName = "TraceConnection";
/// <summary>
/// Used to get the current object context session if you're communicating with a single database.
/// When communicating with multiple databases, invoke <see cref="CurrentFor()" /> instead.
/// </summary>
public static ObjectContext Current
{
get
{
return CurrentFor(DefaultConnectionStringName);
}
}
/// <summary>
/// Used to get the current ObjectContext associated with a key; i.e., the key
/// associated with an object context for a specific database.
///
/// If you're only communicating with one database, you should call <see cref="Current" /> instead,
/// although you're certainly welcome to call this if you have the key available.
/// </summary>
public static ObjectContext CurrentFor(string key)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException("key");
}
if (Storage == null)
{
throw new ApplicationException("An IObjectContextStorage has not been initialized");
}
ObjectContext context = null;
lock (_syncLock)
{
context = Storage.GetObjectContextForKey(key);
if (context == null)
{
context = ObjectContextFactory.GetTraceContext(key);
Storage.SetObjectContextForKey(key, context);
}
}
return context;
}
/// <summary>
/// This method is used by application-specific object context storage implementations
/// and unit tests. Its job is to walk thru existing cached object context(s) and Close() each one.
/// </summary>
public static void CloseAllObjectContexts()
{
foreach (ObjectContext ctx in Storage.GetAllObjectContexts())
{
if (ctx.Connection.State == System.Data.ConnectionState.Open)
ctx.Connection.Close();
}
}
/// <summary>
/// An application-specific implementation of IObjectContextStorage must be setup either thru
/// <see cref="InitStorage" /> or one of the <see cref="Init" /> overloads.
/// </summary>
private static IObjectContextStorage Storage { get; set; }
private static object _syncLock = new object();
}
ObjectContextInitializer
public class ObjectContextInitializer
{
private static readonly object syncLock = new object();
private static ObjectContextInitializer instance;
protected ObjectContextInitializer() { }
private bool isInitialized = false;
public static ObjectContextInitializer Instance()
{
if (instance == null)
{
lock (syncLock)
{
if (instance == null)
{
instance = new ObjectContextInitializer();
}
}
}
return instance;
}
/// <summary>
/// This is the method which should be given the call to intialize the ObjectContext; e.g.,
/// ObjectContextInitializer.Instance().InitializeObjectContextOnce(() => InitializeObjectContext());
/// where InitializeObjectContext() is a method which calls ObjectContextManager.Init()
/// </summary>
/// <param name="initMethod"></param>
public void InitializeObjectContextOnce(Action initMethod)
{
lock (syncLock)
{
if (!isInitialized)
{
initMethod();
isInitialized = true;
}
}
}
}
ObjectContextFactory
public static class ObjectContextFactory
{
/// <summary>
/// Gets the TraceContext
/// </summary>
/// <param name="connectionString">Connection string to use for database queries</param>
/// <returns>The TraceContext</returns>
public static TraceContext GetTraceContext(string configName)
{
string connectionString = ConfigurationManager.ConnectionStrings[configName].ConnectionString;
return new TraceContext(connectionString);
}
}
WebObjectContextStorage
public class WebObjectContextStorage : IObjectContextStorage
{
public WebObjectContextStorage(HttpApplication app)
{
app.EndRequest += (sender, args) =>
{
ObjectContextManager.CloseAllObjectContexts();
HttpContext.Current.Items.Remove(HttpContextObjectContextStorageKey);
};
}
public ObjectContext GetObjectContextForKey(string key)
{
ObjectContextStorage storage = GetObjectContextStorage();
return storage.GetObjectContextForKey(key);
}
public void SetObjectContextForKey(string factoryKey, ObjectContext session)
{
ObjectContextStorage storage = GetObjectContextStorage();
storage.SetObjectContextForKey(factoryKey, session);
}
public IEnumerable<ObjectContext> GetAllObjectContexts()
{
ObjectContextStorage storage = GetObjectContextStorage();
return storage.GetAllObjectContexts();
}
private ObjectContextStorage GetObjectContextStorage()
{
HttpContext context = HttpContext.Current;
ObjectContextStorage storage = context.Items[HttpContextObjectContextStorageKey] as ObjectContextStorage;
if (storage == null)
{
storage = new ObjectContextStorage();
context.Items[HttpContextObjectContextStorageKey] = storage;
}
return storage;
}
private static readonly string HttpContextObjectContextStorageKey = "HttpContextObjectContextStorageKey";
}
ObjectContextStorage
public class ObjectContextStorage : IObjectContextStorage
{
private Dictionary<string, ObjectContext> storage = new Dictionary<string, ObjectContext>();
/// <summary>
/// Initializes a new instance of the <see cref="SimpleObjectContextStorage"/> class.
/// </summary>
public ObjectContextStorage() { }
/// <summary>
/// Returns the object context associated with the specified key or
/// null if the specified key is not found.
/// </summary>
/// <param name="key">The key.</param>
/// <returns></returns>
public ObjectContext GetObjectContextForKey(string key)
{
ObjectContext context;
if (!this.storage.TryGetValue(key, out context))
return null;
return context;
}
/// <summary>
/// Stores the object context into a dictionary using the specified key.
/// If an object context already exists by the specified key,
/// it gets overwritten by the new object context passed in.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="objectContext">The object context.</param>
public void SetObjectContextForKey(string key, ObjectContext objectContext)
{
this.storage.Add(key, objectContext);
}
/// <summary>
/// Returns all the values of the internal dictionary of object contexts.
/// </summary>
/// <returns></returns>
public IEnumerable<ObjectContext> GetAllObjectContexts()
{
return this.storage.Values;
}
}
GenericRepository
public class GenericRepository : IRepository
{
private readonly string _connectionStringName;
private ObjectContext _objectContext;
private readonly PluralizationService _pluralizer = PluralizationService.CreateService(CultureInfo.GetCultureInfo("en"));
private bool _usePlurazation;
/// <summary>
/// Initializes a new instance of the <see cref="GenericRepository<TEntity>"/> class.
/// </summary>
public GenericRepository()
: this(string.Empty, false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GenericRepository<TEntity>"/> class.
/// </summary>
/// <param name="connectionStringName">Name of the connection string.</param>
public GenericRepository(string connectionStringName, bool usePlurazation)
{
this._connectionStringName = connectionStringName;
this._usePlurazation = usePlurazation;
}
/// <summary>
/// Initializes a new instance of the <see cref="GenericRepository"/> class.
/// </summary>
/// <param name="objectContext">The object context.</param>
public GenericRepository(ObjectContext objectContext, bool usePlurazation)
{
if (objectContext == null)
throw new ArgumentNullException("objectContext");
this._objectContext = objectContext;
this._usePlurazation = usePlurazation;
}
public TEntity GetByKey<TEntity>(object keyValue) where TEntity : class
{
EntityKey key = GetEntityKey<TEntity>(keyValue);
object originalItem;
if (ObjectContext.TryGetObjectByKey(key, out originalItem))
{
return (TEntity)originalItem;
}
return default(TEntity);
}
public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
{
var entityName = GetEntityName<TEntity>();
return ObjectContext.CreateQuery<TEntity>(entityName).OfType<TEntity>();
}
public IQueryable<TEntity> GetQuery<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
{
return GetQuery<TEntity>().Where(predicate);
}
public IQueryable<TEntity> GetQuery<TEntity>(ISpecification<TEntity> specification) where TEntity : class
{
return specification.SatisfyingEntitiesFrom(GetQuery<TEntity>());
}
public IEnumerable<TEntity> Get<TEntity>(Expression<Func<TEntity, string>> orderBy, int pageIndex, int pageSize, SortOrder sortOrder = SortOrder.Ascending) where TEntity : class
{
if (sortOrder == SortOrder.Ascending)
{
return GetQuery<TEntity>().OrderBy(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
return GetQuery<TEntity>().OrderByDescending(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
public IEnumerable<TEntity> Get<TEntity>(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, string>> orderBy, int pageIndex, int pageSize, SortOrder sortOrder = SortOrder.Ascending) where TEntity : class
{
if (sortOrder == SortOrder.Ascending)
{
return GetQuery<TEntity>().Where(predicate).OrderBy(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
return GetQuery<TEntity>().Where(predicate).OrderByDescending(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
public IEnumerable<TEntity> Get<TEntity>(ISpecification<TEntity> specification, Expression<Func<TEntity, string>> orderBy, int pageIndex, int pageSize, SortOrder sortOrder = SortOrder.Ascending) where TEntity : class
{
if (sortOrder == SortOrder.Ascending)
{
return specification.SatisfyingEntitiesFrom(GetQuery<TEntity>()).OrderBy(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
return specification.SatisfyingEntitiesFrom(GetQuery<TEntity>()).OrderByDescending(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
public TEntity Single<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
{
return GetQuery<TEntity>().SingleOrDefault<TEntity>(criteria);
}
public TEntity Single<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
return criteria.SatisfyingEntityFrom(GetQuery<TEntity>());
}
public TEntity First<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
{
return GetQuery<TEntity>().FirstOrDefault(predicate);
}
public TEntity First<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
return criteria.SatisfyingEntitiesFrom(GetQuery<TEntity>()).FirstOrDefault();
}
public void Add<TEntity>(TEntity entity) where TEntity : class
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
ObjectContext.AddObject(GetEntityName<TEntity>(), entity);
}
public void Attach<TEntity>(TEntity entity) where TEntity : class
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
ObjectContext.AttachTo(GetEntityName<TEntity>(), entity);
}
public void Delete<TEntity>(TEntity entity) where TEntity : class
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
ObjectContext.DeleteObject(entity);
}
public void Delete<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
{
IEnumerable<TEntity> records = Find<TEntity>(criteria);
foreach (TEntity record in records)
{
Delete<TEntity>(record);
}
}
public void Delete<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
IEnumerable<TEntity> records = Find<TEntity>(criteria);
foreach (TEntity record in records)
{
Delete<TEntity>(record);
}
}
public IEnumerable<TEntity> GetAll<TEntity>() where TEntity : class
{
return GetQuery<TEntity>().AsEnumerable();
}
public void Update<TEntity>(TEntity entity) where TEntity : class
{
var fqen = GetEntityName<TEntity>();
object originalItem;
EntityKey key = ObjectContext.CreateEntityKey(fqen, entity);
if (ObjectContext.TryGetObjectByKey(key, out originalItem))
{
ObjectContext.ApplyCurrentValues(key.EntitySetName, entity);
}
}
public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
{
return GetQuery<TEntity>().Where(criteria);
}
public TEntity FindOne<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
{
return GetQuery<TEntity>().Where(criteria).FirstOrDefault();
}
public TEntity FindOne<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
return criteria.SatisfyingEntityFrom(GetQuery<TEntity>());
}
public IEnumerable<TEntity> Find<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
return criteria.SatisfyingEntitiesFrom(GetQuery<TEntity>());
}
public int Count<TEntity>() where TEntity : class
{
return GetQuery<TEntity>().Count();
}
public int Count<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
{
return GetQuery<TEntity>().Count(criteria);
}
public int Count<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
return criteria.SatisfyingEntitiesFrom(GetQuery<TEntity>()).Count();
}
public IUnitOfWork UnitOfWork
{
get
{
if (unitOfWork == null)
{
unitOfWork = new UnitOfWork(this.ObjectContext);
}
return unitOfWork;
}
}
private ObjectContext ObjectContext
{
get
{
if (this._objectContext == null)
{
if (string.IsNullOrEmpty(this._connectionStringName))
{
this._objectContext = ObjectContextManager.Current;
}
else
{
this._objectContext = ObjectContextManager.CurrentFor(this._connectionStringName);
}
}
return this._objectContext;
}
}
private EntityKey GetEntityKey<TEntity>(object keyValue) where TEntity : class
{
var entitySetName = GetEntityName<TEntity>();
var objectSet = ObjectContext.CreateObjectSet<TEntity>();
var keyPropertyName = objectSet.EntitySet.ElementType.KeyMembers[0].ToString();
var entityKey = new EntityKey(entitySetName, new[] { new EntityKeyMember(keyPropertyName, keyValue) });
return entityKey;
}
private string GetEntityName<TEntity>() where TEntity : class
{
// WARNING! : Exceptions for inheritance
if (_usePlurazation)
{
return string.Format("{0}.{1}", ObjectContext.DefaultContainerName, _pluralizer.Pluralize(typeof(TEntity).Name));
}
else
{
return string.Format("{0}.{1}", ObjectContext.DefaultContainerName, typeof(TEntity).Name);
}
}
private IUnitOfWork unitOfWork;
}
I know it will take some time to read through the code, but it would help me miles is somebody looks at it and gives tips on what to do better or where I do not dispose an object.
Also I got a little question: "I would like to put a business layer above this repository, that would keep things like global.asax the same I guess but would need static classes(right?) like a BookProvider which gives me all the data about my book-entities?
Thanks in advance!
The only concrete remark I can give is about disposing the context:
foreach (ObjectContext ctx in Storage.GetAllObjectContexts())
{
if (ctx.Connection.State == System.Data.ConnectionState.Open)
ctx.Connection.Close();
}
ObjectContext implements IDisposable, so the standard way would be in my opinion:
foreach (ObjectContext ctx in Storage.GetAllObjectContexts())
ctx.Dispose();
As far as I know ObjectContext.Dispose() just closes the connection, so it does the same what you are doing. But I'd consider this as an internal implementation detail which might potentially change between EF releases.
Your generic repository is one as there are many of this kind. A few points which came to my mind when looking at the methods:
Since you are exposing IQueryable in public IQueryable<TEntity> GetQuery<TEntity>(...) why do you need most of the other methods like Single, First, Count, etc.? (Why not Any, etc.?) You get all this from your IQueryable.
Your Update method only works for scalar properties. But this is a common problem with generic repositories. There is no easy solution or no solution at all for updating entities in a generic way.
What is your goal you want to reach by using the repository pattern at all? If you have unit testability with an in-memory data store in mind, you cannot expose IQueryable because LINQ to Entities and LINQ to Objects are not the same. To test if your IQueryables work you need integration tests and the real database your application is supposed to work with in production. But if you don't expose IQueryable your repository needs a lot of business specific methods which return the results as POCOs, POCO collections or DTOs of projected/selected properties and hide the internal query specifications so that you can mock these methods with in-memory data to test your business logic. But this is the point where a generic repository is not sufficient anymore. (How are you going to write, for instance, a LINQ Join where more than one Entity/ObjectSet is involved in a repository which has only one entity type as generic parameter?)
If you ask ten people how their repositories are architectured, you'll get ten different answers. And none is really wrong or the best because it depends on the application you are going to build with this repository. I believe that nobody can tell you what your repository is really worth. You will see it in practice when you start writing an application. For some applications it might be over-architectured (which I consider as most dangerous because managing and keeping a meaningless architecture under control is expensive and a waste of time you lose for writing real application content). And for other needs you will likely have to extend the repository. For example:
How do you handle explicit loading or queries on navigation properties of an entity (with CreateSourceQuery or DbEntityEntry.Collection/Reference in EF 4.1)? If your application never needs explicite loading: Fine. If it's necessary you need to extend your repo.
How do you control eager loading? Sometimes perhaps you just want a parent entity. Sometimes you want to Includ the children1 collection and sometimes the child2 reference.
How do you set the entity state of an entity manually? Perhaps you never must. But in the next application it might be very helpful.
How do you detach an entity from the context manually?
How do you control loading behaviour (with or without tracking of entities by the context)?
How do you control manually lazy loading behaviour and creation of change tracking proxies?
How do you create an entity proxy manually? You might need it in some situations when you work with lazy loading or change tracking proxies.
How do you load entities into the context without building a result collection? Yet another repository method, perhaps... or perhaps not. Who knows in advance what your application logic will require.
And so on and so forth...