PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/mcs/class/System.Data.Linq/src/DbLinq/Data/Linq/Implementation/MemberModificationHandler.cs

https://bitbucket.org/foobar22/mono
C# | 490 lines | 266 code | 40 blank | 184 comment | 54 complexity | 408265ab6162e088612b6359bf1c65d2 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0, Unlicense, Apache-2.0, LGPL-2.0
  1. #region MIT license
  2. //
  3. // MIT license
  4. //
  5. // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne
  6. //
  7. // Permission is hereby granted, free of charge, to any person obtaining a copy
  8. // of this software and associated documentation files (the "Software"), to deal
  9. // in the Software without restriction, including without limitation the rights
  10. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. // copies of the Software, and to permit persons to whom the Software is
  12. // furnished to do so, subject to the following conditions:
  13. //
  14. // The above copyright notice and this permission notice shall be included in
  15. // all copies or substantial portions of the Software.
  16. //
  17. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. // THE SOFTWARE.
  24. //
  25. #endregion
  26. using System;
  27. using System.Collections.Generic;
  28. using System.ComponentModel;
  29. using System.Data.Linq.Mapping;
  30. using System.Reflection;
  31. using DbLinq.Util;
  32. namespace DbLinq.Data.Linq.Implementation
  33. {
  34. /// <summary>
  35. /// ModificationHandler class handles entities in two ways:
  36. /// 1. if entity implements IModifed, uses the interface and its IsModifed flag property
  37. /// 2. otherwise, the handler keeps a dictionary of raw data per entity
  38. /// </summary>
  39. internal class MemberModificationHandler : IMemberModificationHandler
  40. {
  41. private readonly IDictionary<object, IDictionary<string, object>> rawDataEntities = new Dictionary<object, IDictionary<string, object>>(new ReferenceEqualityComparer<object>());
  42. private readonly IDictionary<object, IDictionary<string, MemberInfo>> modifiedProperties = new Dictionary<object, IDictionary<string, MemberInfo>>(new ReferenceEqualityComparer<object>());
  43. private static readonly IDictionary<string, MemberInfo> propertyChangingSentinal = new Dictionary<string, MemberInfo>();
  44. /// <summary>
  45. /// Gets the column members.
  46. /// </summary>
  47. /// <param name="entityType">Type of the entity.</param>
  48. /// <param name="metaModel">The meta model.</param>
  49. /// <returns></returns>
  50. protected virtual IEnumerable<MemberInfo> GetColumnMembers(Type entityType, MetaModel metaModel)
  51. {
  52. foreach (var dataMember in metaModel.GetTable(entityType).RowType.PersistentDataMembers)
  53. {
  54. yield return dataMember.Member;
  55. }
  56. }
  57. /// <summary>
  58. /// Determines whether the specified type is primitive type.
  59. /// </summary>
  60. /// <param name="type">The type.</param>
  61. /// <returns>
  62. /// <c>true</c> if the specified type is primitive type; otherwise, <c>false</c>.
  63. /// </returns>
  64. protected static bool IsPrimitiveType(Type type)
  65. {
  66. if (type.IsValueType)
  67. return true;
  68. if (type == typeof(string))
  69. return true;
  70. return false;
  71. }
  72. /// <summary>
  73. /// Adds simple (value) properties of an object to a given dictionary
  74. /// and recurses if a property contains complex data
  75. /// </summary>
  76. /// <param name="entity"></param>
  77. /// <param name="rawData"></param>
  78. /// <param name="prefix"></param>
  79. /// <param name="metaModel"></param>
  80. protected void AddRawData(object entity, IDictionary<string, object> rawData, string prefix, MetaModel metaModel)
  81. {
  82. if (entity == null)
  83. return;
  84. foreach (var memberInfo in GetColumnMembers(entity.GetType(), metaModel))
  85. {
  86. var propertyValue = memberInfo.GetMemberValue(entity);
  87. // if it is a value, it can be stored directly
  88. var memberType = memberInfo.GetMemberType();
  89. if (IsPrimitiveType(memberType))
  90. {
  91. rawData[prefix + memberInfo.Name] = propertyValue;
  92. }
  93. else if (memberType.IsArray)
  94. {
  95. if (propertyValue != null)
  96. {
  97. var arrayValue = (Array) propertyValue;
  98. for (int arrayIndex = 0; arrayIndex < arrayValue.Length; arrayIndex++)
  99. {
  100. rawData[string.Format("{0}[{1}]", memberInfo.Name, arrayIndex)] =
  101. arrayValue.GetValue(arrayIndex);
  102. }
  103. }
  104. }
  105. else // otherwise, we recurse, and prefix the current property name to sub properties to avoid conflicts
  106. {
  107. AddRawData(propertyValue, rawData, memberInfo.Name + ".", metaModel);
  108. }
  109. }
  110. }
  111. /// <summary>
  112. /// Creates a "flat view" from a composite object
  113. /// </summary>
  114. /// <param name="entity"></param>
  115. /// <param name="metaModel"></param>
  116. /// <returns>a pair of {property name, property value}</returns>
  117. protected IDictionary<string, object> GetEntityRawData(object entity, MetaModel metaModel)
  118. {
  119. var rawData = new Dictionary<string, object>();
  120. AddRawData(entity, rawData, string.Empty, metaModel);
  121. return rawData;
  122. }
  123. /// <summary>
  124. /// Tells if the object notifies a change
  125. /// </summary>
  126. /// <param name="entity"></param>
  127. /// <returns></returns>
  128. private static bool IsNotifying(object entity)
  129. {
  130. return entity is INotifyPropertyChanged
  131. || entity is INotifyPropertyChanging;
  132. }
  133. /// <summary>
  134. /// Start to watch an entity. From here, changes will make IsModified() return true
  135. /// </summary>
  136. /// <param name="entity"></param>
  137. /// <param name="metaModel"></param>
  138. public void Register(object entity, MetaModel metaModel)
  139. {
  140. Register(entity, entity, metaModel);
  141. }
  142. /// <summary>
  143. /// Start to watch an entity. From here, changes will make IsModified() return true if the entity has changed
  144. /// If the entity is already registered, there's no error, but the entity is reset to its original state
  145. /// </summary>
  146. /// <param name="entity"></param>
  147. /// <param name="entityOriginalState"></param>
  148. /// <param name="metaModel"></param>
  149. public void Register(object entity, object entityOriginalState, MetaModel metaModel)
  150. {
  151. // notifying, we need to wait for changes
  152. if (IsNotifying(entity))
  153. {
  154. RegisterNotification(entity, entityOriginalState, metaModel);
  155. }
  156. // raw data, we keep a snapshot of the current state
  157. else
  158. {
  159. if (!rawDataEntities.ContainsKey(entity) && entityOriginalState != null)
  160. rawDataEntities[entity] = GetEntityRawData(entityOriginalState, metaModel);
  161. }
  162. }
  163. private void RegisterNotification(object entity, object entityOriginalState, MetaModel metaModel)
  164. {
  165. if (modifiedProperties.ContainsKey(entity))
  166. return;
  167. modifiedProperties[entity] = null;
  168. var entityChanged = entity as INotifyPropertyChanged;
  169. if (entityChanged != null)
  170. {
  171. entityChanged.PropertyChanged += OnPropertyChangedEvent;
  172. }
  173. var entityChanging = entity as INotifyPropertyChanging;
  174. if (entityChanging != null)
  175. {
  176. entityChanging.PropertyChanging += OnPropertyChangingEvent;
  177. }
  178. // then check all properties, and note them as changed if they already did
  179. if (!ReferenceEquals(entity, entityOriginalState)) // only if we specified another original entity
  180. {
  181. foreach (var dataMember in metaModel.GetTable(entity.GetType()).RowType.PersistentDataMembers)
  182. {
  183. var memberInfo = dataMember.Member;
  184. if (entityOriginalState == null ||
  185. IsPropertyModified(memberInfo.GetMemberValue(entity),
  186. memberInfo.GetMemberValue(entityOriginalState)))
  187. {
  188. SetPropertyChanged(entity, memberInfo.Name);
  189. }
  190. }
  191. }
  192. }
  193. /// <summary>
  194. /// Occurs on INotifyPropertyChanged.PropertyChanged
  195. /// </summary>
  196. /// <param name="sender"></param>
  197. /// <param name="e"></param>
  198. private void OnPropertyChangedEvent(object sender, PropertyChangedEventArgs e)
  199. {
  200. SetPropertyChanged(sender, e.PropertyName);
  201. }
  202. private void OnPropertyChangingEvent(object entity, PropertyChangingEventArgs e)
  203. {
  204. if (modifiedProperties[entity] == null)
  205. modifiedProperties[entity] = propertyChangingSentinal;
  206. }
  207. /// <summary>
  208. /// Unregisters an entity.
  209. /// This is useful when it is switched from update to delete list
  210. /// </summary>
  211. /// <param name="entity"></param>
  212. public void Unregister(object entity)
  213. {
  214. if (IsNotifying(entity))
  215. UnregisterNotification(entity);
  216. else
  217. {
  218. if (rawDataEntities.ContainsKey(entity))
  219. rawDataEntities.Remove(entity);
  220. }
  221. }
  222. /// <summary>
  223. /// Unregisters an entity.
  224. /// This is useful when the DataContext has been disposed
  225. /// </summary>
  226. /// <param name="entity"></param>
  227. public void UnregisterAll()
  228. {
  229. //Duplicate the list to not modify modifiedEntities
  230. var modifiedEntities = new List<object>(modifiedProperties.Keys);
  231. foreach (var entity in modifiedEntities)
  232. {
  233. if (IsNotifying(entity))
  234. UnregisterNotification(entity);
  235. }
  236. }
  237. private void UnregisterNotification(object entity)
  238. {
  239. if (!modifiedProperties.ContainsKey(entity))
  240. return;
  241. modifiedProperties.Remove(entity);
  242. INotifyPropertyChanged npc = entity as INotifyPropertyChanged;
  243. if (npc != null)
  244. {
  245. npc.PropertyChanged -= OnPropertyChangedEvent;
  246. }
  247. var changing = entity as INotifyPropertyChanging;
  248. if (changing != null)
  249. {
  250. changing.PropertyChanging -= OnPropertyChangingEvent;
  251. }
  252. }
  253. /// <summary>
  254. /// This method is called when a notifying object sends an event because of a property change
  255. /// We may keep track of the precise change in the future
  256. /// </summary>
  257. /// <param name="entity"></param>
  258. /// <param name="propertyName"></param>
  259. private void SetPropertyChanged(object entity, string propertyName)
  260. {
  261. PropertyInfo pi = GetProperty(entity, propertyName);
  262. if (pi == null)
  263. throw new ArgumentException("Incorrect property changed");
  264. if (modifiedProperties[entity] == null ||
  265. ReferenceEquals(propertyChangingSentinal, modifiedProperties[entity]))
  266. {
  267. modifiedProperties[entity] = new Dictionary<string, MemberInfo>();
  268. }
  269. modifiedProperties[entity][propertyName] = pi;
  270. }
  271. /// <summary>
  272. /// Returns if the entity was modified since it has been Register()ed for the first time
  273. /// </summary>
  274. /// <param name="entity"></param>
  275. /// <param name="metaModel"></param>
  276. /// <returns></returns>
  277. public bool IsModified(object entity, MetaModel metaModel)
  278. {
  279. // 1. event notifying case (INotify*)
  280. if (IsNotifying(entity))
  281. return IsNotifyingModified(entity);
  282. // 2. raw data
  283. return IsRawModified(entity, metaModel);
  284. }
  285. /// <summary>
  286. /// Determines whether the specified notifiying entity is modified.
  287. /// </summary>
  288. /// <param name="entity">The entity.</param>
  289. /// <returns>
  290. /// <c>true</c> if the specified notifiying entity is modified; otherwise, <c>false</c>.
  291. /// </returns>
  292. private bool IsNotifyingModified(object entity)
  293. {
  294. if (!modifiedProperties.ContainsKey(entity) || modifiedProperties[entity] == null)
  295. return false;
  296. return ReferenceEquals(propertyChangingSentinal, modifiedProperties[entity]) ||
  297. modifiedProperties[entity].Count > 0;
  298. }
  299. /// <summary>
  300. /// Determines whether the specified property has changed, by comparing its current and previous value.
  301. /// </summary>
  302. /// <param name="p1">The p1.</param>
  303. /// <param name="p2">The p2.</param>
  304. /// <returns>
  305. /// <c>true</c> if the specified property has changed; otherwise, <c>false</c>.
  306. /// </returns>
  307. private static bool IsPropertyModified(object p1, object p2)
  308. {
  309. return !Equals(p1, p2);
  310. }
  311. /// <summary>
  312. /// Determines whether the specified raw entity has changed.
  313. /// </summary>
  314. /// <param name="entity">The entity.</param>
  315. /// <param name="metaModel">The meta model.</param>
  316. /// <returns>
  317. /// <c>true</c> if the specified raw entity has changed; otherwise, <c>false</c>.
  318. /// </returns>
  319. private bool IsRawModified(object entity, MetaModel metaModel)
  320. {
  321. // if not present, maybe it was inserted (or set to dirty)
  322. // TODO: this will be useless when we will support the differential properties
  323. if (!rawDataEntities.ContainsKey(entity))
  324. return true;
  325. IDictionary<string, object> originalData = rawDataEntities[entity];
  326. IDictionary<string, object> currentData = GetEntityRawData(entity, metaModel);
  327. foreach (string key in originalData.Keys)
  328. {
  329. object originalValue = originalData[key];
  330. object currentValue = currentData[key];
  331. if (IsPropertyModified(originalValue, currentValue))
  332. return true;
  333. }
  334. return false;
  335. }
  336. /// <summary>
  337. /// Returns a list of all modified properties since last Register/ClearModified
  338. /// </summary>
  339. /// <param name="entity"></param>
  340. /// <param name="metaModel"></param>
  341. /// <returns></returns>
  342. public IList<MemberInfo> GetModifiedProperties(object entity, MetaModel metaModel)
  343. {
  344. if (IsNotifying(entity))
  345. return GetNotifyingModifiedProperties(entity, metaModel);
  346. return GetRawModifiedProperties(entity, metaModel);
  347. }
  348. /// <summary>
  349. /// Gets all column properties.
  350. /// </summary>
  351. /// <param name="entity">The entity.</param>
  352. /// <param name="metaModel">The meta model.</param>
  353. /// <returns></returns>
  354. protected IList<MemberInfo> GetAllColumnProperties(object entity, MetaModel metaModel)
  355. {
  356. if (entity == null)
  357. throw new ArgumentNullException("entity");
  358. var properties = new List<MemberInfo>(GetColumnMembers(entity.GetType(), metaModel));
  359. return properties;
  360. }
  361. /// <summary>
  362. /// Gets the self declaring entity modified properties.
  363. /// </summary>
  364. /// <param name="entity">The entity.</param>
  365. /// <param name="metaModel">The meta model.</param>
  366. /// <returns></returns>
  367. protected IList<MemberInfo> GetSelfDeclaringModifiedProperties(object entity, MetaModel metaModel)
  368. {
  369. return GetAllColumnProperties(entity, metaModel);
  370. }
  371. /// <summary>
  372. /// Gets the notifying entity modified properties.
  373. /// </summary>
  374. /// <param name="entity">The entity.</param>
  375. /// <param name="metaModel">The meta model.</param>
  376. /// <returns></returns>
  377. protected IList<MemberInfo> GetNotifyingModifiedProperties(object entity, MetaModel metaModel)
  378. {
  379. IDictionary<string, MemberInfo> properties;
  380. // if we don't have it, it is fully dirty
  381. if (!modifiedProperties.TryGetValue(entity, out properties) ||
  382. ReferenceEquals(propertyChangingSentinal, modifiedProperties[entity]))
  383. return GetAllColumnProperties(entity, metaModel);
  384. return new List<MemberInfo>(properties.Values);
  385. }
  386. /// <summary>
  387. /// Gets modified properties for entity, by using raw compare method.
  388. /// </summary>
  389. /// <param name="entity">The entity.</param>
  390. /// <param name="metaModel">The meta model.</param>
  391. /// <returns></returns>
  392. protected IList<MemberInfo> GetRawModifiedProperties(object entity, MetaModel metaModel)
  393. {
  394. var properties = new List<MemberInfo>();
  395. IDictionary<string, object> originalData;
  396. // if we don't have this entity we consider all its properties as having been modified
  397. if (!rawDataEntities.TryGetValue(entity, out originalData))
  398. return GetAllColumnProperties(entity, metaModel);
  399. var currentData = GetEntityRawData(entity, metaModel);
  400. // otherwise, we iterate and find what's changed
  401. foreach (string key in currentData.Keys)
  402. {
  403. var currentValue = currentData[key];
  404. var originalValue = originalData[key];
  405. if (IsPropertyModified(originalValue, currentValue))
  406. properties.Add(GetProperty(entity, key));
  407. }
  408. return properties;
  409. }
  410. /// <summary>
  411. /// Marks the entity as not dirty.
  412. /// </summary>
  413. /// <param name="entity"></param>
  414. /// <param name="metaModel"></param>
  415. public void ClearModified(object entity, MetaModel metaModel)
  416. {
  417. if (IsNotifying(entity))
  418. ClearNotifyingModified(entity);
  419. else
  420. ClearRawModified(entity, metaModel);
  421. }
  422. /// <summary>
  423. /// Sets the notifying entity as unmodified.
  424. /// </summary>
  425. /// <param name="entity">The entity.</param>
  426. private void ClearNotifyingModified(object entity)
  427. {
  428. modifiedProperties[entity] = null;
  429. }
  430. /// <summary>
  431. /// Sets the raw entity as unmodified.
  432. /// </summary>
  433. /// <param name="entity">The entity.</param>
  434. /// <param name="metaModel">The meta model.</param>
  435. private void ClearRawModified(object entity, MetaModel metaModel)
  436. {
  437. rawDataEntities[entity] = GetEntityRawData(entity, metaModel);
  438. }
  439. /// <summary>
  440. /// Gets the property, given a property name.
  441. /// </summary>
  442. /// <param name="entity">The entity.</param>
  443. /// <param name="propertyName">Name of the property.</param>
  444. /// <returns></returns>
  445. private static PropertyInfo GetProperty(object entity, string propertyName)
  446. {
  447. return entity.GetType().GetProperty(propertyName);
  448. }
  449. }
  450. }