I have the Cache layer described below. The problem is that I need to get Lists or specific element from List, and then modify it, then persist it to db, and update the cache list.
So to avoid problems between the cache layer & DB layer, when getting data from cache I should make/get a copy of it, so it doesn't get changed down the way (if it's changes DB layer throws an error).
Question is how to properly approach this copy strategy, and other question is should the lock be present for add/remove operations too? (if yes, what is the right way to lock prior to modify?)
The cache is a singleton DI through services.
services.AddSingleton<MyCache>();
public class MyCache
{
private readonly AsyncLock _mutex = new AsyncLock();
public MemoryCache Cache { get; set; }
public MyCache()
{
Cache = new MemoryCache(new MemoryCacheOptions());
// populate cache from DB
}
public async Task<List<UDT>> GetUDTsForUser(string id, bool fetchFromDB = false)
{
List<UDT> list = new List<UDT>();
Cache.TryGetValue(id, out list);
if (list == null)
{
using (await _mutex.LockAsync())
{
Cache.TryGetValue(id, out list);
if (list == null && fetchFromDB)
{
var DAO = new DAO();
list = (await DAO.GetInfoForUser(id))?.UDTs;
if (list != null)
{
Cache.Set(id, list);
}
}
}
}
return list;
}
public async Task<UDT> GetUDTForUser(string id, long udtId)
{
List<UDT> list = new List<UDT>();
Cache.TryGetValue(id, out list);
if (list == null)
{
using (await _mutex.LockAsync())
{
Cache.TryGetValue(id, out list);
if (list == null)
{
var DAO = new DAO();
list = (await DAO.GetInfoForUser(id)).UDTs;
Cache.Set(id, list);
return list.FirstOrDefault(u => u.Id == udtId);
}
}
}
return list.FirstOrDefault(u => u.Id == udtId);
}
public void AddElement(UDT udt)
{
if (udt == null)
return;
var udtList = Cache.GetOrCreate(udt.GroupId, entry => {
return new List<UDT>();
});
if (udtList.Contains(udt) == false)
{
udtList.Add(udt);
Cache.Set(udt.GroupId, udtList);
}
}
public void RemoveElement(string groupId, long udtId)
{
var udtList = Cache.Get<List<UDT>>(groupId);
if (udtList != null)
{
udtList.RemoveAll(e => e.Id == udtId);
Cache.Set(groupId, udtList);
}
}
}
Related
I have a class that returns a cache, usage currently:
var cache = new ProductCache().Get();
then cache is a List<> that can be enumerated.
question is really should i populate this cache when ProductCache() is instantiated in the constructor, or when it is retrieved?
Option 1:
public class ProductCache
{
private readonly string key = "Product";
private readonly object cacheLock = new object();
ObjectCache cache = MemoryCache.Default;
public ProductCache()
{
}
public List<string> Get()
{
// Try to return.
var data = cache.Get(key) as List<string>;
if (data != null)
return data;
lock (cacheLock)
{
// Check again.
data = cache.Get(key) as List<string>;
if (data != null)
return data;
// Populate, and return.
data = PopulateFromElsewhere();
cache.Set(key, data, DateTimeOffset.UtcNow.AddSeconds(20));
return data;
}
}
private List<string> PopulateFromElsewhere()
{
return new List<string> { "Ball", "Stick" };
}
}
Option 2:
public class ProductCache
{
private readonly string key = "Product";
private readonly object cacheLock = new object();
ObjectCache cache = MemoryCache.Default;
public ProductCache()
{
var data = cache.Get(key);
if (data != null)
return;
lock (cacheLock)
{
// Check again.
data = cache.Get(key);
if (data != null)
return;
// Populate, and return.
PopulateFromElsewhere();
}
}
public List<string> Get()
{
return cache.Get(key) as List<string>;
}
private void PopulateFromElsewhere()
{
var data = new List<string> { "Ball", "Stick" };
cache.Set(key, data, DateTimeOffset.UtcNow.AddSeconds(20));
}
}
is the second option thread safe (enough)? i think the first one is....
there are other caches too.. and they are all similar, so i was planning on putting all the actual locking / loading behaviour in an abstract class
var storeCache = new StoreCache().Get();
var otherCache = new OtherCache().Get();
I guess the other option is a static class, but then there would need to be duplication of the locking mechanisms as i can't make that abstract... that could be quite nice, and used like...
var cache = GlobalCache.Stores();
If you want to reuse your cache logic but want flexibility in your child classes you could use Template method pattern:
public abstract class BaseCache
{
private readonly object cacheLock = new object();
protected ObjectCache cache = MemoryCache.Default;
public List<string> Get()
{
// for example. It could be anywhere and return any type.
ChildLogic();
var data = cache.Get(key);
if (data != null)
return;
lock (cacheLock)
{
// Check again.
data = cache.Get(key);
if (data != null)
return;
// Populate, and return.
PopulateFromElsewhere();
}
}
protected abstract void ChildLogic();
protected abstract void PopulateFromElsewhere();
}
And then in your child classes you should implement ChildLogic() and PopulateFromElsewhere() any way you want.
Of course you are not required to have method ChildLogic() at all.
I currently have a multi one-to-many relationship hierarchy database tblProjects->tblLines->tblGroups->tblStations etc. And an Entity framework 6 model.
These entity framework classes all implement a base class "tblBase":
public abstract class TblBase : INotifyPropertyChanged
{
private int _id;
public int ID
{
get
{
return _id;
}
set
{
_id = value;
NotifyPropertyChanged();
}
}
private Nullable<int> _coid;
public Nullable<int> COID
{
get
{
NotifyPropertyChanged();
return _coid;
}
set
{
_coid = value;
NotifyPropertyChanged();
}
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
NotifyPropertyChanged();
}
}
I have a treeview that allows me to select any node as the parent type, and currently I have a method for each type that allows me to reload all the child entities.
I would like to see how this could be made generic:
private async static Task<bool> RefreshLinesAsync(LocalUser ThisUser, ProjectEntities DBContext, object Entity)
{
List<object> NonExistingNodes = new List<object>();
var bContinue = false;
var PassedEntity = Entity as TblBase;
//Scan through all DB child entities and reload their DB values
foreach (var SubEntity in DBContext.tblLines.Where(x => x.ProjectID == PassedEntity.ID).ToList())
{
await DBContext.Entry(SubEntity).ReloadAsync().ContinueWith(x =>
{
if (!x.IsFaulted)
{
if ((SubEntity.COID.GetValueOrDefault() != 0) && (SubEntity.COID.GetValueOrDefault() != ThisUser.ID))
NotifyCOIDConflict(SubEntity, new CheckedOutArgs()
{
ConflictCOID = SubEntity.COID.GetValueOrDefault()
});
bContinue = true;
}
}, TaskScheduler.FromCurrentSynchronizationContext());
if (bContinue)
//Continue to child entities method
await RefreshGroupsAsync(ThisUser, DBContext, SubEntity);
}
return true;
}
private async static Task<bool> RefreshGroupsAsync(LocalUser ThisUser, ProjectEntities DBContext, object Entity)
{
List<object> NonExistingNodes = new List<object>();
var bContinue = false;
var PassedEntity = Entity as TblBase;
foreach (var SubEntity in DBContext.tblGroups.Where(x => x.LineID == PassedEntity.ID).ToList())
{
await DBContext.Entry(SubEntity).ReloadAsync().ContinueWith(x =>
{
if (!x.IsFaulted)
{
if ((SubEntity.COID.GetValueOrDefault() != 0) && (SubEntity.COID.GetValueOrDefault() != ThisUser.ID))
NotifyCOIDConflict(SubEntity, new CheckedOutArgs()
{
ConflictCOID = SubEntity.COID.GetValueOrDefault()
});
bContinue = true;
}
}, TaskScheduler.FromCurrentSynchronizationContext());
if (bContinue)
await RefreshStationsAsync(ThisUser,DBContext, SubEntity);
}
return true;
}
The only method I can see useful is Set(), although it does not provide a Where() method, which is critical since I do not want to retrieve the entire table.
You can make your functions generic. They maybe like this one:
private async static Task<bool> RefreshLinesAsync<TEntity>(LocalUser ThisUser, ProjectEntities DBContext, TEntity Entity) where TEntity : TblBase
{
List<TEntity> NonExistingNodes = new List<TEntity>();
var bContinue = false;
var PassedEntity = Entity as TblBase;
foreach (var SubEntity in DBContext.Set<TEntity>().Where(x => (x as TblBase).ProjectID == PassedEntity.ID).ToList()) {
//Your other code here...
}
}
The where clause in function definition, make you sure that this method can be called only with subclasses of TblBase.
EDIT:
I forgot to mention that you need to cast SubEntity as TblBase inside foreach loop to use it...
EDIT (in response of comments):
If you need to get all TblBase subclasses from your entity, you cannot make your function so generic if you keep them in separate tables: It will became hardly mantainable when you have to add more subclasses.
I suggest you to use a single table through Table Per Hierarchy (see this article in MSDN) changing TblBase from abstract to concrete class, then you can get all of them this way:
var allSubClassEntities = DBContext.Set<TblBase>();
In my project I have some cached values implemented using singleton pattern - it looks like this:
Roles GetRoles
{
get{
var cached = HttpContext.Current.Cache["key"];
if(cached == null){
cached = new GetRolesFromDb(...);
}
return cached as Roles;
}
}
When I change the roles I'm clearing the cache (iterating over all keys).
I think it isn't thread-safe - if some request tries to get cached roles,
cached != null and meanwhile cache had been cleared GetRoles returns null.
private object lockRoles = new object();
public Roles GetRoles
{
get
{
object cached = HttpContext.Current.Cache["key"];
if(cached == null)
{
lock(lockRoles)
{
cached = HttpContext.Current.Cache["key"];
if (cached == null)
{
cached = new GetRolesFromDb(...);
HttpContext.Current.Cache["key"] = cached;
}
}
}
return (Roles)cached;
}
}
public void ClearRoles()
{
HttpContext.Current.Cache.Remove("key");
}
Let's assume I have an instance of this class.
public class MyClass
{
public string LocationCode;
public string PickUpCode
}
There is another class that takes a List<MyClass> as input and saves to the DB.
Now I need to apply some business rules:
For example if LocationCode is null this item in the List<MyClass> must be skipped and the foreach loop must continue to the next item in the list.
I've written the following code and the items with null LocationCode are indeed skipped but the var instance = new SomeClass(); somehow remains in memory so when the loop reaches an valid item and proceeds to save it in the DB, it also saves all the previously skipped instances of var instance = new SomeClass();. Which means I have null entries in the DB.
I'm using NHibernate and Evictdoesn't seam to be doing the trick. Any suggestions?
public void Save(List<MyClass> listOfItems)
{
using (UnitOfWork.Start())
{
var repository = new Repository();
try
{
foreach (var item in listOfItems.Select(i => i.Item).Where(item => item != null))
{
var instance = new SomeClass();
if (pickUpCode != null)
{
instance.PickUpCode = pickUpCode;
}
else
{
instance.PickUpCode = null;
}
if (locationCode != null)
{
instance.StartLocation = locationCode
}
else
{
UnitOfWork.CurrentSession.Evict(instance);
continue;
}
repository.SaveSomeClass(instance);
}
}
catch (Exception ex)
{
_log.Error(" Unhandled error", ex);
}
}
}
** Because someone asked, here's some code on UnitOfWork.Start()
public static class UnitOfWork
{
public static IUnitOfWork Start();
}
public interface IUnitOfWork : IDisposable
{
bool IsInActiveTransaction { get; }
IUnitOfWorkFactory SessionFactory { get; }
IGenericTransaction BeginTransaction();
IGenericTransaction BeginTransaction(IsolationLevel isolationLevel);
void Flush();
void TransactionalFlush();
void TransactionalFlush(IsolationLevel isolationLevel);
}
Why don't you fail first and avoid all of this?
Example being:
foreach (var item in listOfItems.Select(i => i.Item).Where(item => item != null))
{
if (item.LocationCode == null){
continue;
}
var instance = new SomeClass();
if (pickUpCode != null)
{
instance.PickUpCode = pickUpCode;
}
else
{
instance.PickUpCode = null;
}
// if we reach here, location code is definitley not null, no need for the check
instance.StartLocation = locationCode
repository.SaveSomeClass(instance);
}
Alternatively, you could add the check to you LINQ where clause
foreach (var item in listOfItems.where(item=> item != null && item.LocationCode != null)
Without more code on how UnitofWork.Start works its hard to suggest. But, It's worth trying by implementing IDisposable on SomeClass.
Session.queryover() and session.save() are working well. All logics getting data from or saving data to the database are working well.Changes to database through session.update() and session.delete() are not reflected. No exeption was thrown. Using session.SaveOrUpdate did not solve this problem.
This is my mapping
[Serializable]
public class Requirement
{
public virtual int Id { get; set; }
public virtual int CreditRequired { get; set; }
public virtual string Name { get; set; }
public virtual IList<CourseRequirement> CourseRequirements
{
get
{
return new RequirementBC().getCourseRequirement(Id);
}
}
public Requirement()
{ }
public Requirement(DataRow row)
{
Id = int.Parse(row["Id"].ToString());
CreditRequired = int.Parse(row["CreditRequired"].ToString());
Name = row["Name"].ToString();
}
}
public class RequirementMap : ClassMapping<Requirement>
{
public RequirementMap()
{
Table("Requirements");
Id<int>(x => x.Id, m => { m.Column("Id"); m.Generator(Generators.Native); });
Property<int>(x => x.CreditRequired, m => { m.Column("CreditRequired");});
Property<string>(x => x.Name, m => { m.Column("Name"); });
}
}
This is my logic
[Serializable]
public class RequirementBC
{
ISession session = NHibernateHelper.GetCurrentSession();
public void UpdateRequirement(int reqId, string newName, int creditsRequired)
{
session.BeginTransaction();
var req = session.QueryOver<Requirement>().Where(x => x.Id == reqId).SingleOrDefault<Requirement>();
var old = session.QueryOver<Requirement>().Where(x => x.Name == newName && x.Id != reqId).SingleOrDefault<Requirement>();
if (old != null)
throw new Exception("Requirement with that name already exists");
req.Name = newName;
req.CreditRequired = creditsRequired;
session.Update(req);
session.Flush();
session.Transaction.Commit();
}
}
Logic for getting current session
public static ISession GetCurrentSession()
{
HttpContext context = HttpContext.Current;
ISession currentSession = context.Items[CURRENT_NHIBERNATE_SESSION_KEY] as ISession;
if (currentSession == null)
{
currentSession = sessionFactory.OpenSession();
context.Items[CURRENT_NHIBERNATE_SESSION_KEY] = currentSession;
}
if (currentSession.Connection.State == System.Data.ConnectionState.Closed)
{
currentSession = sessionFactory.OpenSession();
}
if (!currentSession.IsConnected)
{
currentSession = sessionFactory.OpenSession();
}
if (!currentSession.IsOpen)
{
currentSession = sessionFactory.OpenSession();
}
if (currentSession.IsDirty())
{
currentSession.Clear();
}
return currentSession;
}
From my searches on this forum and others, those who have encountered such were getting one exception or the other, but no exception was thrown in my own case which makes the problem difficult to trace.
Any help will be appreciated.
You shouldn't have to flush the session if you are using transactions. Also I'm not sure about the syntax you are using for transactions. If I were to do this it would look like this:
public void UpdateRequirement(int reqId, string newName, int creditsRequired)
{
using(ISession session = NHibernateHelper.GetCurrentSession())
{
using(ITransaction transaction = session.BeginTransaction())
{
var req = session.QueryOver<Requirement>().Where(x => x.Id == reqId).SingleOrDefault<Requirement>();
var old = session.QueryOver<Requirement>().Where(x => x.Name == newName && x.Id != reqId).SingleOrDefault<Requirement>();
if (old != null)
throw new Exception("Requirement with that name already exists");
req.Name = newName;
req.CreditRequired = creditsRequired;
transaction.Commit();
}
}
}
It is not required to call Update() on an object already tracked by the session. (And Update() will not necessarily execute an UPDATE SQL statement.)
Where do you open and commit your transaction?
Your GetCurrentSession() looks weird - so far I've not felt the need to have all those check my "get-current-session" methods. Particularly the if-dirty-then-clear looks strange. I would typically expect to be able to call "get-current-session" multiple times within a unit-of-work without it have the side effect of throwing away any changes to far.