/// <summary> /// Check the cache. /// </summary> /// <param name="key">Key to look up.</param> /// <param name="result">The value result - either from cache or freshly determined.</param> /// <param name="valueFactory">A callback that will provide the values.</param> /// <returns></returns> protected bool TryGetOrAdd(TKey key, out TValue result, Func <TKey, TValue> valueFactory) { bool fromCache = false; // Wrap value-factory to handle cache invalidator Func <TKey, TValue> valueFactoryImpl = (k) => { TValue innerResult; using (CacheContext cacheContext = new CacheContext()) { innerResult = valueFactory(key); // Add the cache context entries to the appropriate CacheInvalidator _cacheInvalidator.AddInvalidations(cacheContext, key); } return(innerResult); }; // Check cache fromCache = Cache.TryGetOrAdd(key, out result, valueFactoryImpl); if (fromCache && CacheContext.IsSet()) { // Add the already stored changes that should invalidate this cache // entry to any outer or containing cache contexts. using (CacheContext cacheContext = CacheContext.GetContext()) { cacheContext.AddInvalidationsFor(_cacheInvalidator, key); } } return(fromCache); }
public void Test_ItemsRemoved() { CacheInvalidator <int, string> cacheInvalidator; ICache <int, string> cache; const int testKey1 = 42; const int testKey2 = 54; const string testValue1 = "foo"; const string testValue2 = "bar"; cache = new DictionaryCache <int, string>(); cache.Add(testKey1, testValue1); cache.Add(testKey2, testValue2); cacheInvalidator = new CacheInvalidator <int, string>(cache, "foo"); using (CacheContext cacheContext = new CacheContext()) { cacheContext.Entities.Add(testKey1); cacheInvalidator.AddInvalidations(cacheContext, testKey1); } using (CacheContext cacheContext = new CacheContext()) { cacheContext.Entities.Add(testKey2); cacheInvalidator.AddInvalidations(cacheContext, testKey2); } cache.Remove(testKey1); Assert.That(cacheInvalidator.EntityToCacheKey.Keys, Has.None.EqualTo(testKey1)); Assert.That(cacheInvalidator.EntityToCacheKey.Keys, Has.Exactly(1).EqualTo(testKey2)); }
public void Test_OnEntityChange_EntityTypes() { MockRepository mockRepository; CacheInvalidator <long, string> cacheInvalidator; ICache <long, string> cache; IEntity[] testEntities; Mock <IEntity> mockEntity; const int numEntities = 10; const long typeIdOffset = 100; mockRepository = new MockRepository(MockBehavior.Loose); testEntities = new IEntity[numEntities]; for (int i = 0; i < testEntities.Length; i++) { mockEntity = mockRepository.Create <IEntity>(); mockEntity.SetupGet(e => e.Id).Returns(i); mockEntity.SetupGet(e => e.TypeIds).Returns(new [] { typeIdOffset + 100 }); testEntities[i] = mockEntity.Object; } cache = new DictionaryCache <long, string>(); for (int i = 0; i < numEntities; i++) { cache.Add(i, i.ToString()); } cacheInvalidator = new CacheInvalidator <long, string>(cache, "a"); // Make the second and third entities depend on the type of the first. using (CacheContext cacheContext = new CacheContext()) { cacheContext.EntityTypes.Add(testEntities[0].TypeIds.First()); // Will convert to EntityRef using this ID cacheInvalidator.AddInvalidations(cacheContext, testEntities[1].Id); cacheInvalidator.AddInvalidations(cacheContext, testEntities[2].Id); } // Save the first entity (only) cacheInvalidator.OnEntityChange(new [] { testEntities[0] }, InvalidationCause.Save, null); Assert.That( cache.Select(ce => ce.Key), Is.EquivalentTo( testEntities.Select(er => er.Id) .Where(id => id != testEntities[1].Id && id != testEntities[2].Id)), "Second and third entities (only) have not been removed"); }
public void Test_OnRelationshipChange() { CacheInvalidator <int, string> cacheInvalidator; ICache <int, string> cache; EntityRef[] testRelationshipTypes; const int numRelationshipTypes = 10; Func <long, bool> relationshipTypesToRemove; cache = new DictionaryCache <int, string>(); for (int i = 0; i < numRelationshipTypes; i++) { cache.Add(i, i.ToString()); } relationshipTypesToRemove = e => e % 2 == 0; // All even numbered relationship types. testRelationshipTypes = Enumerable.Range(0, numRelationshipTypes) .Where(i => relationshipTypesToRemove(i)) .Select(i => new EntityRef(i)).ToArray(); cacheInvalidator = new CacheInvalidator <int, string>(cache, "a"); for (int i = 0; i < numRelationshipTypes; i++) { using (CacheContext cacheContext = new CacheContext()) { cacheContext.RelationshipTypes.Add(i); cacheInvalidator.AddInvalidations(cacheContext, i); } } cacheInvalidator.OnRelationshipChange(testRelationshipTypes); Assert.That(cache.Where(ce => relationshipTypesToRemove(ce.Key)), Is.Empty); }
/// <summary> /// Execute a request for bulk data from the SQL database. /// </summary> /// <param name="request">The requested data</param> /// <returns></returns> public BulkRequestResult GetBulkResult(EntityRequest request) { if (request == null) { throw new ArgumentNullException("request"); } // Bypass cache if (request.IgnoreResultCache) { return(CreateResult(request)); } BulkRequestResult result; CachingBulkRequestRunnerValue cacheValue; CachingBulkRequestRunnerKey key = CachingBulkRequestRunnerKey.Create(request); // Check cache bool inCache = Cache.TryGetValue(key, out cacheValue); // Should parent cache contexts be notified of invalidations // .. no for now, for compatibility with previous system. Consider changing bool notifyParentCacheContext = false; if (!inCache) { using (var cacheContext = new CacheContext(notifyParentCacheContext ? ContextType.New : ContextType.Detached)) // Detached for now.. { result = CreateResult(request); cacheValue = new CachingBulkRequestRunnerValue(result); Cache.Add(key, cacheValue); // Add the cache context entries to the appropriate CacheInvalidator cacheContext.Entities.Add(result.AllEntities.Keys); cacheContext.EntityInvalidatingRelationshipTypes.Add(GetRelationshipTypesUsed(result)); _cacheInvalidator.AddInvalidations(cacheContext, key); } } else { if (notifyParentCacheContext && CacheContext.IsSet( )) { using (CacheContext cacheContext = new CacheContext(ContextType.Attached)) { // Add the already stored changes that should invalidate this cache // entry to any outer or containing cache contexts. cacheContext.AddInvalidationsFor(_cacheInvalidator, key); } } } result = cacheValue.BulkRequestResult; request.ResultFromCache = inCache; // TODO: Find a better channel to return this info. (It can't be in the response, because that's cached) return(result); }
/// <summary> /// Build an <see cref="EntityMemberRequest"/> used to look for entities /// to perform additional security checks on. /// </summary> /// <param name="entityType"> /// The type of entity whose security is being checked. This cannot be null. /// </param> /// <param name="permissions">The type of permissions required.</param> /// <returns> /// The <see cref="EntityMemberRequest"/>. /// </returns> /// <exception cref="ArgumentNullException"> /// <paramref name="entityType"/> cannot be null. /// </exception> public EntityMemberRequest BuildEntityMemberRequest(EntityType entityType, IList <EntityRef> permissions) { if (entityType == null) { throw new ArgumentNullException("entityType"); } bool isModify = false; if (permissions != null) { foreach (EntityRef perm in permissions) { if (perm.Id == Permissions.Modify.Id || perm.Id == Permissions.Delete.Id) { isModify = true; break; } } } // negative keys for 'modify', +ve keys for read // because #27911 popped up just hours before the branch of a feature that got promised to someone long cacheKey = isModify ? -entityType.Id : entityType.Id; EntityMemberRequest result; if (!Cache.TryGetValue(cacheKey, out result)) { using (CacheContext cacheContext = new CacheContext()) { result = Factory.BuildEntityMemberRequest(entityType, permissions); Cache.Add(cacheKey, result); _cacheInvalidator.AddInvalidations(cacheContext, cacheKey); } } else if (CacheContext.IsSet( )) { // Add the already stored changes that should invalidate this cache // entry to any outer or containing cache contexts. using (CacheContext cacheContext = CacheContext.GetContext( )) { cacheContext.AddInvalidationsFor(_cacheInvalidator, cacheKey); } } return(result); }
public void Test_AddInvalidations() { CacheInvalidator <string, string> cacheInvalidator; long testEntity; long testRelationshipType; long testFieldType; long testEntityInvalidatingRelationshipTypeRef; long testEntityTypeRef; cacheInvalidator = new CacheInvalidator <string, string>( new DictionaryCache <string, string>(), "foo"); testEntity = 1; testRelationshipType = 2; testFieldType = 3; testEntityInvalidatingRelationshipTypeRef = 4; testEntityTypeRef = 5; using (CacheContext cacheContext = new CacheContext()) { cacheContext.Entities.Add(testEntity); cacheContext.RelationshipTypes.Add(testRelationshipType); cacheContext.FieldTypes.Add(testFieldType); cacheContext.EntityInvalidatingRelationshipTypes.Add(testEntityInvalidatingRelationshipTypeRef); cacheContext.EntityTypes.Add(testEntityTypeRef); // Sanity check Assert.That(cacheInvalidator.EntityToCacheKey, Has.Property("Keys").Empty); Assert.That(cacheInvalidator.RelationshipTypeToCacheKey, Has.Property("Keys").Empty); Assert.That(cacheInvalidator.FieldTypeToCacheKey, Has.Property("Keys").Empty); Assert.That(cacheInvalidator.EntityInvalidatingRelationshipTypesToCacheKey, Has.Property("Keys").Empty); Assert.That(cacheInvalidator.EntityTypeToCacheKey, Has.Property("Keys").Empty); cacheInvalidator.AddInvalidations(cacheContext, "foo"); Assert.That(cacheInvalidator.EntityToCacheKey, Has.Property("Keys").Exactly(1).EqualTo(testEntity)); Assert.That(cacheInvalidator.RelationshipTypeToCacheKey, Has.Property("Keys").Exactly(1).EqualTo(testRelationshipType)); Assert.That(cacheInvalidator.FieldTypeToCacheKey, Has.Property("Keys").Exactly(1).EqualTo(testFieldType)); Assert.That(cacheInvalidator.EntityInvalidatingRelationshipTypesToCacheKey, Has.Property("Keys").Exactly(1).EqualTo(testEntityInvalidatingRelationshipTypeRef)); Assert.That(cacheInvalidator.EntityTypeToCacheKey, Has.Property("Keys").Exactly(1).EqualTo(testEntityTypeRef)); } }
public void Test_AddInvalidationsFor(long[] entities, long[] relationshipTypes, long[] fieldTypes, long[] entityInvalidatingRelationshipTypes) { CacheInvalidator <long, long> cacheInvalidator; const int testKey = 1; IList <long> entityRefs; IList <long> relationshipTypeRefs; IList <long> fieldTypeRefs; IList <long> entityInvalidatingRelationshipTypeRefs; entityRefs = entities.ToList(); relationshipTypeRefs = relationshipTypes.ToList(); fieldTypeRefs = fieldTypes.ToList(); entityInvalidatingRelationshipTypeRefs = entityInvalidatingRelationshipTypes.ToList(); cacheInvalidator = new CacheInvalidator <long, long>(new DictionaryCache <long, long>(), "test"); using (CacheContext originalCacheContext = new CacheContext()) { originalCacheContext.Entities.Add(entityRefs); originalCacheContext.RelationshipTypes.Add(relationshipTypeRefs); originalCacheContext.FieldTypes.Add(fieldTypeRefs); originalCacheContext.EntityInvalidatingRelationshipTypes.Add(entityInvalidatingRelationshipTypeRefs); cacheInvalidator.AddInvalidations(originalCacheContext, testKey); } using (CacheContext outerCacheContext = new CacheContext()) using (CacheContext innerCacheContext = new CacheContext()) { Assert.That(innerCacheContext.Entities, Is.Empty, "Entities not initially empty"); Assert.That(innerCacheContext.RelationshipTypes, Is.Empty, "RelationshipTypes not initially empty"); Assert.That(innerCacheContext.FieldTypes, Is.Empty, "FieldTypes not initially empty"); Assert.That(innerCacheContext.EntityInvalidatingRelationshipTypes, Is.Empty, "EntityInvalidatingRelationshipTypes not initially empty"); innerCacheContext.AddInvalidationsFor(cacheInvalidator, testKey); Assert.That(innerCacheContext.Entities, Is.EquivalentTo(entityRefs).Using(EntityRefComparer.Instance), "Unexpected Entities"); Assert.That(innerCacheContext.RelationshipTypes, Is.EquivalentTo(relationshipTypeRefs).Using(EntityRefComparer.Instance), "Unexpected RelationshipTypes"); Assert.That(innerCacheContext.FieldTypes, Is.EquivalentTo(fieldTypeRefs).Using(EntityRefComparer.Instance), "Unexpected FieldTypes"); Assert.That(innerCacheContext.EntityInvalidatingRelationshipTypes, Is.EquivalentTo(entityInvalidatingRelationshipTypeRefs).Using(EntityRefComparer.Instance), "Unexpected EntityInvalidatingRelationshipTypes"); Assert.That(outerCacheContext.Entities, Is.EquivalentTo(entityRefs).Using(EntityRefComparer.Instance), "Unexpected Entities in outer cache context"); Assert.That(outerCacheContext.RelationshipTypes, Is.EquivalentTo(relationshipTypeRefs).Using(EntityRefComparer.Instance), "Unexpected RelationshipTypes in outer cache context"); Assert.That(outerCacheContext.FieldTypes, Is.EquivalentTo(fieldTypeRefs).Using(EntityRefComparer.Instance), "Unexpected FieldTypes in outer cache context"); Assert.That(outerCacheContext.EntityInvalidatingRelationshipTypes, Is.EquivalentTo(entityInvalidatingRelationshipTypeRefs).Using(EntityRefComparer.Instance), "Unexpected EntityInvalidatingRelationshipTypes in outer cache context"); } }