ChangeProcessor.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using System.Text;
  6. using System.Diagnostics;
  7. namespace System.Data.Linq {
  8. using System.Data.Linq.Mapping;
  9. using System.Data.Linq.Provider;
  10. /// <summary>
  11. /// Describes the type of change the entity will undergo when submitted to the database.
  12. /// </summary>
  13. public enum ChangeAction {
  14. /// <summary>
  15. /// The entity will not be submitted.
  16. /// </summary>
  17. None = 0,
  18. /// <summary>
  19. /// The entity will be deleted.
  20. /// </summary>
  21. Delete,
  22. /// <summary>
  23. /// The entity will be inserted.
  24. /// </summary>
  25. Insert,
  26. /// <summary>
  27. /// The entity will be updated.
  28. /// </summary>
  29. Update
  30. }
  31. internal class ChangeProcessor {
  32. CommonDataServices services;
  33. DataContext context;
  34. ChangeTracker tracker;
  35. ChangeDirector changeDirector;
  36. EdgeMap currentParentEdges;
  37. EdgeMap originalChildEdges;
  38. ReferenceMap originalChildReferences;
  39. internal ChangeProcessor(CommonDataServices services, DataContext context) {
  40. this.services = services;
  41. this.context = context;
  42. this.tracker = services.ChangeTracker;
  43. this.changeDirector = services.ChangeDirector;
  44. this.currentParentEdges = new EdgeMap();
  45. this.originalChildEdges = new EdgeMap();
  46. this.originalChildReferences = new ReferenceMap();
  47. }
  48. internal void SubmitChanges(ConflictMode failureMode) {
  49. this.TrackUntrackedObjects();
  50. // Must apply inferred deletions only after any untracked objects
  51. // are tracked
  52. this.ApplyInferredDeletions();
  53. this.BuildEdgeMaps();
  54. var list = this.GetOrderedList();
  55. ValidateAll(list);
  56. int numUpdatesAttempted = 0;
  57. ChangeConflictSession conflictSession = new ChangeConflictSession(this.context);
  58. List<ObjectChangeConflict> conflicts = new List<ObjectChangeConflict>();
  59. List<TrackedObject> deletedItems = new List<TrackedObject>();
  60. List<TrackedObject> insertedItems = new List<TrackedObject>();
  61. List<TrackedObject> syncDependentItems = new List<TrackedObject>();
  62. foreach (TrackedObject item in list) {
  63. try {
  64. if (item.IsNew) {
  65. if (item.SynchDependentData()) {
  66. syncDependentItems.Add(item);
  67. }
  68. changeDirector.Insert(item);
  69. // store all inserted items for post processing
  70. insertedItems.Add(item);
  71. }
  72. else if (item.IsDeleted) {
  73. // Delete returns 1 if the delete was successfull, 0 if the row exists
  74. // but wasn't deleted due to an OC conflict, or -1 if the row was
  75. // deleted by another context (no OC conflict in this case)
  76. numUpdatesAttempted++;
  77. int ret = changeDirector.Delete(item);
  78. if (ret == 0) {
  79. conflicts.Add(new ObjectChangeConflict(conflictSession, item, false));
  80. }
  81. else {
  82. // store all deleted items for post processing
  83. deletedItems.Add(item);
  84. }
  85. }
  86. else if (item.IsPossiblyModified) {
  87. if (item.SynchDependentData()) {
  88. syncDependentItems.Add(item);
  89. }
  90. if (item.IsModified) {
  91. CheckForInvalidChanges(item);
  92. numUpdatesAttempted++;
  93. if (changeDirector.Update(item) <= 0) {
  94. conflicts.Add(new ObjectChangeConflict(conflictSession, item));
  95. }
  96. }
  97. }
  98. }
  99. catch (ChangeConflictException) {
  100. conflicts.Add(new ObjectChangeConflict(conflictSession, item));
  101. }
  102. if (conflicts.Count > 0 && failureMode == ConflictMode.FailOnFirstConflict) {
  103. break;
  104. }
  105. }
  106. // if we have accumulated any failed updates, throw the exception now
  107. if (conflicts.Count > 0) {
  108. // First we need to rollback any value that have already been auto-[....]'d, since the values are no longer valid on the server
  109. changeDirector.RollbackAutoSync();
  110. // Also rollback any dependent items that were [....]'d, since their parent values may have been rolled back
  111. foreach (TrackedObject syncDependentItem in syncDependentItems) {
  112. Debug.Assert(syncDependentItem.IsNew || syncDependentItem.IsPossiblyModified, "SynchDependent data should only be rolled back for new and modified objects.");
  113. syncDependentItem.SynchDependentData();
  114. }
  115. this.context.ChangeConflicts.Fill(conflicts);
  116. throw CreateChangeConflictException(numUpdatesAttempted, conflicts.Count);
  117. }
  118. else {
  119. // No conflicts occurred, so we don't need to save the rollback values anymore
  120. changeDirector.ClearAutoSyncRollback();
  121. }
  122. // Only after all updates have been sucessfully processed do we want to make
  123. // post processing modifications to the objects and/or cache state.
  124. PostProcessUpdates(insertedItems, deletedItems);
  125. }
  126. private void PostProcessUpdates(List<TrackedObject> insertedItems, List<TrackedObject> deletedItems) {
  127. // perform post delete processing
  128. foreach (TrackedObject deletedItem in deletedItems) {
  129. // remove deleted item from identity cache
  130. this.services.RemoveCachedObjectLike(deletedItem.Type, deletedItem.Original);
  131. ClearForeignKeyReferences(deletedItem);
  132. }
  133. // perform post insert processing
  134. foreach (TrackedObject insertedItem in insertedItems) {
  135. object lookup = this.services.InsertLookupCachedObject(insertedItem.Type, insertedItem.Current);
  136. if (lookup != insertedItem.Current) {
  137. throw new DuplicateKeyException(insertedItem.Current, Strings.DatabaseGeneratedAlreadyExistingKey);
  138. }
  139. insertedItem.InitializeDeferredLoaders();
  140. }
  141. }
  142. /// <summary>
  143. /// Clears out the foreign key values and parent object references for deleted objects on the child side of a relationship.
  144. /// For bi-directional relationships, also performs the following fixup:
  145. /// - for 1:N we remove the deleted entity from the opposite EntitySet or collection
  146. /// - for 1:1 we null out the back reference
  147. /// </summary>
  148. private void ClearForeignKeyReferences(TrackedObject to) {
  149. Debug.Assert(to.IsDeleted, "Foreign key reference cleanup should only happen on Deleted objects.");
  150. foreach (MetaAssociation assoc in to.Type.Associations) {
  151. if (assoc.IsForeignKey) {
  152. // If there is a member on the other side referring back to us (i.e. this is a bi-directional relationship),
  153. // we want to do a cache lookup to find the other side, then will remove ourselves from that collection.
  154. // This cache lookup is only possible if the other key is the primary key, since that is the only way items can be found in the cache.
  155. if (assoc.OtherMember != null && assoc.OtherKeyIsPrimaryKey) {
  156. Debug.Assert(assoc.OtherMember.IsAssociation, "OtherMember of the association is expected to also be an association.");
  157. // Search the cache for the target of the association, since
  158. // it might not be loaded on the object being deleted, and we
  159. // don't want to force a load.
  160. object[] keyValues = CommonDataServices.GetForeignKeyValues(assoc, to.Current);
  161. object cached = this.services.IdentityManager.Find(assoc.OtherType, keyValues);
  162. if (cached != null) {
  163. if (assoc.OtherMember.Association.IsMany) {
  164. // Note that going through the IList interface handles
  165. // EntitySet as well as POCO collections that implement IList
  166. // and are not FixedSize.
  167. System.Collections.IList collection = assoc.OtherMember.MemberAccessor.GetBoxedValue(cached) as System.Collections.IList;
  168. if (collection != null && !collection.IsFixedSize) {
  169. collection.Remove(to.Current);
  170. // Explicitly clear the foreign key values and parent object reference
  171. ClearForeignKeysHelper(assoc, to.Current);
  172. }
  173. }
  174. else {
  175. // Null out the other association. Since this is a 1:1 association,
  176. // we're not concerned here with causing a deferred load, since the
  177. // target is already cached (since we're deleting it).
  178. assoc.OtherMember.MemberAccessor.SetBoxedValue(ref cached, null);
  179. // Explicitly clear the foreign key values and parent object reference
  180. ClearForeignKeysHelper(assoc, to.Current);
  181. }
  182. }
  183. // else the item was not found in the cache, so there is no fixup that has to be done
  184. // We are explicitly not calling ClearForeignKeysHelper because it breaks existing shipped behavior and we want to maintain backward compatibility
  185. }
  186. else {
  187. // This is a unidirectional relationship or we have no way to look up the other side in the cache, so just clear our own side
  188. ClearForeignKeysHelper(assoc, to.Current);
  189. }
  190. }
  191. // else this is not the 1-side (foreign key) of the relationship, so there is nothing for us to do
  192. }
  193. }
  194. // Ensure the the member and foreign keys are nulled so that after trackedInstance is deleted,
  195. // the object does not appear to be associated with the other side anymore. This prevents the deleted object
  196. // from referencing objects still in the cache, but also will prevent the related object from being implicitly loaded
  197. private static void ClearForeignKeysHelper(MetaAssociation assoc, object trackedInstance) {
  198. Debug.Assert(assoc.IsForeignKey, "Foreign key clearing should only happen on foreign key side of the association.");
  199. Debug.Assert(assoc.ThisMember.IsAssociation, "Expected ThisMember of an association to always be an association.");
  200. // If this member is one of our deferred loaders, and it does not already have a value, explicitly set the deferred source to
  201. // null so that when we set the association member itself to null later, it doesn't trigger an implicit load.
  202. // This is only necessary if the value has not already been assigned or set, because otherwise we won't implicitly load anyway when the member is accessed.
  203. MetaDataMember thisMember = assoc.ThisMember;
  204. if (thisMember.IsDeferred &&
  205. !(thisMember.StorageAccessor.HasAssignedValue(trackedInstance) || thisMember.StorageAccessor.HasLoadedValue(trackedInstance)))
  206. {
  207. // If this is a deferred member, set the value directly in the deferred accessor instead of going
  208. // through the normal member accessor, so that we don't trigger an implicit load.
  209. thisMember.DeferredSourceAccessor.SetBoxedValue(ref trackedInstance, null);
  210. }
  211. // Notify the object that the relationship should be considered deleted.
  212. // This allows the object to do its own fixup even when we can't do it automatically.
  213. thisMember.MemberAccessor.SetBoxedValue(ref trackedInstance, null);
  214. // Also set the foreign key values to null if possible
  215. for (int i = 0, n = assoc.ThisKey.Count; i < n; i++) {
  216. MetaDataMember thisKey = assoc.ThisKey[i];
  217. if (thisKey.CanBeNull) {
  218. thisKey.StorageAccessor.SetBoxedValue(ref trackedInstance, null);
  219. }
  220. }
  221. }
  222. private static void ValidateAll(IEnumerable<TrackedObject> list) {
  223. foreach (var item in list) {
  224. if (item.IsNew) {
  225. item.SynchDependentData();
  226. if (item.Type.HasAnyValidateMethod) {
  227. SendOnValidate(item.Type, item, ChangeAction.Insert);
  228. }
  229. } else if (item.IsDeleted) {
  230. if (item.Type.HasAnyValidateMethod) {
  231. SendOnValidate(item.Type, item, ChangeAction.Delete);
  232. }
  233. } else if (item.IsPossiblyModified) {
  234. item.SynchDependentData();
  235. if (item.IsModified && item.Type.HasAnyValidateMethod) {
  236. SendOnValidate(item.Type, item, ChangeAction.Update);
  237. }
  238. }
  239. }
  240. }
  241. private static void SendOnValidate(MetaType type, TrackedObject item, ChangeAction changeAction) {
  242. if (type != null) {
  243. SendOnValidate(type.InheritanceBase, item, changeAction);
  244. if (type.OnValidateMethod != null) {
  245. try {
  246. type.OnValidateMethod.Invoke(item.Current, new object[] { changeAction });
  247. } catch (TargetInvocationException tie) {
  248. if (tie.InnerException != null) {
  249. throw tie.InnerException;
  250. }
  251. throw;
  252. }
  253. }
  254. }
  255. }
  256. internal string GetChangeText() {
  257. this.ObserveUntrackedObjects();
  258. // Must apply inferred deletions only after any untracked objects
  259. // are tracked
  260. this.ApplyInferredDeletions();
  261. this.BuildEdgeMaps();
  262. // append change text only
  263. StringBuilder changeText = new StringBuilder();
  264. foreach (TrackedObject item in this.GetOrderedList()) {
  265. if (item.IsNew) {
  266. item.SynchDependentData();
  267. changeDirector.AppendInsertText(item, changeText);
  268. }
  269. else if (item.IsDeleted) {
  270. changeDirector.AppendDeleteText(item, changeText);
  271. }
  272. else if (item.IsPossiblyModified) {
  273. item.SynchDependentData();
  274. if (item.IsModified) {
  275. changeDirector.AppendUpdateText(item, changeText);
  276. }
  277. }
  278. }
  279. return changeText.ToString();
  280. }
  281. internal ChangeSet GetChangeSet() {
  282. List<object> newEntities = new List<object>();
  283. List<object> deletedEntities = new List<object>();
  284. List<object> changedEntities = new List<object>();
  285. this.ObserveUntrackedObjects();
  286. // Must apply inferred deletions only after any untracked objects
  287. // are tracked
  288. this.ApplyInferredDeletions();
  289. foreach (TrackedObject item in this.tracker.GetInterestingObjects()) {
  290. if (item.IsNew) {
  291. item.SynchDependentData();
  292. newEntities.Add(item.Current);
  293. }
  294. else if (item.IsDeleted) {
  295. deletedEntities.Add(item.Current);
  296. }
  297. else if (item.IsPossiblyModified) {
  298. item.SynchDependentData();
  299. if (item.IsModified) {
  300. changedEntities.Add(item.Current);
  301. }
  302. }
  303. }
  304. return new ChangeSet(newEntities.AsReadOnly(), deletedEntities.AsReadOnly(), changedEntities.AsReadOnly());
  305. }
  306. // verify that primary key and db-generated values have not changed
  307. private static void CheckForInvalidChanges(TrackedObject tracked) {
  308. foreach (MetaDataMember mem in tracked.Type.PersistentDataMembers) {
  309. if (mem.IsPrimaryKey || mem.IsDbGenerated || mem.IsVersion) {
  310. if (tracked.HasChangedValue(mem)) {
  311. if (mem.IsPrimaryKey) {
  312. throw Error.IdentityChangeNotAllowed(mem.Name, tracked.Type.Name);
  313. }
  314. else {
  315. throw Error.DbGeneratedChangeNotAllowed(mem.Name, tracked.Type.Name);
  316. }
  317. }
  318. }
  319. }
  320. }
  321. /// <summary>
  322. /// Create an ChangeConflictException with the best message
  323. /// </summary>
  324. static private ChangeConflictException CreateChangeConflictException(int totalUpdatesAttempted, int failedUpdates) {
  325. string msg = Strings.RowNotFoundOrChanged;
  326. if (totalUpdatesAttempted > 1) {
  327. msg = Strings.UpdatesFailedMessage(failedUpdates, totalUpdatesAttempted);
  328. }
  329. return new ChangeConflictException(msg);
  330. }
  331. internal void TrackUntrackedObjects() {
  332. Dictionary<object, object> visited = new Dictionary<object, object>();
  333. // search for untracked new objects
  334. List<TrackedObject> items = new List<TrackedObject>(this.tracker.GetInterestingObjects());
  335. foreach (TrackedObject item in items) {
  336. this.TrackUntrackedObjects(item.Type, item.Current, visited);
  337. }
  338. }
  339. internal void ApplyInferredDeletions() {
  340. foreach (TrackedObject item in this.tracker.GetInterestingObjects()) {
  341. if (item.CanInferDelete()) {
  342. // based on DeleteOnNull specifications on the item's associations,
  343. // a deletion can be inferred for this item. The actual state transition
  344. // is dependent on the current item state.
  345. if (item.IsNew) {
  346. item.ConvertToRemoved();
  347. }
  348. else if (item.IsPossiblyModified || item.IsModified) {
  349. item.ConvertToDeleted();
  350. }
  351. }
  352. }
  353. }
  354. private void TrackUntrackedObjects(MetaType type, object item, Dictionary<object, object> visited) {
  355. if (!visited.ContainsKey(item)) {
  356. visited.Add(item, item);
  357. TrackedObject tracked = this.tracker.GetTrackedObject(item);
  358. if (tracked == null) {
  359. tracked = this.tracker.Track(item);
  360. tracked.ConvertToNew();
  361. }
  362. else if (tracked.IsDead || tracked.IsRemoved) {
  363. // ignore
  364. return;
  365. }
  366. // search parents (objects we are dependent on)
  367. foreach (RelatedItem parent in this.services.GetParents(type, item)) {
  368. this.TrackUntrackedObjects(parent.Type, parent.Item, visited);
  369. }
  370. // synch up primary key
  371. if (tracked.IsNew) {
  372. tracked.InitializeDeferredLoaders();
  373. if (!tracked.IsPendingGeneration(tracked.Type.IdentityMembers)) {
  374. tracked.SynchDependentData();
  375. object cached = this.services.InsertLookupCachedObject(tracked.Type, item);
  376. if (cached != item) {
  377. TrackedObject cachedTracked = this.tracker.GetTrackedObject(cached);
  378. Debug.Assert(cachedTracked != null);
  379. if (cachedTracked.IsDeleted || cachedTracked.CanInferDelete()) {
  380. // adding new object with same ID as object being deleted.. turn into modified
  381. tracked.ConvertToPossiblyModified(cachedTracked.Original);
  382. // turn deleted to dead...
  383. cachedTracked.ConvertToDead();
  384. this.services.RemoveCachedObjectLike(tracked.Type, item);
  385. this.services.InsertLookupCachedObject(tracked.Type, item);
  386. }
  387. else if (!cachedTracked.IsDead) {
  388. throw new DuplicateKeyException(item, Strings.CantAddAlreadyExistingKey);
  389. }
  390. }
  391. }
  392. else {
  393. // we may have a generated PK, however we set the PK on this new item to
  394. // match a deleted item
  395. object cached = this.services.GetCachedObjectLike(tracked.Type, item);
  396. if (cached != null) {
  397. TrackedObject cachedTracked = this.tracker.GetTrackedObject(cached);
  398. Debug.Assert(cachedTracked != null);
  399. if (cachedTracked.IsDeleted || cachedTracked.CanInferDelete()) {
  400. // adding new object with same ID as object being deleted.. turn into modified
  401. tracked.ConvertToPossiblyModified(cachedTracked.Original);
  402. // turn deleted to dead...
  403. cachedTracked.ConvertToDead();
  404. this.services.RemoveCachedObjectLike(tracked.Type, item);
  405. this.services.InsertLookupCachedObject(tracked.Type, item);
  406. }
  407. }
  408. }
  409. }
  410. // search children (objects that are dependent on us)
  411. foreach (RelatedItem child in this.services.GetChildren(type, item)) {
  412. this.TrackUntrackedObjects(child.Type, child.Item, visited);
  413. }
  414. }
  415. }
  416. internal void ObserveUntrackedObjects() {
  417. Dictionary<object, object> visited = new Dictionary<object, object>();
  418. List<TrackedObject> items = new List<TrackedObject>(this.tracker.GetInterestingObjects());
  419. foreach (TrackedObject item in items) {
  420. this.ObserveUntrackedObjects(item.Type, item.Current, visited);
  421. }
  422. }
  423. private void ObserveUntrackedObjects(MetaType type, object item, Dictionary<object, object> visited) {
  424. if (!visited.ContainsKey(item)) {
  425. visited.Add(item, item);
  426. TrackedObject tracked = this.tracker.GetTrackedObject(item);
  427. if (tracked == null) {
  428. tracked = this.tracker.Track(item);
  429. tracked.ConvertToNew();
  430. } else if (tracked.IsDead || tracked.IsRemoved) {
  431. // ignore
  432. return;
  433. }
  434. // search parents (objects we are dependent on)
  435. foreach (RelatedItem parent in this.services.GetParents(type, item)) {
  436. this.ObserveUntrackedObjects(parent.Type, parent.Item, visited);
  437. }
  438. // synch up primary key unless its generated.
  439. if (tracked.IsNew) {
  440. if (!tracked.IsPendingGeneration(tracked.Type.IdentityMembers)) {
  441. tracked.SynchDependentData();
  442. }
  443. }
  444. // search children (objects that are dependent on us)
  445. foreach (RelatedItem child in this.services.GetChildren(type, item)) {
  446. this.ObserveUntrackedObjects(child.Type, child.Item, visited);
  447. }
  448. }
  449. }
  450. private TrackedObject GetOtherItem(MetaAssociation assoc, object instance) {
  451. if (instance == null)
  452. return null;
  453. object other = null;
  454. // Don't load unloaded references
  455. if (assoc.ThisMember.StorageAccessor.HasAssignedValue(instance) ||
  456. assoc.ThisMember.StorageAccessor.HasLoadedValue(instance)
  457. ) {
  458. other = assoc.ThisMember.MemberAccessor.GetBoxedValue(instance);
  459. }
  460. else if (assoc.OtherKeyIsPrimaryKey) {
  461. // Maybe it's in the cache, but not yet attached through reference.
  462. object[] foreignKeys = CommonDataServices.GetForeignKeyValues(assoc, instance);
  463. other = this.services.GetCachedObject(assoc.OtherType, foreignKeys);
  464. }
  465. // else the other key is not the primary key so there is no way to try to look it up
  466. return (other != null) ? this.tracker.GetTrackedObject(other) : null;
  467. }
  468. private bool HasAssociationChanged(MetaAssociation assoc, TrackedObject item) {
  469. if (item.Original != null && item.Current != null) {
  470. if (assoc.ThisMember.StorageAccessor.HasAssignedValue(item.Current) ||
  471. assoc.ThisMember.StorageAccessor.HasLoadedValue(item.Current)
  472. ) {
  473. return this.GetOtherItem(assoc, item.Current) != this.GetOtherItem(assoc, item.Original);
  474. }
  475. else {
  476. object[] currentFKs = CommonDataServices.GetForeignKeyValues(assoc, item.Current);
  477. object[] originaFKs = CommonDataServices.GetForeignKeyValues(assoc, item.Original);
  478. for (int i = 0, n = currentFKs.Length; i < n; i++) {
  479. if (!object.Equals(currentFKs[i], originaFKs[i]))
  480. return true;
  481. }
  482. }
  483. }
  484. return false;
  485. }
  486. private void BuildEdgeMaps() {
  487. this.currentParentEdges.Clear();
  488. this.originalChildEdges.Clear();
  489. this.originalChildReferences.Clear();
  490. List<TrackedObject> list = new List<TrackedObject>(this.tracker.GetInterestingObjects());
  491. foreach (TrackedObject item in list) {
  492. bool isNew = item.IsNew;
  493. MetaType mt = item.Type;
  494. foreach (MetaAssociation assoc in mt.Associations) {
  495. if (assoc.IsForeignKey) {
  496. TrackedObject otherItem = this.GetOtherItem(assoc, item.Current);
  497. TrackedObject dbOtherItem = this.GetOtherItem(assoc, item.Original);
  498. bool pointsToDeleted = (otherItem != null && otherItem.IsDeleted) || (dbOtherItem != null && dbOtherItem.IsDeleted);
  499. bool pointsToNew = (otherItem != null && otherItem.IsNew);
  500. if (isNew || pointsToDeleted || pointsToNew || this.HasAssociationChanged(assoc, item)) {
  501. if (otherItem != null) {
  502. this.currentParentEdges.Add(assoc, item, otherItem);
  503. }
  504. if (dbOtherItem != null) {
  505. if (assoc.IsUnique) {
  506. this.originalChildEdges.Add(assoc, dbOtherItem, item);
  507. }
  508. this.originalChildReferences.Add(dbOtherItem, item);
  509. }
  510. }
  511. }
  512. }
  513. }
  514. }
  515. enum VisitState {
  516. Before,
  517. After
  518. }
  519. private List<TrackedObject> GetOrderedList() {
  520. var objects = this.tracker.GetInterestingObjects().ToList();
  521. // give list an initial order (most likely correct order) to avoid deadlocks in server
  522. var range = Enumerable.Range(0, objects.Count).ToList();
  523. range.Sort((int x, int y) => Compare(objects[x], x, objects[y], y));
  524. var ordered = range.Select(i => objects[i]).ToList();
  525. // permute order if constraint dependencies requires some changes to come before others
  526. var visited = new Dictionary<TrackedObject, VisitState>();
  527. var list = new List<TrackedObject>();
  528. foreach (TrackedObject item in ordered) {
  529. this.BuildDependencyOrderedList(item, list, visited);
  530. }
  531. return list;
  532. }
  533. private static int Compare(TrackedObject x, int xOrdinal, TrackedObject y, int yOrdinal) {
  534. // deal with possible nulls
  535. if (x == y) {
  536. return 0;
  537. }
  538. if (x == null) {
  539. return -1;
  540. }
  541. else if (y == null) {
  542. return 1;
  543. }
  544. // first order by action: Inserts first, Updates, Deletes last
  545. int xAction = x.IsNew ? 0 : x.IsDeleted ? 2 : 1;
  546. int yAction = y.IsNew ? 0 : y.IsDeleted ? 2 : 1;
  547. if (xAction < yAction) {
  548. return -1;
  549. }
  550. else if (xAction > yAction) {
  551. return 1;
  552. }
  553. // no need to order inserts (PK's may not even exist)
  554. if (x.IsNew) {
  555. // keep original order
  556. return xOrdinal.CompareTo(yOrdinal);
  557. }
  558. // second order by type
  559. if (x.Type != y.Type) {
  560. return string.CompareOrdinal(x.Type.Type.FullName, y.Type.Type.FullName);
  561. }
  562. // lastly, order by PK values
  563. int result = 0;
  564. foreach (MetaDataMember mm in x.Type.IdentityMembers) {
  565. object xValue = mm.StorageAccessor.GetBoxedValue(x.Current);
  566. object yValue = mm.StorageAccessor.GetBoxedValue(y.Current);
  567. if (xValue == null) {
  568. if (yValue != null) {
  569. return -1;
  570. }
  571. }
  572. else {
  573. IComparable xc = xValue as IComparable;
  574. if (xc != null) {
  575. result = xc.CompareTo(yValue);
  576. if (result != 0) {
  577. return result;
  578. }
  579. }
  580. }
  581. }
  582. // they are the same? leave in original order
  583. return xOrdinal.CompareTo(yOrdinal);
  584. }
  585. private void BuildDependencyOrderedList(TrackedObject item, List<TrackedObject> list, Dictionary<TrackedObject, VisitState> visited) {
  586. VisitState state;
  587. if (visited.TryGetValue(item, out state)) {
  588. if (state == VisitState.Before) {
  589. throw Error.CycleDetected();
  590. }
  591. return;
  592. }
  593. visited[item] = VisitState.Before;
  594. if (item.IsInteresting) {
  595. if (item.IsDeleted) {
  596. // if 'item' is deleted
  597. // all objects that used to refer to 'item' must be ordered before item
  598. foreach (TrackedObject other in this.originalChildReferences[item]) {
  599. if (other != item) {
  600. this.BuildDependencyOrderedList(other, list, visited);
  601. }
  602. }
  603. }
  604. else {
  605. // if 'item' is new or changed
  606. // for all objects 'other' that 'item' refers to along association 'assoc'
  607. // if 'other' is new then 'other' must be ordered before 'item'
  608. // if 'assoc' is pure one-to-one and some other item 'prevItem' used to refer to 'other'
  609. // then 'prevItem' must be ordered before 'item'
  610. foreach (MetaAssociation assoc in item.Type.Associations) {
  611. if (assoc.IsForeignKey) {
  612. TrackedObject other = this.currentParentEdges[assoc, item];
  613. if (other != null) {
  614. if (other.IsNew) {
  615. // if other is new, visit other first (since item's FK depends on it)
  616. if (other != item || item.Type.DBGeneratedIdentityMember != null) {
  617. this.BuildDependencyOrderedList(other, list, visited);
  618. }
  619. }
  620. else if ((assoc.IsUnique || assoc.ThisKeyIsPrimaryKey)) {
  621. TrackedObject prevItem = this.originalChildEdges[assoc, other];
  622. if (prevItem != null && other != item) {
  623. this.BuildDependencyOrderedList(prevItem, list, visited);
  624. }
  625. }
  626. }
  627. }
  628. }
  629. }
  630. list.Add(item);
  631. }
  632. visited[item] = VisitState.After;
  633. }
  634. class EdgeMap {
  635. Dictionary<MetaAssociation, Dictionary<TrackedObject, TrackedObject>> associations;
  636. internal EdgeMap() {
  637. this.associations = new Dictionary<MetaAssociation, Dictionary<TrackedObject, TrackedObject>>();
  638. }
  639. internal void Add(MetaAssociation assoc, TrackedObject from, TrackedObject to) {
  640. Dictionary<TrackedObject, TrackedObject> pairs;
  641. if (!associations.TryGetValue(assoc, out pairs)) {
  642. pairs = new Dictionary<TrackedObject, TrackedObject>();
  643. associations.Add(assoc, pairs);
  644. }
  645. pairs.Add(from, to);
  646. }
  647. internal TrackedObject this[MetaAssociation assoc, TrackedObject from] {
  648. get {
  649. Dictionary<TrackedObject, TrackedObject> pairs;
  650. if (associations.TryGetValue(assoc, out pairs)) {
  651. TrackedObject to;
  652. if (pairs.TryGetValue(from, out to)) {
  653. return to;
  654. }
  655. }
  656. return null;
  657. }
  658. }
  659. internal void Clear() {
  660. this.associations.Clear();
  661. }
  662. }
  663. class ReferenceMap {
  664. Dictionary<TrackedObject, List<TrackedObject>> references;
  665. internal ReferenceMap() {
  666. this.references = new Dictionary<TrackedObject, List<TrackedObject>>();
  667. }
  668. internal void Add(TrackedObject from, TrackedObject to) {
  669. List<TrackedObject> refs;
  670. if (!references.TryGetValue(from, out refs)) {
  671. refs = new List<TrackedObject>();
  672. references.Add(from, refs);
  673. }
  674. if (!refs.Contains(to))
  675. refs.Add(to);
  676. }
  677. internal IEnumerable<TrackedObject> this[TrackedObject from] {
  678. get {
  679. List<TrackedObject> refs;
  680. if (references.TryGetValue(from, out refs)) {
  681. return refs;
  682. }
  683. return Empty;
  684. }
  685. }
  686. internal void Clear() {
  687. this.references.Clear();
  688. }
  689. private static TrackedObject[] Empty = new TrackedObject[] { };
  690. }
  691. }
  692. }