/// <summary>Adds the entity to the storage.</summary> /// <param name="entity">The entity that must be added.</param> /// <param name="dataSourceInfo">The parameter is not used.</param> /// <returns>The entity with the updated recordID.</returns> protected override TEntity AddEntityCore(TEntity entity, DataSourceInfo dataSourceInfo) { EntityEqualityComparer <TEntity> entityComparer = new EntityEqualityComparer <TEntity>(); try { this.temporaryStorageLock.EnterWriteLock(); if (entity.RecordId > 0) { /* The entity already has an ID which suggests that it came from the original datasource */ if (this.deletionCache.Contains(entity, entityComparer)) { /* The entity has been marked for deletion, undelete it... */ this.deletionCache.Remove(entity, entityComparer); /* ...and mark it as updated in case any of the fields have been altered. */ TEntity repositoryEntity = entity.CreateCopyOrClone(); this.updateCache.Add(repositoryEntity); if (this.SelectCloneDataSourceItems(dataSourceInfo)) { return(((ICloneable)repositoryEntity).Clone() as TEntity); } else { entity.CopyFrom(repositoryEntity); return(entity); } } } /* The entity is either new or came from another data source */ /* Determine the new temporary ID for the entity */ int newRecordId = -1; if (this.additionCache.Count > 0) { newRecordId = this.additionCache.Min(t => t.RecordId) - 1; } TEntity copyOfEntity = entity.CreateCopyOrClone(); copyOfEntity.RecordId = newRecordId; /* Add it to the addition cache */ this.additionCache.Add(copyOfEntity); if (this.SelectCloneDataSourceItems(dataSourceInfo)) { return(((ICloneable)copyOfEntity).Clone() as TEntity); } else { entity.CopyFrom(copyOfEntity); return(entity); } } finally { if (this.temporaryStorageLock.IsWriteLockHeld) { this.temporaryStorageLock.ExitWriteLock(); } } }
/// <summary>Concatenates the global caches and local cache into one complete and up-to-date cache.</summary> /// <param name="dataSourceInfo">Any information regarding the data store that is used as data source.</param> /// <returns>The concatenated cache-values.</returns> private IEnumerable <TEntity> ConcatStorage(DataSourceInfo dataSourceInfo) { EntityEqualityComparer <TEntity> entityComparer = new EntityEqualityComparer <TEntity>(); IEnumerable <TEntity> totalCache = this.SelectMemoryStore(dataSourceInfo).Storage; if (this.SelectCloneDataSourceItems(dataSourceInfo)) { totalCache = totalCache.Select(t => ((ICloneable)t).Clone() as TEntity); } totalCache = totalCache.Concat(this.additionCache); /*...then remove the entities that were updated from the basic cache and replace them with the updated versions... */ totalCache = totalCache.Except(this.updateCache, entityComparer).Concat(this.updateCache); /* ...finally, remove the deleted entities */ totalCache = totalCache.Except(this.deletionCache, entityComparer); return(totalCache.OrderBy(t => t.RecordId)); }
/// <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>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>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>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(); } } }
/// <summary>Adds a collection of new entities to the repository. They are added to the addition cache until it is saved using the /// <see cref="Repository{T}.SaveChanges()"/> method. A temporary (negative) RecordID is assigned to the entities. This will be reset when the entity is /// saved.</summary> /// <param name="entities">The entities that must be added.</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 were added to the repository.</returns> protected override IEnumerable <TEntity> AddEntitiesCore(IEnumerable <TEntity> entities, DataSourceInfo dataSourceInfo) { EntityEqualityComparer <TEntity> entityComparer = new EntityEqualityComparer <TEntity>(); /* Place the entities in a list to keep track of the entities that have been handled */ List <TEntity> unhandledEntities = entities.ToList(); this.temporaryStorageLock.EnterWriteLock(); /* Make a copy of the caches. That way, if any thing goes wrong all the changes can be made undone */ List <TEntity> tempDeletionCache = this.deletionCache.ToList(); List <TEntity> tempUpdateCache = this.updateCache.ToList(); List <TEntity> tempAdditionCache = this.additionCache.ToList(); List <TEntity> updatedEntities = new List <TEntity>(); List <TEntity> addedEntities = new List <TEntity>(); Dictionary <TEntity, TEntity> handledEntities = new Dictionary <TEntity, TEntity>(); try { if (entities.Any(e => e.RecordId > 0)) { IEnumerable <TEntity> existingEntities = entities.Where(e => e.RecordId > 0); ReferenceEqualityComparer <TEntity> referenceComparer = new ReferenceEqualityComparer <TEntity>(); /* At least some of the entities already have an ID which suggests that they came from the original datasource */ foreach (TEntity existingEntity in existingEntities) { if (tempDeletionCache.Contains(existingEntity, entityComparer)) { /* The entity has been marked for deletion, undelete it... */ tempDeletionCache.Remove(existingEntity, entityComparer); /* ...and mark it as updated in case any of the fields have been altered. */ TEntity repositoryEntity = existingEntity.CreateCopyOrClone(); tempUpdateCache.Add(repositoryEntity); updatedEntities.Add(repositoryEntity); handledEntities.Add(existingEntity, repositoryEntity); bool removeResult = unhandledEntities.Remove(existingEntity, referenceComparer); Debug.Assert(removeResult, "Somehow the result could not be removed from the collection of handled entities."); } } } if (unhandledEntities.Count > 0) { /* At least some of the entities are either new or came from another data source */ /* Determine the new temporary ID for the entities */ int newRecordId = -1; if (tempAdditionCache.Count > 0) { newRecordId = tempAdditionCache.Min(t => t.RecordId) - 1; } foreach (TEntity unhandledEntity in unhandledEntities) { TEntity copyOfEntity = unhandledEntity.CreateCopyOrClone(); copyOfEntity.RecordId = newRecordId; --newRecordId; /* Add it to the addition cache */ tempAdditionCache.Add(copyOfEntity); addedEntities.Add(copyOfEntity); handledEntities.Add(unhandledEntity, copyOfEntity); } } /* Replace the original caches to complete the 'transaction' */ this.deletionCache = tempDeletionCache; this.updateCache = tempUpdateCache; this.additionCache = tempAdditionCache; if (this.SelectCloneDataSourceItems(dataSourceInfo)) { return(addedEntities.Concat(updatedEntities).Select(entity => ((ICloneable)entity).Clone() as TEntity).ToList()); } else { handledEntities.ForEach(kvp => kvp.Key.CopyFrom(kvp.Value)); return(entities); } } finally { this.temporaryStorageLock.ExitWriteLock(); } }