/// <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>Initializes a new instance of the <see cref="XmlFileRepository{TEntity}"/> class using the specified <see cref="DataSourceInfo"/>. /// </summary> /// <param name="dataSourceInfo">The data source information that must be used to access the source file.</param> public XmlFileRepository(DataSourceInfo dataSourceInfo) : base(dataSourceInfo) { XmlAttributeOverrides overrides = ConstructAttributeOverrides(); this.serializer = new XmlSerializer(typeof(List <TEntity>), overrides); }
/// <summary>Selects the <see cref="SaveGraph"/> from the specified data source information.</summary> /// <param name="dataSourceInfo">The data source information that is queried.</param> /// <returns>The <see cref="SaveGraph"/> that is stored in the data source information or <see langword="null"/> if the <see cref="SaveGraph"/> could not be found.</returns> public static bool SelectSaveGraph(DataSourceInfo dataSourceInfo) { if (IsSaveGraphSpecified(dataSourceInfo)) { return((bool)dataSourceInfo[SaveGraphKey]); } else { return(DatabaseSourceInfo.DefaultSaveGraph); } }
/// <summary>Selects the <see cref="DbContext"/> from the specified data source information.</summary> /// <param name="dataSourceInfo">The data source information that is queried.</param> /// <returns>The <see cref="DbContext"/> that is stored in the data source information or <see langword="null"/> if the <see cref="DbContext"/> could not be found.</returns> public static DbContext SelectDbContext(DataSourceInfo dataSourceInfo) { if (IsDbContextSpecified(dataSourceInfo)) { return(dataSourceInfo[DbContextKey] as DbContext); } else { return(null); } }
/// <summary>Selects the source file's encoding from the specified data source information.</summary> /// <param name="dataSourceInfo">The data source information that is queried.</param> /// <returns>The value that is stored in the data source information or <see langword="null"/> if the value could not be found.</returns> public static Encoding SelectSourceFileEncoding(DataSourceInfo dataSourceInfo) { if (IsSourceFileEncodingSpecified(dataSourceInfo)) { return(dataSourceInfo[SourceFileEncodingKey] as Encoding); } else { return(DefaultSourceFileEncoding); } }
/// <summary>Selects the change complete timeout from the specified data source information.</summary> /// <param name="dataSourceInfo">The data source information that is queried.</param> /// <returns>The value that is stored in the data source information or the default value if the flag could not be found.</returns> public static int SelectChangeCompleteTimeout(DataSourceInfo dataSourceInfo) { if (IsChangeCompleteTimeoutSpecified(dataSourceInfo)) { return((int)dataSourceInfo[ChangeCompleteTimeoutKey]); } else { return(DefaultChangeCompleteTimeout); } }
/// <summary>Selects the monitor-flag from the specified data source information.</summary> /// <param name="dataSourceInfo">The data source information that is queried.</param> /// <returns>The monitor-flag that is stored in the data source information or <see langword="true"/> if the flag could not be found.</returns> public static bool SelectMonitorSourceFile(DataSourceInfo dataSourceInfo) { if (IsMonitorSourceFileSpecified(dataSourceInfo)) { return((bool)dataSourceInfo[MonitorSourceFileKey]); } else { return(DefaultMonitorSourceFile); } }
/// <summary>Selects the 'clone data source items'-flag from the specified data source information.</summary> /// <param name="dataSourceInfo">The data source information that is queried.</param> /// <returns>The flag that is stored in the data source information or <see langword="false"/> if the flag could not be found.</returns> public static bool SelectCloneDataSourceItems(DataSourceInfo dataSourceInfo) { if (IsCloneDataSourceItemsSpecified(dataSourceInfo)) { return((bool)dataSourceInfo[CloneDataSourceItemsKey]); } else { return(DefaultCloneDataSourceItems); } }
/// <summary>Selects the MemoryStore from the specified data source information.</summary> /// <typeparam name="T">The type of object that is stored in the memory store.</typeparam> /// <param name="dataSourceInfo">The data source information that is queried.</param> /// <returns>The MemoryStore that is stored in the data source information or <see langword="null"/> if the MemoryStore could not be found. /// </returns> public static MemoryStore <T> SelectMemoryStore <T>(DataSourceInfo dataSourceInfo) where T : class { if (IsMemoryStoreSpecified(dataSourceInfo)) { return(dataSourceInfo[MemoryStoreKey] as MemoryStore <T>); } else { return(null); } }
/// <summary>Selects the remote address from the specified data source information.</summary> /// <param name="dataSourceInfo">The data source information that is queried.</param> /// <returns>The value that is stored in the data source information or <see langword="null"/> if the value could not be found.</returns> public static EndpointAddress SelectRemoteAddress(DataSourceInfo dataSourceInfo) { if (IsRemoteAddressSpecified(dataSourceInfo)) { return(dataSourceInfo[RemoteAddressKey] as EndpointAddress); } else { return(null); } }
/// <summary>Selects the binding from the specified data source information.</summary> /// <param name="dataSourceInfo">The data source information that is queried.</param> /// <returns>The value that is stored in the data source information or <see langword="null"/> if the value could not be found.</returns> public static Binding SelectBinding(DataSourceInfo dataSourceInfo) { if (IsBindingSpecified(dataSourceInfo)) { return(dataSourceInfo[BindingKey] as Binding); } else { return(null); } }
/// <summary>Selects the MemoryStore that must be used. If the specified DataSourceInfo contains a valid MemoryStore, it is used; otherwise the /// value of the property 'MemoryStore' is used.</summary> /// <param name="dataSourceInfo">Any information regarding the data store that is used as data source.</param> /// <returns>The MemoryStore that must be used.</returns> private MemoryStore <TEntity> SelectMemoryStore(DataSourceInfo dataSourceInfo) { if (MemorySourceInfo.IsMemoryStoreSpecified(dataSourceInfo)) { return(MemorySourceInfo.SelectMemoryStore <TEntity>(dataSourceInfo)); } else { return(this.MemoryStore); } }
/// <summary>Selects the endpoint configuration from the specified data source information.</summary> /// <param name="dataSourceInfo">The data source information that is queried.</param> /// <returns>The value that is stored in the data source information or <see langword="null"/> if the value could not be found.</returns> public static string SelectEndpointConfigurationName(DataSourceInfo dataSourceInfo) { if (IsEndpointConfigurationNameSpecified(dataSourceInfo)) { return(dataSourceInfo[EndpointConfigurationNameKey] as string); } else { return(null); } }
/// <summary>Selects the source FileInfo from the specified data source information.</summary> /// <param name="dataSourceInfo">The data source information that is queried.</param> /// <returns>The FileInfo that is stored in the data source information or <see langword="null"/> if the FileInfo could not be found.</returns> public static FileInfo SelectSourceFileInfo(DataSourceInfo dataSourceInfo) { if (IsSourceFileInfoSpecified(dataSourceInfo)) { return(dataSourceInfo[SourceFileInfoKey] as FileInfo); } else { return(null); } }
/// <summary>Initializes a new instance of the <see cref="MemoryRepository{TEntity}"/> class using the specified <see cref="DataSourceInfo"/>. /// </summary> /// <param name="dataSourceInfo">The data source information that must be used to access the data source.</param> /// <exception cref="InvalidOperationException"><paramref name="dataSourceInfo"/> does not specify a valid <see cref="Enkoni.Framework.Entities.MemoryStore{T}"/>. /// </exception> public MemoryRepository(DataSourceInfo dataSourceInfo) : base(dataSourceInfo) { /* Initializes the internal collections */ this.additionCache = new List <TEntity>(); this.updateCache = new List <TEntity>(); this.deletionCache = new List <TEntity>(); this.MemoryStore = MemorySourceInfo.SelectMemoryStore <TEntity>(dataSourceInfo); if (this.MemoryStore == null) { throw new InvalidOperationException("The memory store is mandatory."); } }
/// <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>Initializes a new instance of the <see cref="ServiceRepository{TEntity}"/> class using the specified <see cref="DataSourceInfo"/>. /// </summary> /// <param name="dataSourceInfo">The data source information that must be used to access the source file.</param> protected ServiceRepository(DataSourceInfo dataSourceInfo) : base(dataSourceInfo) { /* Determine if the supported properties have been specified */ if (ServiceSourceInfo.IsEndpointConfigurationNameSpecified(dataSourceInfo)) { this.EndpointConfigurationName = ServiceSourceInfo.SelectEndpointConfigurationName(dataSourceInfo); } if (ServiceSourceInfo.IsRemoteAddressSpecified(dataSourceInfo)) { this.RemoteAddress = ServiceSourceInfo.SelectRemoteAddress(dataSourceInfo); } if (ServiceSourceInfo.IsBindingSpecified(dataSourceInfo)) { this.Binding = ServiceSourceInfo.SelectBinding(dataSourceInfo); } }
/// <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>Determines if the remote address is specified in the source information.</summary> /// <param name="dataSourceInfo">The data source information that is queried.</param> /// <returns><see langword="true"/> if the value is defined; <see langword="false"/> otherwise.</returns> public static bool IsRemoteAddressSpecified([ValidatedNotNull] DataSourceInfo dataSourceInfo) { return(dataSourceInfo != null && dataSourceInfo.IsValueSpecified(RemoteAddressKey)); }
/// <summary>Determines if the endpoint configuration is specified in the source information.</summary> /// <param name="dataSourceInfo">The data source information that is queried.</param> /// <returns><see langword="true"/> if the value is defined; <see langword="false"/> otherwise.</returns> public static bool IsEndpointConfigurationNameSpecified([ValidatedNotNull] DataSourceInfo dataSourceInfo) { return(dataSourceInfo != null && dataSourceInfo.IsValueSpecified(EndpointConfigurationNameKey)); }
/// <summary>Finds the single entity that matches the expression or returns the default value if there were no matches.</summary> /// <param name="expression">The search-specification.</param> /// <param name="includePaths">The dot-separated lists of related objects to return in the query results.</param> /// <param name="dataSourceInfo">The parameter is not used.</param> /// <param name="defaultValue">The value that must be returned if there were no matches.</param> /// <returns>The single result or the default value.</returns> protected override TEntity FindSingleCore(Func <TEntity, bool> expression, string[] includePaths, DataSourceInfo dataSourceInfo, TEntity defaultValue) { MemoryStore <TEntity> memoryStore = this.SelectMemoryStore(dataSourceInfo); try { this.temporaryStorageLock.EnterReadLock(); memoryStore.EnterReadLock(); return(this.ConcatStorage(dataSourceInfo).SingleOrDefault(expression, defaultValue)); } finally { memoryStore.ExitReadLock(); if (this.temporaryStorageLock.IsReadLockHeld) { this.temporaryStorageLock.ExitReadLock(); } } }
/// <summary>Determines if the 'clone data source items'-flag is specified in the source information.</summary> /// <param name="dataSourceInfo">The data source information that is queried.</param> /// <returns><see langword="true"/> if the flag is defined; <see langword="false"/> otherwise.</returns> public static bool IsCloneDataSourceItemsSpecified([ValidatedNotNull] DataSourceInfo dataSourceInfo) { return(dataSourceInfo != null && dataSourceInfo.IsValueSpecified(CloneDataSourceItemsKey)); }
/// <summary>Determines if the binding is specified in the source information.</summary> /// <param name="dataSourceInfo">The data source information that is queried.</param> /// <returns><see langword="true"/> if the value is defined; <see langword="false"/> otherwise.</returns> public static bool IsBindingSpecified([ValidatedNotNull] DataSourceInfo dataSourceInfo) { return(dataSourceInfo != null && dataSourceInfo.IsValueSpecified(BindingKey)); }
/// <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(); } } }
/// <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(); } }
/// <summary>Resets the repository by undoing any unsaved changes.</summary> /// <param name="dataSourceInfo">Information about the data source that may not have been set at an earlier stage.</param> protected override void ResetCore(DataSourceInfo dataSourceInfo) { this.additionCache.Clear(); this.deletionCache.Clear(); this.updateCache.Clear(); }
/// <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>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>Finds the first entity that matches the expression or returns the default value if there were no matches.</summary> /// <param name="expression">The search-specification.</param> /// <param name="sortRules">The specification of the sort rules that must be applied. Use <see langword="null"/> to ignore the ordering.</param> /// <param name="includePaths">The dot-separated lists of related objects to return in the query results.</param> /// <param name="dataSourceInfo">The parameter is not used.</param> /// <param name="defaultValue">The value that must be returned if there were no matches.</param> /// <returns>The first result or the default value.</returns> protected override TEntity FindFirstCore(Func <TEntity, bool> expression, SortSpecifications <TEntity> sortRules, string[] includePaths, DataSourceInfo dataSourceInfo, TEntity defaultValue) { MemoryStore <TEntity> memoryStore = this.SelectMemoryStore(dataSourceInfo); try { this.temporaryStorageLock.EnterReadLock(); memoryStore.EnterReadLock(); return(this.ConcatStorage(dataSourceInfo).OrderBy(sortRules).FirstOrDefault(expression, defaultValue)); } finally { memoryStore.ExitReadLock(); if (this.temporaryStorageLock.IsReadLockHeld) { this.temporaryStorageLock.ExitReadLock(); } } }