/// <summary> /// <para> /// Overloaded. Attempts to read an entity from the cache. If it is not found then the <paramref name="reader"/> /// is used to retrieve a fresh copy of the entity. If a fresh copy is retrieved, the given /// <paramref name="itemPolicy"/> is used to provide additional control over the entity's lifetime in the cache. /// </para> /// </summary> /// <remarks> /// <para> /// This is the core method that attempts to read entities from this cache instance and then either returns them /// from the <see cref="BackingStore"/> or reads them in afresh using the <paramref name="reader"/>. When entities /// are retrieved using the reader they are implicitly added to the cache as part of the action. This method can /// result in two core outcomes: /// </para> /// <list type="table"> /// <item> /// <term>Cache hit</term> /// <description> /// The desired entity was found within the cache and is suitable to be returned directly from the cache. /// </description> /// </item> /// <item> /// <term>Cache miss</term> /// <description> /// The desired entity was either not found within the cache or a policy deemed that is must not be returned from /// the cache. The entity is fetched using the <paramref name="reader"/>, added to the cache and then returned. /// </description> /// </item> /// </list> /// </remarks> /// <param name="identity"> /// A <see cref="IIdentity"/> /// </param> /// <param name="itemPolicy"> /// A <see cref="IItemPolicy"/> /// </param> /// <param name="reader"> /// A <see cref="EntityReader"/> /// </param> /// <returns> /// A <see cref="IEntity"/> /// </returns> public IEntity Read(IIdentity identity, IItemPolicy itemPolicy, EntityReader reader) { IEntity output; if (identity == null) { throw new ArgumentNullException("identity"); } else if (reader == null) { throw new ArgumentNullException("reader"); } try { this.SyncRoot.EnterUpgradeableReadLock(); if (this.IsCacheHit(identity)) { // This is a cache hit, downgrade our lock immediately and return the entity from the cache this.SyncRoot.EnterReadLock(); this.SyncRoot.ExitUpgradeableReadLock(); this.OnCacheHit(identity); output = this.BackingStore.Read(identity); } else { // This is a cache miss, upgrade our lock and go get the entity from the reader this.SyncRoot.EnterWriteLock(); this.OnCacheMiss(identity); output = reader(identity); this.LockedAdd(output, itemPolicy); } } finally { if (this.SyncRoot.IsWriteLockHeld) { this.SyncRoot.ExitWriteLock(); } if (this.SyncRoot.IsUpgradeableReadLockHeld) { this.SyncRoot.ExitUpgradeableReadLock(); } if (this.SyncRoot.IsReadLockHeld) { this.SyncRoot.ExitReadLock(); } } return(output); }
/// <summary> /// <para>Initialises this instance with the given components.</para> /// </summary> /// <param name="replacementPolicy"> /// An <see cref="IReplacementPolicy"/> /// </param> /// <param name="backingStore"> /// An <see cref="ICacheBackingStore"/> /// </param> /// <param name="defaultItemPolicy"> /// An <see cref="IItemPolicy"/> /// </param> public EntityCacheBase(IReplacementPolicy replacementPolicy, ICacheBackingStore backingStore, IItemPolicy defaultItemPolicy) { _disposed = false; this.SyncRoot = new ReaderWriterLockSlim(); this.ItemPolicies = new Dictionary <IIdentity, IItemPolicy>(); this.ReplacementPolicy = replacementPolicy; this.BackingStore = backingStore; this.DefaultItemPolicy = defaultItemPolicy; this.CacheHit += this.ReplacementPolicy.HandleCacheEvent; this.CacheMiss += this.ReplacementPolicy.HandleCacheEvent; this.ItemAdded += this.ReplacementPolicy.HandleCacheEvent; this.ItemRemoved += this.ReplacementPolicy.HandleCacheEvent; }
/// <summary> /// <para> /// Overloaded. Explicitly adds a single entity to the cache, along with an explicit policy, providing additional /// control over its lifetime in the cache. /// </para> /// </summary> /// <param name="entity"> /// A <see cref="IEntity"/> /// </param> /// <param name="itemPolicy"> /// A <see cref="IItemPolicy"/> /// </param> public void Add(IEntity entity, IItemPolicy itemPolicy) { if (entity == null) { throw new ArgumentNullException("entity"); } try { this.SyncRoot.EnterWriteLock(); this.LockedAdd(entity, itemPolicy); } finally { if (this.SyncRoot.IsWriteLockHeld) { this.SyncRoot.ExitWriteLock(); } } }
/// <summary> /// <para> /// Performs the actual operations involved in adding an <paramref name="entity"/> to this instance. This method /// must be called within a write lock. /// </para> /// </summary> /// <param name="entity"> /// A <see cref="IEntity"/> /// </param> /// <param name="itemPolicy"> /// A <see cref="IItemPolicy"/> /// </param> private void LockedAdd(IEntity entity, IItemPolicy itemPolicy) { IIdentity identity = entity.GetIdentity(); if (!this.SyncRoot.IsWriteLockHeld) { throw new InvalidOperationException("Cannot add an entity to the current instance unless an appropriate " + "lock is held."); } else if (this.BackingStore == null) { throw new InvalidOperationException("The cache backing store must not be null."); } this.BackingStore.Add(entity); if (itemPolicy != null) { this.ItemPolicies.Add(identity, itemPolicy); } this.OnItemAdded(identity); }
/// <summary> /// <para>Initialises this instance, including a default item policy.</para> /// </summary> /// <param name="policy"> /// A <see cref="IReplacementPolicy"/> /// </param> /// <param name="backingStore"> /// A <see cref="ICacheBackingStore"/> /// </param> /// <param name="defaultItemPolicy"> /// A <see cref="IItemPolicy"/> /// </param> public InlineEntityCache (IReplacementPolicy policy, ICacheBackingStore backingStore, IItemPolicy defaultItemPolicy) : base(policy, backingStore, defaultItemPolicy) { this.ItemAdded += HandleItemAdded; }
/// <summary> /// <para>Initialises this instance, including a default item policy.</para> /// </summary> /// <param name="policy"> /// A <see cref="IReplacementPolicy"/> /// </param> /// <param name="backingStore"> /// A <see cref="ICacheBackingStore"/> /// </param> /// <param name="defaultItemPolicy"> /// A <see cref="IItemPolicy"/> /// </param> public InlineEntityCache(IReplacementPolicy policy, ICacheBackingStore backingStore, IItemPolicy defaultItemPolicy) : base(policy, backingStore, defaultItemPolicy) { this.ItemAdded += HandleItemAdded; }
/// <summary> /// <para>Initialises this instance with the given components.</para> /// </summary> /// <param name="replacementPolicy"> /// An <see cref="IReplacementPolicy"/> /// </param> /// <param name="backingStore"> /// An <see cref="ICacheBackingStore"/> /// </param> /// <param name="defaultItemPolicy"> /// An <see cref="IItemPolicy"/> /// </param> public EntityCacheBase (IReplacementPolicy replacementPolicy, ICacheBackingStore backingStore, IItemPolicy defaultItemPolicy) { _disposed = false; this.SyncRoot = new ReaderWriterLockSlim(); this.ItemPolicies = new Dictionary<IIdentity, IItemPolicy>(); this.ReplacementPolicy = replacementPolicy; this.BackingStore = backingStore; this.DefaultItemPolicy = defaultItemPolicy; this.CacheHit += this.ReplacementPolicy.HandleCacheEvent; this.CacheMiss += this.ReplacementPolicy.HandleCacheEvent; this.ItemAdded += this.ReplacementPolicy.HandleCacheEvent; this.ItemRemoved += this.ReplacementPolicy.HandleCacheEvent; }
/// <summary> /// <para> /// Performs the actual operations involved in adding an <paramref name="entity"/> to this instance. This method /// must be called within a write lock. /// </para> /// </summary> /// <param name="entity"> /// A <see cref="IEntity"/> /// </param> /// <param name="itemPolicy"> /// A <see cref="IItemPolicy"/> /// </param> private void LockedAdd(IEntity entity, IItemPolicy itemPolicy) { IIdentity identity = entity.GetIdentity(); if(!this.SyncRoot.IsWriteLockHeld) { throw new InvalidOperationException("Cannot add an entity to the current instance unless an appropriate " + "lock is held."); } else if(this.BackingStore == null) { throw new InvalidOperationException("The cache backing store must not be null."); } this.BackingStore.Add(entity); if(itemPolicy != null) { this.ItemPolicies.Add(identity, itemPolicy); } this.OnItemAdded(identity); }
/// <summary> /// <para> /// Overloaded. Attempts to read an entity from the cache. If it is not found then the <paramref name="reader"/> /// is used to retrieve a fresh copy of the entity. If a fresh copy is retrieved, the given /// <paramref name="itemPolicy"/> is used to provide additional control over the entity's lifetime in the cache. /// </para> /// </summary> /// <remarks> /// <para> /// This is the core method that attempts to read entities from this cache instance and then either returns them /// from the <see cref="BackingStore"/> or reads them in afresh using the <paramref name="reader"/>. When entities /// are retrieved using the reader they are implicitly added to the cache as part of the action. This method can /// result in two core outcomes: /// </para> /// <list type="table"> /// <item> /// <term>Cache hit</term> /// <description> /// The desired entity was found within the cache and is suitable to be returned directly from the cache. /// </description> /// </item> /// <item> /// <term>Cache miss</term> /// <description> /// The desired entity was either not found within the cache or a policy deemed that is must not be returned from /// the cache. The entity is fetched using the <paramref name="reader"/>, added to the cache and then returned. /// </description> /// </item> /// </list> /// </remarks> /// <param name="identity"> /// A <see cref="IIdentity"/> /// </param> /// <param name="itemPolicy"> /// A <see cref="IItemPolicy"/> /// </param> /// <param name="reader"> /// A <see cref="EntityReader"/> /// </param> /// <returns> /// A <see cref="IEntity"/> /// </returns> public IEntity Read (IIdentity identity, IItemPolicy itemPolicy, EntityReader reader) { IEntity output; if(identity == null) { throw new ArgumentNullException("identity"); } else if(reader == null) { throw new ArgumentNullException("reader"); } try { this.SyncRoot.EnterUpgradeableReadLock(); if(this.IsCacheHit(identity)) { // This is a cache hit, downgrade our lock immediately and return the entity from the cache this.SyncRoot.EnterReadLock(); this.SyncRoot.ExitUpgradeableReadLock(); this.OnCacheHit(identity); output = this.BackingStore.Read(identity); } else { // This is a cache miss, upgrade our lock and go get the entity from the reader this.SyncRoot.EnterWriteLock(); this.OnCacheMiss(identity); output = reader(identity); this.LockedAdd(output, itemPolicy); } } finally { if(this.SyncRoot.IsWriteLockHeld) { this.SyncRoot.ExitWriteLock(); } if(this.SyncRoot.IsUpgradeableReadLockHeld) { this.SyncRoot.ExitUpgradeableReadLock(); } if(this.SyncRoot.IsReadLockHeld) { this.SyncRoot.ExitReadLock(); } } return output; }
/// <summary> /// <para> /// Overloaded. Explicitly adds a single entity to the cache, along with an explicit policy, providing additional /// control over its lifetime in the cache. /// </para> /// </summary> /// <param name="entity"> /// A <see cref="IEntity"/> /// </param> /// <param name="itemPolicy"> /// A <see cref="IItemPolicy"/> /// </param> public void Add (IEntity entity, IItemPolicy itemPolicy) { if(entity == null) { throw new ArgumentNullException("entity"); } try { this.SyncRoot.EnterWriteLock(); this.LockedAdd(entity, itemPolicy); } finally { if(this.SyncRoot.IsWriteLockHeld) { this.SyncRoot.ExitWriteLock(); } } }