PageRenderTime 131ms CodeModel.GetById 33ms RepoModel.GetById 10ms app.codeStats 0ms

/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
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using Upreader.Application.Model;
  6. using System.Collections.Concurrent;
  7. using System.Linq.Expressions;
  8. using ProtoBuf.Meta;
  9. using System.Threading;
  10. namespace Upreader.Infrastructure.ProtoBuf
  11. {
  12. /// <summary>
  13. /// Default implementation for working with the dataContext
  14. /// </summary>
  15. public class DataContextModelCollection<TModel> : IModelCollection<TModel>
  16. where TModel : ModelBase
  17. {
  18. #region Static fields
  19. private static readonly ConcurrentDictionary<Expression, object> _expressionCache = new ConcurrentDictionary<Expression, object>();
  20. #endregion // Static fields
  21. #region Fields
  22. private readonly DataContext _dataContext;
  23. private readonly TypeModel _typeModel;
  24. private readonly ISet<TModel> _modelSet;
  25. private readonly Func<int> _idFactory;
  26. private Dictionary<TModel, ModelState> _changedEntries;
  27. #endregion // Fields
  28. #region Constructor
  29. public DataContextModelCollection(DataContext dataContext,
  30. TypeModel typeModel,
  31. Func<ISet<TModel>> modelSetFactory,
  32. Func<int> idFactory)
  33. {
  34. if (dataContext == null)
  35. throw new ArgumentNullException("dataContext");
  36. if (typeModel == null)
  37. throw new ArgumentNullException("typeModel");
  38. if (modelSetFactory == null)
  39. throw new ArgumentNullException("modelSetFactory");
  40. if (idFactory == null)
  41. throw new ArgumentNullException("idFactory");
  42. _dataContext = dataContext;
  43. _typeModel = typeModel;
  44. _modelSet = modelSetFactory();
  45. _idFactory = idFactory;
  46. _changedEntries = new Dictionary<TModel, ModelState>();
  47. }
  48. #endregion // Constructor
  49. #region Methods
  50. /// <summary>
  51. /// Finds all models that satisfy the given predicate in unsorted order
  52. /// A new colllection will be returned containing copies of all models
  53. /// </summary>
  54. public IEnumerable<TModel> Find(Expression<Func<TModel, bool>> predicate)
  55. {
  56. // if no predicate, use default predicate
  57. if (predicate == null)
  58. predicate = x => true;
  59. // compile the predicate or get it from the cache
  60. Func<TModel, bool> compiledPredicate = GetFuncFromExpression(predicate);
  61. IEnumerable<TModel> localQuery = _changedEntries.Where(x => x.Value == ModelState.Added || x.Value == ModelState.Updated)
  62. .Select(x => x.Key);
  63. IEnumerable<TModel> excludeQuery = _changedEntries.Where(x => x.Value == ModelState.Removed).Select(x => x.Key);
  64. EnterReadLock();
  65. try
  66. {
  67. IEnumerable<TModel> remoteQuery = ModelSet.Where(compiledPredicate)
  68. .Except(excludeQuery);
  69. return localQuery.Union(remoteQuery)
  70. .Select(x => DeepClone(x))
  71. .ToArray();
  72. }
  73. finally
  74. {
  75. ExitReadLock();
  76. }
  77. }
  78. /// <summary>
  79. /// Adds the given item
  80. /// </summary>
  81. public void Add(TModel item)
  82. {
  83. if (item == null)
  84. throw new ArgumentNullException("item");
  85. if (item.Id != 0)
  86. throw new ArgumentException("Id of the item is already set. Only Models with Id as 0 can be added");
  87. // take a write lock since we have to assign a unique id to the item
  88. EnterWriteLock();
  89. try
  90. {
  91. item.Id = _idFactory();
  92. }
  93. finally
  94. {
  95. ExitWriteLock();
  96. }
  97. _changedEntries[DeepClone(item)] = ModelState.Added;
  98. }
  99. public void Update(TModel item)
  100. {
  101. if (item == null)
  102. throw new ArgumentNullException("item");
  103. // first see if the item exists in the local cache
  104. ModelState existingState;
  105. if (_changedEntries.TryGetValue(item, out existingState))
  106. {
  107. switch (existingState)
  108. {
  109. case ModelState.Added:
  110. break;
  111. case ModelState.Updated:
  112. // replace the added entity with the updated one but still mark it as added
  113. _changedEntries.Remove(item);
  114. _changedEntries[item] = ModelState.Added;
  115. return;
  116. case ModelState.Removed:
  117. throw new InvalidOperationException("Cannot update an item which does not exist");
  118. }
  119. }
  120. // secondly, make sure it is also removed in the persisted Set
  121. EnterReadLock();
  122. try
  123. {
  124. if (ModelSet.Contains(item))
  125. {
  126. _changedEntries[DeepClone(item)] = ModelState.Updated;
  127. }
  128. else if (_changedEntries.ContainsKey(item))
  129. {
  130. ModelState currentState = _changedEntries[item];
  131. if (currentState == ModelState.Removed)
  132. {
  133. throw new InvalidOperationException("Cannot update an item which does not exist");
  134. }
  135. else
  136. {
  137. // simply readd the item
  138. _changedEntries.Remove(item);
  139. _changedEntries[DeepClone(item)] = ModelState.Updated;
  140. }
  141. }
  142. else
  143. {
  144. throw new InvalidOperationException("Cannot update an item which does not exist");
  145. }
  146. }
  147. finally
  148. {
  149. ExitReadLock();
  150. }
  151. }
  152. /// <summary>
  153. /// Marks all models as removed
  154. /// </summary>
  155. public void Clear()
  156. {
  157. // any pending changes can be ignored
  158. _changedEntries.Clear();
  159. EnterReadLock();
  160. try
  161. {
  162. foreach (TModel model in ModelSet)
  163. {
  164. _changedEntries[DeepClone(model)] = ModelState.Removed;
  165. }
  166. }
  167. finally
  168. {
  169. ExitReadLock();
  170. }
  171. }
  172. /// <summary>
  173. /// Determines wether the givem item exists
  174. /// </summary>
  175. public bool Contains(TModel item)
  176. {
  177. if (item == null)
  178. throw new ArgumentNullException("item");
  179. // first see if the item exists in the local cache
  180. ModelState existingState;
  181. if (_changedEntries.TryGetValue(item, out existingState))
  182. {
  183. if (existingState == ModelState.Added)
  184. return true;
  185. }
  186. // secondly, search in the underlying data context
  187. EnterReadLock();
  188. try
  189. {
  190. return ModelSet.Contains(item);
  191. }
  192. finally
  193. {
  194. ExitReadLock();
  195. }
  196. }
  197. /// <summary>
  198. /// Copies the entire internal collection to an outstanding array.
  199. /// Note that in the current implementation a second copy.
  200. /// </summary>
  201. public void CopyTo(TModel[] array, int arrayIndex)
  202. {
  203. TModel[] copiedSet = (TModel[])Find(null);
  204. copiedSet.CopyTo(array, arrayIndex);
  205. }
  206. /// <summary>
  207. /// Marks the item given as removed from the underlying set.
  208. /// </summary>
  209. public bool Remove(TModel item)
  210. {
  211. if (item == null)
  212. throw new ArgumentNullException("item");
  213. // first see if the item exists in the local cache
  214. ModelState existingState;
  215. if (_changedEntries.TryGetValue(item, out existingState))
  216. {
  217. if (existingState == ModelState.Added)
  218. {
  219. _changedEntries.Remove(item);
  220. return true;
  221. }
  222. }
  223. // secondly, make sure it is also removed in the persisted Set
  224. EnterReadLock();
  225. try
  226. {
  227. if (ModelSet.Contains(item))
  228. {
  229. _changedEntries[DeepClone(item)] = ModelState.Removed;
  230. return true;
  231. }
  232. else
  233. {
  234. return false;
  235. }
  236. }
  237. finally
  238. {
  239. ExitReadLock();
  240. }
  241. }
  242. /// <summary>
  243. /// Get a enumerator for all models. Note that this method takes a snapshot of the current modelSet
  244. /// </summary>
  245. public IEnumerator<TModel> GetEnumerator()
  246. {
  247. return Find(null).GetEnumerator();
  248. }
  249. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  250. {
  251. return (IEnumerator<TModel>)GetEnumerator();
  252. }
  253. /// <summary>
  254. /// Persists all changes to the underlying data context
  255. /// </summary>
  256. public IEnumerable<ModelEntry> SaveChanges()
  257. {
  258. EnterWriteLock();
  259. try
  260. {
  261. foreach (KeyValuePair<TModel, ModelState> entry in _changedEntries)
  262. {
  263. switch (entry.Value)
  264. {
  265. case ModelState.Added:
  266. _modelSet.Add(entry.Key);
  267. break;
  268. case ModelState.Updated:
  269. // readding this entry will remove the existing one and add the new one
  270. _modelSet.Remove(entry.Key);
  271. _modelSet.Add(entry.Key);
  272. break;
  273. case ModelState.Removed:
  274. _modelSet.Remove(entry.Key);
  275. break;
  276. }
  277. }
  278. // create a new set of changed entries and use the old one for reporting back
  279. IEnumerable<ModelEntry> result = _changedEntries.Select(x => new ModelEntry(x.Key.Id, x.Value));
  280. _changedEntries = new Dictionary<TModel, ModelState>();
  281. return result;
  282. }
  283. finally
  284. {
  285. ExitWriteLock();
  286. }
  287. }
  288. #endregion // Methods
  289. #region Properties
  290. /// <summary>
  291. /// Get the total number of models in the current set
  292. /// </summary>
  293. public int Count
  294. {
  295. get
  296. {
  297. // count locally added entries
  298. int addedCount = _changedEntries.Count(x => x.Value == ModelState.Added);
  299. int removedCount = _changedEntries.Count(x => x.Value == ModelState.Removed);
  300. EnterReadLock();
  301. try
  302. {
  303. int persistedCount = ModelSet.Count;
  304. return persistedCount + addedCount - removedCount;
  305. }
  306. finally
  307. {
  308. ExitReadLock();
  309. }
  310. }
  311. }
  312. /// <summary>
  313. /// Always false
  314. /// </summary>
  315. public bool IsReadOnly
  316. {
  317. get { return false; }
  318. }
  319. #endregion // Properties
  320. #region Helpers
  321. protected Func<T, bool> GetFuncFromExpression<T>(Expression<Func<T, bool>> expression)
  322. {
  323. object cachedResult;
  324. if (_expressionCache.TryGetValue(expression, out cachedResult))
  325. {
  326. return (Func<T, bool>)cachedResult;
  327. }
  328. else
  329. {
  330. Func<T, bool> compiledResult = expression.Compile();
  331. _expressionCache.TryAdd(expression, compiledResult);
  332. return compiledResult;
  333. }
  334. }
  335. protected void EnterReadLock()
  336. {
  337. _dataContext.SynchronizationContext.EnterReadLock();
  338. }
  339. protected void ExitReadLock()
  340. {
  341. _dataContext.SynchronizationContext.ExitReadLock();
  342. }
  343. protected void EnterWriteLock()
  344. {
  345. _dataContext.SynchronizationContext.EnterWriteLock();
  346. }
  347. protected void ExitWriteLock()
  348. {
  349. _dataContext.SynchronizationContext.ExitWriteLock();
  350. }
  351. /// <summary>
  352. /// Get the underlying set from the DataContext relative to this collection
  353. /// </summary>
  354. protected ISet<TModel> ModelSet { get { return _modelSet; } }
  355. /// <summary>
  356. /// Materializes the given source object to a new copied instance
  357. /// </summary>
  358. protected TModel DeepClone(TModel source)
  359. {
  360. return (TModel)_typeModel.DeepClone(source);
  361. }
  362. #endregion // Helpers
  363. }
  364. }