/src/Upreader.Infrastructure.ProtoBuf/DataContextModelCollection.cs
C# | 424 lines | 308 code | 60 blank | 56 comment | 42 complexity | 603653af9263eb1d8d55a48bb8ed0537 MD5 | raw file
Possible License(s): BSD-3-Clause
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using Upreader.Application.Model;
- using System.Collections.Concurrent;
- using System.Linq.Expressions;
- using ProtoBuf.Meta;
- using System.Threading;
-
- namespace Upreader.Infrastructure.ProtoBuf
- {
- /// <summary>
- /// Default implementation for working with the dataContext
- /// </summary>
- public class DataContextModelCollection<TModel> : IModelCollection<TModel>
- where TModel : ModelBase
- {
- #region Static fields
-
- private static readonly ConcurrentDictionary<Expression, object> _expressionCache = new ConcurrentDictionary<Expression, object>();
-
- #endregion // Static fields
-
- #region Fields
-
- private readonly DataContext _dataContext;
- private readonly TypeModel _typeModel;
- private readonly ISet<TModel> _modelSet;
- private readonly Func<int> _idFactory;
-
- private Dictionary<TModel, ModelState> _changedEntries;
-
- #endregion // Fields
-
- #region Constructor
-
- public DataContextModelCollection(DataContext dataContext,
- TypeModel typeModel,
- Func<ISet<TModel>> modelSetFactory,
- Func<int> idFactory)
- {
- if (dataContext == null)
- throw new ArgumentNullException("dataContext");
- if (typeModel == null)
- throw new ArgumentNullException("typeModel");
- if (modelSetFactory == null)
- throw new ArgumentNullException("modelSetFactory");
- if (idFactory == null)
- throw new ArgumentNullException("idFactory");
-
- _dataContext = dataContext;
- _typeModel = typeModel;
- _modelSet = modelSetFactory();
- _idFactory = idFactory;
-
- _changedEntries = new Dictionary<TModel, ModelState>();
- }
-
- #endregion // Constructor
-
- #region Methods
-
- /// <summary>
- /// Finds all models that satisfy the given predicate in unsorted order
- /// A new colllection will be returned containing copies of all models
- /// </summary>
- public IEnumerable<TModel> Find(Expression<Func<TModel, bool>> predicate)
- {
- // if no predicate, use default predicate
- if (predicate == null)
- predicate = x => true;
-
- // compile the predicate or get it from the cache
- Func<TModel, bool> compiledPredicate = GetFuncFromExpression(predicate);
-
- IEnumerable<TModel> localQuery = _changedEntries.Where(x => x.Value == ModelState.Added || x.Value == ModelState.Updated)
- .Select(x => x.Key);
-
- IEnumerable<TModel> excludeQuery = _changedEntries.Where(x => x.Value == ModelState.Removed).Select(x => x.Key);
-
- EnterReadLock();
- try
- {
- IEnumerable<TModel> remoteQuery = ModelSet.Where(compiledPredicate)
- .Except(excludeQuery);
-
- return localQuery.Union(remoteQuery)
- .Select(x => DeepClone(x))
- .ToArray();
- }
- finally
- {
- ExitReadLock();
- }
- }
-
- /// <summary>
- /// Adds the given item
- /// </summary>
- public void Add(TModel item)
- {
- if (item == null)
- throw new ArgumentNullException("item");
- if (item.Id != 0)
- throw new ArgumentException("Id of the item is already set. Only Models with Id as 0 can be added");
-
- // take a write lock since we have to assign a unique id to the item
- EnterWriteLock();
- try
- {
- item.Id = _idFactory();
- }
- finally
- {
- ExitWriteLock();
- }
-
- _changedEntries[DeepClone(item)] = ModelState.Added;
- }
-
- public void Update(TModel item)
- {
- if (item == null)
- throw new ArgumentNullException("item");
-
- // first see if the item exists in the local cache
- ModelState existingState;
- if (_changedEntries.TryGetValue(item, out existingState))
- {
- switch (existingState)
- {
- case ModelState.Added:
- break;
- case ModelState.Updated:
- // replace the added entity with the updated one but still mark it as added
- _changedEntries.Remove(item);
- _changedEntries[item] = ModelState.Added;
- return;
- case ModelState.Removed:
- throw new InvalidOperationException("Cannot update an item which does not exist");
- }
- }
-
- // secondly, make sure it is also removed in the persisted Set
- EnterReadLock();
- try
- {
- if (ModelSet.Contains(item))
- {
- _changedEntries[DeepClone(item)] = ModelState.Updated;
- }
- else if (_changedEntries.ContainsKey(item))
- {
- ModelState currentState = _changedEntries[item];
- if (currentState == ModelState.Removed)
- {
- throw new InvalidOperationException("Cannot update an item which does not exist");
- }
- else
- {
- // simply readd the item
- _changedEntries.Remove(item);
- _changedEntries[DeepClone(item)] = ModelState.Updated;
- }
- }
- else
- {
- throw new InvalidOperationException("Cannot update an item which does not exist");
- }
- }
- finally
- {
- ExitReadLock();
- }
- }
-
- /// <summary>
- /// Marks all models as removed
- /// </summary>
- public void Clear()
- {
- // any pending changes can be ignored
- _changedEntries.Clear();
-
- EnterReadLock();
- try
- {
- foreach (TModel model in ModelSet)
- {
- _changedEntries[DeepClone(model)] = ModelState.Removed;
- }
- }
- finally
- {
- ExitReadLock();
- }
- }
-
- /// <summary>
- /// Determines wether the givem item exists
- /// </summary>
- public bool Contains(TModel item)
- {
- if (item == null)
- throw new ArgumentNullException("item");
-
- // first see if the item exists in the local cache
- ModelState existingState;
- if (_changedEntries.TryGetValue(item, out existingState))
- {
- if (existingState == ModelState.Added)
- return true;
- }
-
- // secondly, search in the underlying data context
- EnterReadLock();
- try
- {
- return ModelSet.Contains(item);
- }
- finally
- {
- ExitReadLock();
- }
- }
-
- /// <summary>
- /// Copies the entire internal collection to an outstanding array.
- /// Note that in the current implementation a second copy.
- /// </summary>
- public void CopyTo(TModel[] array, int arrayIndex)
- {
- TModel[] copiedSet = (TModel[])Find(null);
-
- copiedSet.CopyTo(array, arrayIndex);
- }
-
- /// <summary>
- /// Marks the item given as removed from the underlying set.
- /// </summary>
- public bool Remove(TModel item)
- {
- if (item == null)
- throw new ArgumentNullException("item");
-
- // first see if the item exists in the local cache
- ModelState existingState;
- if (_changedEntries.TryGetValue(item, out existingState))
- {
- if (existingState == ModelState.Added)
- {
- _changedEntries.Remove(item);
- return true;
- }
- }
-
- // secondly, make sure it is also removed in the persisted Set
- EnterReadLock();
- try
- {
- if (ModelSet.Contains(item))
- {
- _changedEntries[DeepClone(item)] = ModelState.Removed;
- return true;
- }
- else
- {
- return false;
- }
- }
- finally
- {
- ExitReadLock();
- }
- }
-
- /// <summary>
- /// Get a enumerator for all models. Note that this method takes a snapshot of the current modelSet
- /// </summary>
- public IEnumerator<TModel> GetEnumerator()
- {
- return Find(null).GetEnumerator();
- }
-
- System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
- {
- return (IEnumerator<TModel>)GetEnumerator();
- }
-
- /// <summary>
- /// Persists all changes to the underlying data context
- /// </summary>
- public IEnumerable<ModelEntry> SaveChanges()
- {
- EnterWriteLock();
- try
- {
- foreach (KeyValuePair<TModel, ModelState> entry in _changedEntries)
- {
- switch (entry.Value)
- {
- case ModelState.Added:
- _modelSet.Add(entry.Key);
- break;
- case ModelState.Updated:
- // readding this entry will remove the existing one and add the new one
- _modelSet.Remove(entry.Key);
- _modelSet.Add(entry.Key);
- break;
- case ModelState.Removed:
- _modelSet.Remove(entry.Key);
- break;
- }
- }
-
- // create a new set of changed entries and use the old one for reporting back
- IEnumerable<ModelEntry> result = _changedEntries.Select(x => new ModelEntry(x.Key.Id, x.Value));
-
- _changedEntries = new Dictionary<TModel, ModelState>();
-
- return result;
- }
- finally
- {
- ExitWriteLock();
- }
- }
-
- #endregion // Methods
-
- #region Properties
-
- /// <summary>
- /// Get the total number of models in the current set
- /// </summary>
- public int Count
- {
- get
- {
- // count locally added entries
- int addedCount = _changedEntries.Count(x => x.Value == ModelState.Added);
- int removedCount = _changedEntries.Count(x => x.Value == ModelState.Removed);
-
- EnterReadLock();
-
- try
- {
- int persistedCount = ModelSet.Count;
-
- return persistedCount + addedCount - removedCount;
- }
- finally
- {
- ExitReadLock();
- }
- }
- }
-
- /// <summary>
- /// Always false
- /// </summary>
- public bool IsReadOnly
- {
- get { return false; }
- }
-
- #endregion // Properties
-
- #region Helpers
-
- protected Func<T, bool> GetFuncFromExpression<T>(Expression<Func<T, bool>> expression)
- {
- object cachedResult;
-
- if (_expressionCache.TryGetValue(expression, out cachedResult))
- {
- return (Func<T, bool>)cachedResult;
- }
- else
- {
- Func<T, bool> compiledResult = expression.Compile();
- _expressionCache.TryAdd(expression, compiledResult);
-
- return compiledResult;
- }
- }
-
- protected void EnterReadLock()
- {
- _dataContext.SynchronizationContext.EnterReadLock();
- }
-
- protected void ExitReadLock()
- {
- _dataContext.SynchronizationContext.ExitReadLock();
- }
-
- protected void EnterWriteLock()
- {
- _dataContext.SynchronizationContext.EnterWriteLock();
- }
-
- protected void ExitWriteLock()
- {
- _dataContext.SynchronizationContext.ExitWriteLock();
- }
-
- /// <summary>
- /// Get the underlying set from the DataContext relative to this collection
- /// </summary>
- protected ISet<TModel> ModelSet { get { return _modelSet; } }
-
- /// <summary>
- /// Materializes the given source object to a new copied instance
- /// </summary>
- protected TModel DeepClone(TModel source)
- {
- return (TModel)_typeModel.DeepClone(source);
- }
-
- #endregion // Helpers
- }
- }