ChangeConflicts.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Collections.ObjectModel;
  5. using System.Text;
  6. using System.Reflection;
  7. using System.Linq;
  8. using System.Diagnostics;
  9. namespace System.Data.Linq {
  10. using System.Data.Linq.Mapping;
  11. using System.Data.Linq.Provider;
  12. using System.Diagnostics.CodeAnalysis;
  13. public sealed class ChangeConflictCollection : ICollection<ObjectChangeConflict>, ICollection, IEnumerable<ObjectChangeConflict>, IEnumerable {
  14. private List<ObjectChangeConflict> conflicts;
  15. internal ChangeConflictCollection() {
  16. this.conflicts = new List<ObjectChangeConflict>();
  17. }
  18. /// <summary>
  19. /// The number of conflicts in the collection
  20. /// </summary>
  21. public int Count {
  22. get { return this.conflicts.Count; }
  23. }
  24. public ObjectChangeConflict this[int index] {
  25. get { return this.conflicts[index]; }
  26. }
  27. bool ICollection<ObjectChangeConflict>.IsReadOnly {
  28. get { return true; }
  29. }
  30. void ICollection<ObjectChangeConflict>.Add(ObjectChangeConflict item) {
  31. throw Error.CannotAddChangeConflicts();
  32. }
  33. /// <summary>
  34. /// Removes the specified conflict from the collection.
  35. /// </summary>
  36. /// <param name="item">The conflict to remove</param>
  37. /// <returns></returns>
  38. public bool Remove(ObjectChangeConflict item) {
  39. return this.conflicts.Remove(item);
  40. }
  41. /// <summary>
  42. /// Removes all conflicts from the collection
  43. /// </summary>
  44. public void Clear() {
  45. this.conflicts.Clear();
  46. }
  47. /// <summary>
  48. /// Returns true if the specified conflict is a member of the collection.
  49. /// </summary>
  50. /// <param name="item"></param>
  51. /// <returns></returns>
  52. public bool Contains(ObjectChangeConflict item) {
  53. return this.conflicts.Contains(item);
  54. }
  55. public void CopyTo(ObjectChangeConflict[] array, int arrayIndex) {
  56. this.conflicts.CopyTo(array, arrayIndex);
  57. }
  58. /// <summary>
  59. /// Returns the enumerator for the collection.
  60. /// </summary>
  61. /// <returns></returns>
  62. public IEnumerator<ObjectChangeConflict> GetEnumerator() {
  63. return this.conflicts.GetEnumerator();
  64. }
  65. IEnumerator IEnumerable.GetEnumerator() {
  66. return this.conflicts.GetEnumerator();
  67. }
  68. bool ICollection.IsSynchronized {
  69. get { return false; }
  70. }
  71. object ICollection.SyncRoot {
  72. get { return null; }
  73. }
  74. void ICollection.CopyTo(Array array, int index) {
  75. ((ICollection)this.conflicts).CopyTo(array, index);
  76. }
  77. /// <summary>
  78. /// Resolves all conflicts in the collection using the specified strategy.
  79. /// </summary>
  80. /// <param name="mode">The strategy to use to resolve the conflicts.</param>
  81. public void ResolveAll(RefreshMode mode) {
  82. this.ResolveAll(mode, true);
  83. }
  84. /// <summary>
  85. /// Resolves all conflicts in the collection using the specified strategy.
  86. /// </summary>
  87. /// <param name="mode">The strategy to use to resolve the conflicts.</param>
  88. /// <param name="autoResolveDeletes">If true conflicts resulting from the modified
  89. /// object no longer existing in the database will be automatically resolved.</param>
  90. public void ResolveAll(RefreshMode mode, bool autoResolveDeletes) {
  91. foreach (ObjectChangeConflict c in this.conflicts) {
  92. if (!c.IsResolved) {
  93. c.Resolve(mode, autoResolveDeletes);
  94. }
  95. }
  96. }
  97. internal void Fill(List<ObjectChangeConflict> conflictList) {
  98. this.conflicts = conflictList;
  99. }
  100. }
  101. internal sealed class ChangeConflictSession {
  102. private DataContext context;
  103. private DataContext refreshContext;
  104. internal ChangeConflictSession(DataContext context) {
  105. this.context = context;
  106. }
  107. internal DataContext Context {
  108. get { return this.context; }
  109. }
  110. internal DataContext RefreshContext {
  111. get {
  112. if (this.refreshContext == null) {
  113. this.refreshContext = this.context.CreateRefreshContext();
  114. }
  115. return this.refreshContext;
  116. }
  117. }
  118. }
  119. /// <summary>
  120. /// Represents an update with one or more optimistic concurrency conflicts.
  121. /// </summary>
  122. public sealed class ObjectChangeConflict {
  123. private ChangeConflictSession session;
  124. private TrackedObject trackedObject;
  125. private bool isResolved;
  126. private ReadOnlyCollection<MemberChangeConflict> memberConflicts;
  127. private object database;
  128. private object original;
  129. private bool? isDeleted;
  130. /// <summary>
  131. /// Constructor.
  132. /// </summary>
  133. /// <param name="session">The session in which the conflicts occurred.</param>
  134. /// <param name="trackedObject">The tracked item in conflict.</param>
  135. internal ObjectChangeConflict(ChangeConflictSession session, TrackedObject trackedObject) {
  136. this.session = session;
  137. this.trackedObject = trackedObject;
  138. this.original = trackedObject.CreateDataCopy(trackedObject.Original);
  139. }
  140. /// <summary>
  141. /// Constructor.
  142. /// </summary>
  143. /// <param name="session">The session in which the conflicts occurred.</param>
  144. /// <param name="trackedObject">The tracked item in conflict.</param>
  145. /// <param name="isDeleted">True if the item in conflict no longer exists in the database.</param>
  146. internal ObjectChangeConflict(ChangeConflictSession session, TrackedObject trackedObject, bool isDeleted)
  147. : this(session, trackedObject) {
  148. this.isDeleted = isDeleted;
  149. }
  150. internal ChangeConflictSession Session {
  151. get { return this.session; }
  152. }
  153. internal TrackedObject TrackedObject {
  154. get { return this.trackedObject; }
  155. }
  156. /// <summary>
  157. /// The object in conflict.
  158. /// </summary>
  159. public object Object {
  160. get { return this.trackedObject.Current; }
  161. }
  162. /// <summary>
  163. /// An instance containing the baseline original values used to perform the concurrency check.
  164. /// </summary>
  165. internal object Original {
  166. get { return this.original; }
  167. }
  168. /// <summary>
  169. /// True if the conflicts for this object have already been resovled.
  170. /// </summary>
  171. public bool IsResolved {
  172. get { return this.isResolved; }
  173. }
  174. /// <summary>
  175. /// True if the object in conflict has been deleted from the database.
  176. /// </summary>
  177. public bool IsDeleted {
  178. get {
  179. if (this.isDeleted.HasValue) {
  180. return this.isDeleted.Value;
  181. }
  182. return (this.Database == null);
  183. }
  184. }
  185. /// <summary>
  186. /// An instance containing the most recent values from the database
  187. /// </summary>
  188. internal object Database {
  189. get {
  190. if (this.database == null) {
  191. // use the 'refresh' context to retrieve the current database state
  192. DataContext ctxt = this.session.RefreshContext;
  193. object[] keyValues = CommonDataServices.GetKeyValues(this.trackedObject.Type, this.original);
  194. this.database = ctxt.Services.GetObjectByKey(this.trackedObject.Type, keyValues);
  195. }
  196. return this.database;
  197. }
  198. }
  199. /// <summary>
  200. /// Resolve member conflicts keeping current values and resetting the baseline 'Original' values
  201. /// to match the more recent 'Database' values.
  202. /// </summary>
  203. public void Resolve() {
  204. this.Resolve(RefreshMode.KeepCurrentValues, true);
  205. }
  206. /// <summary>
  207. /// Resolve member conflicts using the mode specified and resetting the baseline 'Original' values
  208. /// to match the more recent 'Database' values.
  209. /// </summary>
  210. /// <param name="refreshMode">The mode that determines how the current values are
  211. /// changed in order to resolve the conflict</param>
  212. public void Resolve(RefreshMode refreshMode) {
  213. this.Resolve(refreshMode, false);
  214. }
  215. /// <summary>
  216. /// Resolve member conflicts using the mode specified and resetting the baseline 'Original' values
  217. /// to match the more recent 'Database' values.
  218. /// </summary>
  219. /// <param name="refreshMode">The mode that determines how the current values are
  220. /// changed in order to resolve the conflict</param>
  221. /// <param name="autoResolveDeletes">If true conflicts resulting from the modified
  222. /// object no longer existing in the database will be automatically resolved.</param>
  223. public void Resolve(RefreshMode refreshMode, bool autoResolveDeletes) {
  224. if (autoResolveDeletes && this.IsDeleted) {
  225. this.ResolveDelete();
  226. }
  227. else {
  228. // We make these calls explicity rather than simply calling
  229. // DataContext.Refresh (which does virtually the same thing)
  230. // since we want to cache the database value read.
  231. if (this.Database == null) {
  232. throw Error.RefreshOfDeletedObject();
  233. }
  234. trackedObject.Refresh(refreshMode, this.Database);
  235. this.isResolved = true;
  236. }
  237. }
  238. /// <summary>
  239. /// Resolve a conflict where we have updated an entity that no longer exists
  240. /// in the database.
  241. /// </summary>
  242. private void ResolveDelete() {
  243. Debug.Assert(this.IsDeleted);
  244. // If the user is attempting to update an entity that no longer exists
  245. // in the database, we first need to [....] the delete into the local cache.
  246. if (!trackedObject.IsDeleted) {
  247. trackedObject.ConvertToDeleted();
  248. }
  249. // As the object have been deleted, it needs to leave the cache
  250. this.Session.Context.Services.RemoveCachedObjectLike(trackedObject.Type, trackedObject.Original);
  251. // Now that our cache is in [....], we accept the changes
  252. this.trackedObject.AcceptChanges();
  253. this.isResolved = true;
  254. }
  255. /// <summary>
  256. /// Returns a collection of all member conflicts that caused the update to fail.
  257. /// </summary>
  258. public ReadOnlyCollection<MemberChangeConflict> MemberConflicts {
  259. get {
  260. if (this.memberConflicts == null) {
  261. var list = new List<MemberChangeConflict>();
  262. if (this.Database != null) {
  263. // determine which members are in conflict
  264. foreach (MetaDataMember metaMember in trackedObject.Type.PersistentDataMembers) {
  265. if (!metaMember.IsAssociation && this.HasMemberConflict(metaMember)) {
  266. list.Add(new MemberChangeConflict(this, metaMember));
  267. }
  268. }
  269. }
  270. this.memberConflicts = list.AsReadOnly();
  271. }
  272. return this.memberConflicts;
  273. }
  274. }
  275. private bool HasMemberConflict(MetaDataMember member) {
  276. object oValue = member.StorageAccessor.GetBoxedValue(this.original);
  277. if (!member.DeclaringType.Type.IsAssignableFrom(this.database.GetType())) {
  278. return false;
  279. }
  280. object dValue = member.StorageAccessor.GetBoxedValue(this.database);
  281. return !this.AreEqual(member, oValue, dValue);
  282. }
  283. private bool AreEqual(MetaDataMember member, object v1, object v2) {
  284. if (v1 == null && v2 == null)
  285. return true;
  286. if (v1 == null || v2 == null)
  287. return false;
  288. if (member.Type == typeof(char[])) {
  289. return this.AreEqual((char[])v1, (char[])v2);
  290. }
  291. else if (member.Type == typeof(byte[])) {
  292. return this.AreEqual((byte[])v1, (byte[])v2);
  293. }
  294. else {
  295. return object.Equals(v1, v2);
  296. }
  297. }
  298. [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")]
  299. private bool AreEqual(char[] a1, char[] a2) {
  300. if (a1.Length != a2.Length)
  301. return false;
  302. for (int i = 0, n = a1.Length; i < n; i++) {
  303. if (a1[i] != a2[i])
  304. return false;
  305. }
  306. return true;
  307. }
  308. [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")]
  309. private bool AreEqual(byte[] a1, byte[] a2) {
  310. if (a1.Length != a2.Length)
  311. return false;
  312. for (int i = 0, n = a1.Length; i < n; i++) {
  313. if (a1[i] != a2[i])
  314. return false;
  315. }
  316. return true;
  317. }
  318. internal void OnMemberResolved() {
  319. if (!this.IsResolved) {
  320. int nResolved = this.memberConflicts.AsEnumerable().Count(m => m.IsResolved);
  321. if (nResolved == this.memberConflicts.Count) {
  322. this.Resolve(RefreshMode.KeepCurrentValues, false);
  323. }
  324. }
  325. }
  326. }
  327. /// <summary>
  328. /// Represents a single optimistic concurrency member conflict.
  329. /// </summary>
  330. public sealed class MemberChangeConflict {
  331. private ObjectChangeConflict conflict;
  332. private MetaDataMember metaMember;
  333. private object originalValue;
  334. private object databaseValue;
  335. private object currentValue;
  336. bool isResolved;
  337. internal MemberChangeConflict(ObjectChangeConflict conflict, MetaDataMember metaMember) {
  338. this.conflict = conflict;
  339. this.metaMember = metaMember;
  340. this.originalValue = metaMember.StorageAccessor.GetBoxedValue(conflict.Original);
  341. this.databaseValue = metaMember.StorageAccessor.GetBoxedValue(conflict.Database);
  342. this.currentValue = metaMember.StorageAccessor.GetBoxedValue(conflict.TrackedObject.Current);
  343. }
  344. /// <summary>
  345. /// The previous client value.
  346. /// </summary>
  347. public object OriginalValue {
  348. get { return this.originalValue; }
  349. }
  350. /// <summary>
  351. /// The current database value.
  352. /// </summary>
  353. public object DatabaseValue {
  354. get { return this.databaseValue; }
  355. }
  356. /// <summary>
  357. /// The current client value.
  358. /// </summary>
  359. public object CurrentValue {
  360. get { return this.currentValue; }
  361. }
  362. /// <summary>
  363. /// MemberInfo for the member in conflict.
  364. /// </summary>
  365. public MemberInfo Member {
  366. get { return this.metaMember.Member; }
  367. }
  368. /// <summary>
  369. /// Updates the current value to the specified value.
  370. /// </summary>
  371. public void Resolve(object value) {
  372. this.conflict.TrackedObject.RefreshMember(this.metaMember, RefreshMode.OverwriteCurrentValues, value);
  373. this.isResolved = true;
  374. this.conflict.OnMemberResolved();
  375. }
  376. /// <summary>
  377. /// Updates the current value using the specified strategy.
  378. /// </summary>
  379. public void Resolve(RefreshMode refreshMode) {
  380. this.conflict.TrackedObject.RefreshMember(this.metaMember, refreshMode, this.databaseValue);
  381. this.isResolved = true;
  382. this.conflict.OnMemberResolved();
  383. }
  384. /// <summary>
  385. /// True if the value was modified by the client.
  386. /// </summary>
  387. public bool IsModified {
  388. get { return this.conflict.TrackedObject.HasChangedValue(this.metaMember); }
  389. }
  390. /// <summary>
  391. /// True if the member conflict has been resolved.
  392. /// </summary>
  393. public bool IsResolved {
  394. get { return this.isResolved; }
  395. }
  396. }
  397. }