/// <summary>Merges the temporary storage with the global storage.</summary> /// <param name="dataSourceInfo">The parameter is not used.</param> protected override void SaveChangesCore(DataSourceInfo dataSourceInfo) { MemoryStore <TEntity> memoryStore = this.SelectMemoryStore(dataSourceInfo); try { /* Make sure no one has access to the global and local storage */ memoryStore.EnterWriteLock(); this.temporaryStorageLock.EnterWriteLock(); /* First, check if all the updated entities still exist in the global storage */ Dictionary <int, TEntity> updatedEntities = new Dictionary <int, TEntity>(); foreach (TEntity entity in this.updateCache) { var storedEntity = memoryStore.Storage.Select((item, index) => new { Entity = item, Index = index }) .FirstOrDefault(a => a.Entity.RecordId == entity.RecordId); if (storedEntity == null) { throw new InvalidOperationException( string.Format(CultureInfo.CurrentCulture, "Cannot update entity {0} since it does not exist in the global storage", entity.RecordId)); } else { updatedEntities.Add(storedEntity.Index, entity); } } /* Then, check if all the deleted entities still exist in the global storage */ Dictionary <int, TEntity> deletedEntities = new Dictionary <int, TEntity>(); foreach (TEntity entity in this.deletionCache) { var storedEntity = memoryStore.Storage.Select((item, index) => new { Entity = item, Index = index }) .FirstOrDefault(a => a.Entity.RecordId == entity.RecordId); if (storedEntity == null) { throw new InvalidOperationException( string.Format(CultureInfo.CurrentCulture, "Cannot delete entity {0} since it does not exist in the global storage", entity.RecordId)); } else { deletedEntities.Add(storedEntity.Index, entity); } } /* All the pre-checks have been completed, start the saving */ /* First, perform the updates */ foreach (KeyValuePair <int, TEntity> updatedEntity in updatedEntities) { if (this.SelectCloneDataSourceItems(dataSourceInfo)) { memoryStore.Storage[updatedEntity.Key] = ((ICloneable)updatedEntity.Value).Clone() as TEntity; } else { memoryStore.Storage[updatedEntity.Key] = updatedEntity.Value; } } /* Then perform the deletions */ int iteration = 0; foreach (KeyValuePair <int, TEntity> deletedEntity in deletedEntities.OrderBy(kvp => kvp.Key)) { memoryStore.Storage.RemoveAt(deletedEntity.Key - iteration); ++iteration; } /* Then apply new identifiers to the new entities */ int startId = memoryStore.Storage.DefaultIfEmpty(new TEntity { RecordId = 0 }).Max(t => t.RecordId) + 1; this.ApplyIdentifiers(this.additionCache, startId); /* Then add the added entities to the global storage */ foreach (TEntity addedEntity in this.additionCache) { if (this.SelectCloneDataSourceItems(dataSourceInfo)) { memoryStore.Storage.Add(((ICloneable)addedEntity).Clone() as TEntity); } else { memoryStore.Storage.Add(addedEntity); } } /* Finally, clear the local storage */ this.additionCache.Clear(); this.updateCache.Clear(); this.deletionCache.Clear(); } finally { memoryStore.ExitWriteLock(); this.temporaryStorageLock.ExitWriteLock(); } }
/// <summary>Updates a collection of entities in the repository. Depending on the status of each entity, it is updated in the addition-cache or /// it is added to the update-cache.</summary> /// <param name="entities">The entities that contain the updated values.</param> /// <param name="dataSourceInfo">Information about the data source that may not have been set at an earlier stage. This parameter is not used. /// </param> /// <returns>The entities as they are stored in the repository.</returns> protected override IEnumerable <TEntity> UpdateEntitiesCore(IEnumerable <TEntity> entities, DataSourceInfo dataSourceInfo) { if (entities.Any(e => e.RecordId == 0)) { throw new InvalidOperationException("Cannot update an entity whose identifier is zero."); } EntityEqualityComparer <TEntity> entityComparer = new EntityEqualityComparer <TEntity>(); MemoryStore <TEntity> memoryStore = this.SelectMemoryStore(dataSourceInfo); IEnumerable <TEntity> addedEntities = entities.Where(e => e.RecordId < 0); IEnumerable <TEntity> updatedEntities = entities.Where(e => e.RecordId > 0); this.temporaryStorageLock.EnterWriteLock(); memoryStore.EnterReadLock(); /* Make a copy of the caches. That way, if any thing goes wrong all the changes can be made undone */ List <TEntity> tempAdditionCache = this.additionCache.ToList(); List <TEntity> tempUpdateCache = this.updateCache.ToList(); List <TEntity> tempDeletionCache = this.deletionCache.ToList(); List <TEntity> newlyAddedEntities = new List <TEntity>(); List <TEntity> currentUpdatedEntities = new List <TEntity>(); try { foreach (TEntity addedEntity in addedEntities) { /* If the entity is marked for addition, update the entity in the addition cache */ if (tempAdditionCache.Contains(addedEntity, entityComparer)) { int indexOfEntity = tempAdditionCache.IndexOf(addedEntity, entityComparer); TEntity repositoryEntity = tempAdditionCache[indexOfEntity]; /* Copy the values of the updated entity into the cached entity */ repositoryEntity.CopyFrom(addedEntity); newlyAddedEntities.Add(repositoryEntity); } else { throw new InvalidOperationException("Could not find the entity in the addition-cache."); } } foreach (TEntity updatedEntity in updatedEntities) { if (memoryStore.Storage.Contains(updatedEntity, entityComparer)) { /* If the entity is already marked for deletion, it can no longer be updated. */ if (tempDeletionCache.Contains(updatedEntity, entityComparer)) { throw new InvalidOperationException("Cannot update the entity since it already marked for deletion."); } TEntity repositoryEntity; if (tempUpdateCache.Contains(updatedEntity, entityComparer)) { /* If the entity was already marked for update, replace it in the update cache */ int indexOfEntity = tempUpdateCache.IndexOf(updatedEntity, entityComparer); repositoryEntity = tempUpdateCache[indexOfEntity]; /* Copy the values of the updated entity into the cached entity */ repositoryEntity.CopyFrom(updatedEntity); } else { /* Retrieve the original version of the entity from the storage */ TEntity originalEntity = memoryStore.Storage[memoryStore.Storage.IndexOf(updatedEntity, entityComparer)]; /* Create a copy of the original entity to avoid any unwanted updates of the original entity */ repositoryEntity = originalEntity.CreateCopyOrClone(); /* Copy the values of the updated entity into the (copy of) original entity */ repositoryEntity.CopyFrom(updatedEntity); /* Store the updated entity in the update cache */ tempUpdateCache.Add(repositoryEntity); } currentUpdatedEntities.Add(repositoryEntity); } else { throw new InvalidOperationException("Could not find the entity in the internal cache."); } } /* Replace the original caches to complete the 'transaction' */ this.additionCache = tempAdditionCache; this.updateCache = tempUpdateCache; this.deletionCache = tempDeletionCache; if (this.SelectCloneDataSourceItems(dataSourceInfo)) { return(newlyAddedEntities.Concat(currentUpdatedEntities).Select(entity => ((ICloneable)entity).Clone() as TEntity).ToList()); } else { IEnumerable <TEntity> resultList = newlyAddedEntities.Concat(currentUpdatedEntities); resultList.ForEach(entity => entities.Single(e => e.RecordId == entity.RecordId).CopyFrom(entity)); return(entities); } } finally { memoryStore.ExitReadLock(); if (this.temporaryStorageLock.IsWriteLockHeld) { this.temporaryStorageLock.ExitWriteLock(); } } }
/// <summary>Removes a collection of entities from the repository. Depending on the status of each entity, it is removed from the addition-cache /// or it is added to the deletion-cache until it is saved using the <see cref="Repository{T}.SaveChanges()"/> method.</summary> /// <param name="entities">The entities that must be removed.</param> /// <param name="dataSourceInfo">Information about the data source that may not have been set at an earlier stage. This parameter is not used. /// </param> protected override void DeleteEntitiesCore(IEnumerable <TEntity> entities, DataSourceInfo dataSourceInfo) { if (entities.Any(e => e.RecordId == 0)) { throw new InvalidOperationException("Cannot delete an entity whose identifier is zero."); } IEqualityComparer <TEntity> entityComparer = new EntityEqualityComparer <TEntity>(); MemoryStore <TEntity> memoryStore = this.SelectMemoryStore(dataSourceInfo); this.temporaryStorageLock.EnterWriteLock(); memoryStore.EnterReadLock(); IEnumerable <TEntity> addedEntities = entities.Where(e => e.RecordId < 0); IEnumerable <TEntity> existingEntities = entities.Where(e => e.RecordId > 0); /* Make a copy of the caches. That way, if any thing goes wrong all the changes can be made undone */ List <TEntity> tempAdditionCache = this.additionCache.ToList(); List <TEntity> tempUpdateCache = this.updateCache.ToList(); List <TEntity> tempDeletionCache = this.deletionCache.ToList(); try { foreach (TEntity addedEntity in addedEntities) { /* If the ID is negative, it should be marked for addition */ if (tempAdditionCache.Contains(addedEntity, entityComparer)) { tempAdditionCache.Remove(addedEntity, entityComparer); } else { throw new InvalidOperationException("Could not find the entity in the memory."); } } foreach (TEntity existingEntity in existingEntities) { /* If the entity was marked for update, remove that mark */ if (tempUpdateCache.Contains(existingEntity, entityComparer)) { tempUpdateCache.Remove(existingEntity, entityComparer); } /* If the entity exists in the original data source and has not yet been marked for deletion, mark it now */ if (memoryStore.Storage.Contains(existingEntity, entityComparer)) { if (!tempDeletionCache.Contains(existingEntity, entityComparer)) { tempDeletionCache.Add(existingEntity); } else { throw new InvalidOperationException("Cannot delete the same entity more then once."); } } else { throw new InvalidOperationException("Could not find the entity in the internal cache."); } } /* Replace the original caches to complete the 'transaction' */ this.additionCache = tempAdditionCache; this.updateCache = tempUpdateCache; this.deletionCache = tempDeletionCache; } finally { memoryStore.ExitReadLock(); if (this.temporaryStorageLock.IsWriteLockHeld) { this.temporaryStorageLock.ExitWriteLock(); } } }
/// <summary>Updates an entity in the storage.</summary> /// <param name="entity">The entity that must be updated.</param> /// <param name="dataSourceInfo">The parameter is not used.</param> /// <returns>The updated entity.</returns> protected override TEntity UpdateEntityCore(TEntity entity, DataSourceInfo dataSourceInfo) { if (entity.RecordId == 0) { throw new InvalidOperationException("Cannot update an entity whose identifier is zero."); } EntityEqualityComparer <TEntity> entityComparer = new EntityEqualityComparer <TEntity>(); MemoryStore <TEntity> memoryStore = this.SelectMemoryStore(dataSourceInfo); try { /* First search the temporary storage */ this.temporaryStorageLock.EnterWriteLock(); memoryStore.EnterReadLock(); if (entity.RecordId < 0) { if (this.additionCache.Contains(entity, entityComparer)) { int indexOfEntity = this.additionCache.IndexOf(entity, entityComparer); TEntity repositoryEntity = this.additionCache[indexOfEntity]; /* Copy the values of the updated entity into the cached entity */ repositoryEntity.CopyFrom(entity); if (this.SelectCloneDataSourceItems(dataSourceInfo)) { return(((ICloneable)repositoryEntity).Clone() as TEntity); } else { entity.CopyFrom(repositoryEntity); return(entity); } } else { throw new InvalidOperationException("Could not find the entity in the addition-cache."); } } else { if (memoryStore.Storage.Contains(entity, entityComparer)) { if (this.deletionCache.Contains(entity, entityComparer)) { throw new InvalidOperationException("Cannot update the entity since it already marked for deletion."); } TEntity repositoryEntity; if (this.updateCache.Contains(entity, entityComparer)) { /* Retrieve the previous updated version of the entity from the cache */ int indexOfEntity = this.updateCache.IndexOf(entity, entityComparer); repositoryEntity = this.updateCache[indexOfEntity]; /* Copy the values of the updated entity into the cached entity */ repositoryEntity.CopyFrom(entity); } else { /* Retrieve the original version of the entity from the storage */ TEntity originalEntity = memoryStore.Storage[memoryStore.Storage.IndexOf(entity, entityComparer)]; /* Create a copy of the original entity to avoid any unwanted updates of the original entity */ repositoryEntity = originalEntity.CreateCopyOrClone(); /* Copy the values of the updated entity into the (copy of) original entity */ repositoryEntity.CopyFrom(entity); /* Store the updated entity in the update cache */ this.updateCache.Add(repositoryEntity); } /* Return an instance of TEntity that reflects the changes, but is disconnected from the instance that is stored in the cache * (to avoid unwanted updates from outside the repository) */ if (this.SelectCloneDataSourceItems(dataSourceInfo)) { return(((ICloneable)repositoryEntity).Clone() as TEntity); } else { entity.CopyFrom(repositoryEntity); return(entity); } } else { throw new InvalidOperationException("Could not find the entity in the internal cache."); } } } finally { memoryStore.ExitReadLock(); if (this.temporaryStorageLock.IsWriteLockHeld) { this.temporaryStorageLock.ExitWriteLock(); } } }
/// <summary>Deletes the entity from the storage.</summary> /// <param name="entity">The entity that must be removed.</param> /// <param name="dataSourceInfo">The parameter is not used.</param> protected override void DeleteEntityCore(TEntity entity, DataSourceInfo dataSourceInfo) { if (entity.RecordId == 0) { throw new InvalidOperationException("Cannot delete an entity whose identifier is zero."); } IEqualityComparer <TEntity> entityComparer = new EntityEqualityComparer <TEntity>(); MemoryStore <TEntity> memoryStore = this.SelectMemoryStore(dataSourceInfo); try { /* First search the temporary storage */ this.temporaryStorageLock.EnterWriteLock(); memoryStore.EnterReadLock(); if (entity.RecordId < 0) { /* If the ID is negative, it should be marked for addition */ if (this.additionCache.Contains(entity, entityComparer)) { this.additionCache.Remove(entity, entityComparer); } else { throw new InvalidOperationException("Could not find the entity in the memory."); } } else { /* If the entity was marked for update, remove that mark */ if (this.updateCache.Contains(entity, entityComparer)) { this.updateCache.Remove(entity, entityComparer); } /* If the entity exists in the original data source and has not yet been marked for deletion, mark it now */ if (memoryStore.Storage.Contains(entity, entityComparer)) { if (!this.deletionCache.Contains(entity, entityComparer)) { this.deletionCache.Add(entity); } else { throw new InvalidOperationException("Cannot delete the same entity more then once."); } } else { throw new InvalidOperationException("Could not find the entity in the memory."); } } } finally { memoryStore.ExitReadLock(); if (this.temporaryStorageLock.IsWriteLockHeld) { this.temporaryStorageLock.ExitWriteLock(); } } }