2 using System
.Collections
.Generic
;
4 using System
.Reflection
;
6 using System
.Diagnostics
;
8 namespace System
.Data
.Linq
{
9 using System
.Data
.Linq
.Mapping
;
10 using System
.Data
.Linq
.Provider
;
13 /// Describes the type of change the entity will undergo when submitted to the database.
15 public enum ChangeAction
{
17 /// The entity will not be submitted.
21 /// The entity will be deleted.
25 /// The entity will be inserted.
29 /// The entity will be updated.
34 internal class ChangeProcessor
{
35 CommonDataServices services
;
37 ChangeTracker tracker
;
38 ChangeDirector changeDirector
;
39 EdgeMap currentParentEdges
;
40 EdgeMap originalChildEdges
;
41 ReferenceMap originalChildReferences
;
43 internal ChangeProcessor(CommonDataServices services
, DataContext context
) {
44 this.services
= services
;
45 this.context
= context
;
46 this.tracker
= services
.ChangeTracker
;
47 this.changeDirector
= services
.ChangeDirector
;
48 this.currentParentEdges
= new EdgeMap();
49 this.originalChildEdges
= new EdgeMap();
50 this.originalChildReferences
= new ReferenceMap();
53 internal void SubmitChanges(ConflictMode failureMode
) {
54 this.TrackUntrackedObjects();
55 // Must apply inferred deletions only after any untracked objects
57 this.ApplyInferredDeletions();
60 var list
= this.GetOrderedList();
64 int numUpdatesAttempted
= 0;
65 ChangeConflictSession conflictSession
= new ChangeConflictSession(this.context
);
66 List
<ObjectChangeConflict
> conflicts
= new List
<ObjectChangeConflict
>();
67 List
<TrackedObject
> deletedItems
= new List
<TrackedObject
>();
68 List
<TrackedObject
> insertedItems
= new List
<TrackedObject
>();
69 List
<TrackedObject
> syncDependentItems
= new List
<TrackedObject
>();
71 foreach (TrackedObject item
in list
) {
74 if (item
.SynchDependentData()) {
75 syncDependentItems
.Add(item
);
77 changeDirector
.Insert(item
);
78 // store all inserted items for post processing
79 insertedItems
.Add(item
);
81 else if (item
.IsDeleted
) {
82 // Delete returns 1 if the delete was successfull, 0 if the row exists
83 // but wasn't deleted due to an OC conflict, or -1 if the row was
84 // deleted by another context (no OC conflict in this case)
85 numUpdatesAttempted
++;
86 int ret
= changeDirector
.Delete(item
);
88 conflicts
.Add(new ObjectChangeConflict(conflictSession
, item
, false));
91 // store all deleted items for post processing
92 deletedItems
.Add(item
);
95 else if (item
.IsPossiblyModified
) {
96 if (item
.SynchDependentData()) {
97 syncDependentItems
.Add(item
);
99 if (item
.IsModified
) {
100 CheckForInvalidChanges(item
);
101 numUpdatesAttempted
++;
102 if (changeDirector
.Update(item
) <= 0) {
103 conflicts
.Add(new ObjectChangeConflict(conflictSession
, item
));
108 catch (ChangeConflictException
) {
109 conflicts
.Add(new ObjectChangeConflict(conflictSession
, item
));
111 if (conflicts
.Count
> 0 && failureMode
== ConflictMode
.FailOnFirstConflict
) {
116 // if we have accumulated any failed updates, throw the exception now
117 if (conflicts
.Count
> 0) {
118 // First we need to rollback any value that have already been auto-sync'd, since the values are no longer valid on the server
119 changeDirector
.RollbackAutoSync();
120 // Also rollback any dependent items that were sync'd, since their parent values may have been rolled back
121 foreach (TrackedObject syncDependentItem
in syncDependentItems
) {
122 Debug
.Assert(syncDependentItem
.IsNew
|| syncDependentItem
.IsPossiblyModified
, "SynchDependent data should only be rolled back for new and modified objects.");
123 syncDependentItem
.SynchDependentData();
125 this.context
.ChangeConflicts
.Fill(conflicts
);
126 throw CreateChangeConflictException(numUpdatesAttempted
, conflicts
.Count
);
129 // No conflicts occurred, so we don't need to save the rollback values anymore
130 changeDirector
.ClearAutoSyncRollback();
133 // Only after all updates have been sucessfully processed do we want to make
134 // post processing modifications to the objects and/or cache state.
135 PostProcessUpdates(insertedItems
, deletedItems
);
138 private void PostProcessUpdates(List
<TrackedObject
> insertedItems
, List
<TrackedObject
> deletedItems
) {
139 // perform post delete processing
140 foreach (TrackedObject deletedItem
in deletedItems
) {
141 // remove deleted item from identity cache
142 this.services
.RemoveCachedObjectLike(deletedItem
.Type
, deletedItem
.Original
);
143 ClearForeignKeyReferences(deletedItem
);
146 // perform post insert processing
147 foreach (TrackedObject insertedItem
in insertedItems
) {
148 object lookup
= this.services
.InsertLookupCachedObject(insertedItem
.Type
, insertedItem
.Current
);
149 if (lookup
!= insertedItem
.Current
) {
150 throw new DuplicateKeyException(insertedItem
.Current
, Strings
.DatabaseGeneratedAlreadyExistingKey
);
152 insertedItem
.InitializeDeferredLoaders();
157 /// Clears out the foreign key values and parent object references for deleted objects on the child side of a relationship.
158 /// For bi-directional relationships, also performs the following fixup:
159 /// - for 1:N we remove the deleted entity from the opposite EntitySet or collection
160 /// - for 1:1 we null out the back reference
162 private void ClearForeignKeyReferences(TrackedObject to
) {
163 Debug
.Assert(to
.IsDeleted
, "Foreign key reference cleanup should only happen on Deleted objects.");
164 foreach (MetaAssociation assoc
in to
.Type
.Associations
) {
165 if (assoc
.IsForeignKey
) {
166 // If there is a member on the other side referring back to us (i.e. this is a bi-directional relationship),
167 // we want to do a cache lookup to find the other side, then will remove ourselves from that collection.
168 // 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.
169 if (assoc
.OtherMember
!= null && assoc
.OtherKeyIsPrimaryKey
) {
170 Debug
.Assert(assoc
.OtherMember
.IsAssociation
, "OtherMember of the association is expected to also be an association.");
171 // Search the cache for the target of the association, since
172 // it might not be loaded on the object being deleted, and we
173 // don't want to force a load.
174 object[] keyValues
= CommonDataServices
.GetForeignKeyValues(assoc
, to
.Current
);
175 object cached
= this.services
.IdentityManager
.Find(assoc
.OtherType
, keyValues
);
177 if (cached
!= null) {
178 if (assoc
.OtherMember
.Association
.IsMany
) {
179 // Note that going through the IList interface handles
180 // EntitySet as well as POCO collections that implement IList
181 // and are not FixedSize.
182 System
.Collections
.IList collection
= assoc
.OtherMember
.MemberAccessor
.GetBoxedValue(cached
) as System
.Collections
.IList
;
183 if (collection
!= null && !collection
.IsFixedSize
) {
184 collection
.Remove(to
.Current
);
185 // Explicitly clear the foreign key values and parent object reference
186 ClearForeignKeysHelper(assoc
, to
.Current
);
190 // Null out the other association. Since this is a 1:1 association,
191 // we're not concerned here with causing a deferred load, since the
192 // target is already cached (since we're deleting it).
193 assoc
.OtherMember
.MemberAccessor
.SetBoxedValue(ref cached
, null);
194 // Explicitly clear the foreign key values and parent object reference
195 ClearForeignKeysHelper(assoc
, to
.Current
);
198 // else the item was not found in the cache, so there is no fixup that has to be done
199 // We are explicitly not calling ClearForeignKeysHelper because it breaks existing shipped behavior and we want to maintain backward compatibility
202 // 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
203 ClearForeignKeysHelper(assoc
, to
.Current
);
206 // else this is not the 1-side (foreign key) of the relationship, so there is nothing for us to do
210 // Ensure the the member and foreign keys are nulled so that after trackedInstance is deleted,
211 // the object does not appear to be associated with the other side anymore. This prevents the deleted object
212 // from referencing objects still in the cache, but also will prevent the related object from being implicitly loaded
213 private static void ClearForeignKeysHelper(MetaAssociation assoc
, object trackedInstance
) {
214 Debug
.Assert(assoc
.IsForeignKey
, "Foreign key clearing should only happen on foreign key side of the association.");
215 Debug
.Assert(assoc
.ThisMember
.IsAssociation
, "Expected ThisMember of an association to always be an association.");
217 // If this member is one of our deferred loaders, and it does not already have a value, explicitly set the deferred source to
218 // null so that when we set the association member itself to null later, it doesn't trigger an implicit load.
219 // 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.
220 MetaDataMember thisMember
= assoc
.ThisMember
;
222 if (thisMember
.IsDeferred
&&
223 !(thisMember
.StorageAccessor
.HasAssignedValue(trackedInstance
) || thisMember
.StorageAccessor
.HasLoadedValue(trackedInstance
)))
225 // If this is a deferred member, set the value directly in the deferred accessor instead of going
226 // through the normal member accessor, so that we don't trigger an implicit load.
227 thisMember
.DeferredSourceAccessor
.SetBoxedValue(ref trackedInstance
, null);
230 // Notify the object that the relationship should be considered deleted.
231 // This allows the object to do its own fixup even when we can't do it automatically.
232 thisMember
.MemberAccessor
.SetBoxedValue(ref trackedInstance
, null);
234 // Also set the foreign key values to null if possible
235 for (int i
= 0, n
= assoc
.ThisKey
.Count
; i
< n
; i
++) {
236 MetaDataMember thisKey
= assoc
.ThisKey
[i
];
237 if (thisKey
.CanBeNull
) {
238 thisKey
.StorageAccessor
.SetBoxedValue(ref trackedInstance
, null);
243 private static void ValidateAll(IEnumerable
<TrackedObject
> list
) {
244 foreach (var item
in list
) {
246 item
.SynchDependentData();
247 if (item
.Type
.HasAnyValidateMethod
) {
248 SendOnValidate(item
.Type
, item
, ChangeAction
.Insert
);
250 } else if (item
.IsDeleted
) {
251 if (item
.Type
.HasAnyValidateMethod
) {
252 SendOnValidate(item
.Type
, item
, ChangeAction
.Delete
);
254 } else if (item
.IsPossiblyModified
) {
255 item
.SynchDependentData();
256 if (item
.IsModified
&& item
.Type
.HasAnyValidateMethod
) {
257 SendOnValidate(item
.Type
, item
, ChangeAction
.Update
);
263 private static void SendOnValidate(MetaType type
, TrackedObject item
, ChangeAction changeAction
) {
265 SendOnValidate(type
.InheritanceBase
, item
, changeAction
);
267 if (type
.OnValidateMethod
!= null) {
269 type
.OnValidateMethod
.Invoke(item
.Current
, new object[] { changeAction }
);
270 } catch (TargetInvocationException tie
) {
271 if (tie
.InnerException
!= null) {
272 throw tie
.InnerException
;
281 internal string GetChangeText() {
282 this.ObserveUntrackedObjects();
283 // Must apply inferred deletions only after any untracked objects
285 this.ApplyInferredDeletions();
286 this.BuildEdgeMaps();
288 // append change text only
289 StringBuilder changeText
= new StringBuilder();
290 foreach (TrackedObject item
in this.GetOrderedList()) {
292 item
.SynchDependentData();
293 changeDirector
.AppendInsertText(item
, changeText
);
295 else if (item
.IsDeleted
) {
296 changeDirector
.AppendDeleteText(item
, changeText
);
298 else if (item
.IsPossiblyModified
) {
299 item
.SynchDependentData();
300 if (item
.IsModified
) {
301 changeDirector
.AppendUpdateText(item
, changeText
);
305 return changeText
.ToString();
308 internal ChangeSet
GetChangeSet() {
309 List
<object> newEntities
= new List
<object>();
310 List
<object> deletedEntities
= new List
<object>();
311 List
<object> changedEntities
= new List
<object>();
313 this.ObserveUntrackedObjects();
314 // Must apply inferred deletions only after any untracked objects
316 this.ApplyInferredDeletions();
318 foreach (TrackedObject item
in this.tracker
.GetInterestingObjects()) {
320 item
.SynchDependentData();
321 newEntities
.Add(item
.Current
);
323 else if (item
.IsDeleted
) {
324 deletedEntities
.Add(item
.Current
);
326 else if (item
.IsPossiblyModified
) {
327 item
.SynchDependentData();
328 if (item
.IsModified
) {
329 changedEntities
.Add(item
.Current
);
334 return new ChangeSet(newEntities
.AsReadOnly(), deletedEntities
.AsReadOnly(), changedEntities
.AsReadOnly());
337 // verify that primary key and db-generated values have not changed
338 private static void CheckForInvalidChanges(TrackedObject tracked
) {
339 foreach (MetaDataMember mem
in tracked
.Type
.PersistentDataMembers
) {
340 if (mem
.IsPrimaryKey
|| mem
.IsDbGenerated
|| mem
.IsVersion
) {
341 if (tracked
.HasChangedValue(mem
)) {
342 if (mem
.IsPrimaryKey
) {
343 throw Error
.IdentityChangeNotAllowed(mem
.Name
, tracked
.Type
.Name
);
346 throw Error
.DbGeneratedChangeNotAllowed(mem
.Name
, tracked
.Type
.Name
);
354 /// Create an ChangeConflictException with the best message
356 static private ChangeConflictException
CreateChangeConflictException(int totalUpdatesAttempted
, int failedUpdates
) {
357 string msg
= Strings
.RowNotFoundOrChanged
;
358 if (totalUpdatesAttempted
> 1) {
359 msg
= Strings
.UpdatesFailedMessage(failedUpdates
, totalUpdatesAttempted
);
361 return new ChangeConflictException(msg
);
364 internal void TrackUntrackedObjects() {
365 Dictionary
<object, object> visited
= new Dictionary
<object, object>();
367 // search for untracked new objects
368 List
<TrackedObject
> items
= new List
<TrackedObject
>(this.tracker
.GetInterestingObjects());
369 foreach (TrackedObject item
in items
) {
370 this.TrackUntrackedObjects(item
.Type
, item
.Current
, visited
);
374 internal void ApplyInferredDeletions() {
375 foreach (TrackedObject item
in this.tracker
.GetInterestingObjects()) {
376 if (item
.CanInferDelete()) {
377 // based on DeleteOnNull specifications on the item's associations,
378 // a deletion can be inferred for this item. The actual state transition
379 // is dependent on the current item state.
381 item
.ConvertToRemoved();
383 else if (item
.IsPossiblyModified
|| item
.IsModified
) {
384 item
.ConvertToDeleted();
390 private void TrackUntrackedObjects(MetaType type
, object item
, Dictionary
<object, object> visited
) {
391 if (!visited
.ContainsKey(item
)) {
392 visited
.Add(item
, item
);
393 TrackedObject tracked
= this.tracker
.GetTrackedObject(item
);
394 if (tracked
== null) {
395 tracked
= this.tracker
.Track(item
);
396 tracked
.ConvertToNew();
398 else if (tracked
.IsDead
|| tracked
.IsRemoved
) {
403 // search parents (objects we are dependent on)
404 foreach (RelatedItem parent
in this.services
.GetParents(type
, item
)) {
405 this.TrackUntrackedObjects(parent
.Type
, parent
.Item
, visited
);
408 // synch up primary key
410 tracked
.InitializeDeferredLoaders();
412 if (!tracked
.IsPendingGeneration(tracked
.Type
.IdentityMembers
)) {
413 tracked
.SynchDependentData();
414 object cached
= this.services
.InsertLookupCachedObject(tracked
.Type
, item
);
415 if (cached
!= item
) {
416 TrackedObject cachedTracked
= this.tracker
.GetTrackedObject(cached
);
417 Debug
.Assert(cachedTracked
!= null);
418 if (cachedTracked
.IsDeleted
|| cachedTracked
.CanInferDelete()) {
419 // adding new object with same ID as object being deleted.. turn into modified
420 tracked
.ConvertToPossiblyModified(cachedTracked
.Original
);
421 // turn deleted to dead...
422 cachedTracked
.ConvertToDead();
424 this.services
.RemoveCachedObjectLike(tracked
.Type
, item
);
425 this.services
.InsertLookupCachedObject(tracked
.Type
, item
);
427 else if (!cachedTracked
.IsDead
) {
428 throw new DuplicateKeyException(item
, Strings
.CantAddAlreadyExistingKey
);
433 // we may have a generated PK, however we set the PK on this new item to
434 // match a deleted item
435 object cached
= this.services
.GetCachedObjectLike(tracked
.Type
, item
);
436 if (cached
!= null) {
437 TrackedObject cachedTracked
= this.tracker
.GetTrackedObject(cached
);
438 Debug
.Assert(cachedTracked
!= null);
439 if (cachedTracked
.IsDeleted
|| cachedTracked
.CanInferDelete()) {
440 // adding new object with same ID as object being deleted.. turn into modified
441 tracked
.ConvertToPossiblyModified(cachedTracked
.Original
);
442 // turn deleted to dead...
443 cachedTracked
.ConvertToDead();
445 this.services
.RemoveCachedObjectLike(tracked
.Type
, item
);
446 this.services
.InsertLookupCachedObject(tracked
.Type
, item
);
452 // search children (objects that are dependent on us)
453 foreach (RelatedItem child
in this.services
.GetChildren(type
, item
)) {
454 this.TrackUntrackedObjects(child
.Type
, child
.Item
, visited
);
459 internal void ObserveUntrackedObjects() {
460 Dictionary
<object, object> visited
= new Dictionary
<object, object>();
462 List
<TrackedObject
> items
= new List
<TrackedObject
>(this.tracker
.GetInterestingObjects());
463 foreach (TrackedObject item
in items
) {
464 this.ObserveUntrackedObjects(item
.Type
, item
.Current
, visited
);
468 private void ObserveUntrackedObjects(MetaType type
, object item
, Dictionary
<object, object> visited
) {
469 if (!visited
.ContainsKey(item
)) {
470 visited
.Add(item
, item
);
471 TrackedObject tracked
= this.tracker
.GetTrackedObject(item
);
472 if (tracked
== null) {
473 tracked
= this.tracker
.Track(item
);
474 tracked
.ConvertToNew();
475 } else if (tracked
.IsDead
|| tracked
.IsRemoved
) {
480 // search parents (objects we are dependent on)
481 foreach (RelatedItem parent
in this.services
.GetParents(type
, item
)) {
482 this.ObserveUntrackedObjects(parent
.Type
, parent
.Item
, visited
);
485 // synch up primary key unless its generated.
487 if (!tracked
.IsPendingGeneration(tracked
.Type
.IdentityMembers
)) {
488 tracked
.SynchDependentData();
492 // search children (objects that are dependent on us)
493 foreach (RelatedItem child
in this.services
.GetChildren(type
, item
)) {
494 this.ObserveUntrackedObjects(child
.Type
, child
.Item
, visited
);
499 private TrackedObject
GetOtherItem(MetaAssociation assoc
, object instance
) {
500 if (instance
== null)
503 // Don't load unloaded references
504 if (assoc
.ThisMember
.StorageAccessor
.HasAssignedValue(instance
) ||
505 assoc
.ThisMember
.StorageAccessor
.HasLoadedValue(instance
)
507 other
= assoc
.ThisMember
.MemberAccessor
.GetBoxedValue(instance
);
509 else if (assoc
.OtherKeyIsPrimaryKey
) {
510 // Maybe it's in the cache, but not yet attached through reference.
511 object[] foreignKeys
= CommonDataServices
.GetForeignKeyValues(assoc
, instance
);
512 other
= this.services
.GetCachedObject(assoc
.OtherType
, foreignKeys
);
514 // else the other key is not the primary key so there is no way to try to look it up
515 return (other
!= null) ? this.tracker
.GetTrackedObject(other
) : null;
518 private bool HasAssociationChanged(MetaAssociation assoc
, TrackedObject item
) {
519 if (item
.Original
!= null && item
.Current
!= null) {
520 if (assoc
.ThisMember
.StorageAccessor
.HasAssignedValue(item
.Current
) ||
521 assoc
.ThisMember
.StorageAccessor
.HasLoadedValue(item
.Current
)
523 return this.GetOtherItem(assoc
, item
.Current
) != this.GetOtherItem(assoc
, item
.Original
);
526 object[] currentFKs
= CommonDataServices
.GetForeignKeyValues(assoc
, item
.Current
);
527 object[] originaFKs
= CommonDataServices
.GetForeignKeyValues(assoc
, item
.Original
);
528 for (int i
= 0, n
= currentFKs
.Length
; i
< n
; i
++) {
529 if (!object.Equals(currentFKs
[i
], originaFKs
[i
]))
537 private void BuildEdgeMaps() {
538 this.currentParentEdges
.Clear();
539 this.originalChildEdges
.Clear();
540 this.originalChildReferences
.Clear();
542 List
<TrackedObject
> list
= new List
<TrackedObject
>(this.tracker
.GetInterestingObjects());
543 foreach (TrackedObject item
in list
) {
544 bool isNew
= item
.IsNew
;
545 MetaType mt
= item
.Type
;
546 foreach (MetaAssociation assoc
in mt
.Associations
) {
547 if (assoc
.IsForeignKey
) {
548 TrackedObject otherItem
= this.GetOtherItem(assoc
, item
.Current
);
549 TrackedObject dbOtherItem
= this.GetOtherItem(assoc
, item
.Original
);
550 bool pointsToDeleted
= (otherItem
!= null && otherItem
.IsDeleted
) || (dbOtherItem
!= null && dbOtherItem
.IsDeleted
);
551 bool pointsToNew
= (otherItem
!= null && otherItem
.IsNew
);
553 if (isNew
|| pointsToDeleted
|| pointsToNew
|| this.HasAssociationChanged(assoc
, item
)) {
554 if (otherItem
!= null) {
555 this.currentParentEdges
.Add(assoc
, item
, otherItem
);
557 if (dbOtherItem
!= null) {
558 if (assoc
.IsUnique
) {
559 this.originalChildEdges
.Add(assoc
, dbOtherItem
, item
);
561 this.originalChildReferences
.Add(dbOtherItem
, item
);
574 private List
<TrackedObject
> GetOrderedList() {
575 var objects
= this.tracker
.GetInterestingObjects().ToList();
577 // give list an initial order (most likely correct order) to avoid deadlocks in server
578 var range
= Enumerable
.Range(0, objects
.Count
).ToList();
579 range
.Sort((int x
, int y
) => Compare(objects
[x
], x
, objects
[y
], y
));
580 var ordered
= range
.Select(i
=> objects
[i
]).ToList();
582 // permute order if constraint dependencies requires some changes to come before others
583 var visited
= new Dictionary
<TrackedObject
, VisitState
>();
584 var list
= new List
<TrackedObject
>();
585 foreach (TrackedObject item
in ordered
) {
586 this.BuildDependencyOrderedList(item
, list
, visited
);
591 private static int Compare(TrackedObject x
, int xOrdinal
, TrackedObject y
, int yOrdinal
) {
592 // deal with possible nulls
599 else if (y
== null) {
602 // first order by action: Inserts first, Updates, Deletes last
603 int xAction
= x
.IsNew
? 0 : x
.IsDeleted
? 2 : 1;
604 int yAction
= y
.IsNew
? 0 : y
.IsDeleted
? 2 : 1;
605 if (xAction
< yAction
) {
608 else if (xAction
> yAction
) {
611 // no need to order inserts (PK's may not even exist)
613 // keep original order
614 return xOrdinal
.CompareTo(yOrdinal
);
616 // second order by type
617 if (x
.Type
!= y
.Type
) {
618 return string.CompareOrdinal(x
.Type
.Type
.FullName
, y
.Type
.Type
.FullName
);
620 // lastly, order by PK values
622 foreach (MetaDataMember mm
in x
.Type
.IdentityMembers
) {
623 object xValue
= mm
.StorageAccessor
.GetBoxedValue(x
.Current
);
624 object yValue
= mm
.StorageAccessor
.GetBoxedValue(y
.Current
);
625 if (xValue
== null) {
626 if (yValue
!= null) {
631 IComparable xc
= xValue
as IComparable
;
633 result
= xc
.CompareTo(yValue
);
640 // they are the same? leave in original order
641 return xOrdinal
.CompareTo(yOrdinal
);
644 private void BuildDependencyOrderedList(TrackedObject item
, List
<TrackedObject
> list
, Dictionary
<TrackedObject
, VisitState
> visited
) {
646 if (visited
.TryGetValue(item
, out state
)) {
647 if (state
== VisitState
.Before
) {
648 throw Error
.CycleDetected();
653 visited
[item
] = VisitState
.Before
;
655 if (item
.IsInteresting
) {
656 if (item
.IsDeleted
) {
657 // if 'item' is deleted
658 // all objects that used to refer to 'item' must be ordered before item
659 foreach (TrackedObject other
in this.originalChildReferences
[item
]) {
661 this.BuildDependencyOrderedList(other
, list
, visited
);
666 // if 'item' is new or changed
667 // for all objects 'other' that 'item' refers to along association 'assoc'
668 // if 'other' is new then 'other' must be ordered before 'item'
669 // if 'assoc' is pure one-to-one and some other item 'prevItem' used to refer to 'other'
670 // then 'prevItem' must be ordered before 'item'
671 foreach (MetaAssociation assoc
in item
.Type
.Associations
) {
672 if (assoc
.IsForeignKey
) {
673 TrackedObject other
= this.currentParentEdges
[assoc
, item
];
676 // if other is new, visit other first (since item's FK depends on it)
677 if (other
!= item
|| item
.Type
.DBGeneratedIdentityMember
!= null) {
678 this.BuildDependencyOrderedList(other
, list
, visited
);
681 else if ((assoc
.IsUnique
|| assoc
.ThisKeyIsPrimaryKey
)) {
682 TrackedObject prevItem
= this.originalChildEdges
[assoc
, other
];
683 if (prevItem
!= null && other
!= item
) {
684 this.BuildDependencyOrderedList(prevItem
, list
, visited
);
695 visited
[item
] = VisitState
.After
;
699 Dictionary
<MetaAssociation
, Dictionary
<TrackedObject
, TrackedObject
>> associations
;
702 this.associations
= new Dictionary
<MetaAssociation
, Dictionary
<TrackedObject
, TrackedObject
>>();
705 internal void Add(MetaAssociation assoc
, TrackedObject
from, TrackedObject to
) {
706 Dictionary
<TrackedObject
, TrackedObject
> pairs
;
707 if (!associations
.TryGetValue(assoc
, out pairs
)) {
708 pairs
= new Dictionary
<TrackedObject
, TrackedObject
>();
709 associations
.Add(assoc
, pairs
);
714 internal TrackedObject
this[MetaAssociation assoc
, TrackedObject
from] {
716 Dictionary
<TrackedObject
, TrackedObject
> pairs
;
717 if (associations
.TryGetValue(assoc
, out pairs
)) {
719 if (pairs
.TryGetValue(from, out to
)) {
726 internal void Clear() {
727 this.associations
.Clear();
732 Dictionary
<TrackedObject
, List
<TrackedObject
>> references
;
734 internal ReferenceMap() {
735 this.references
= new Dictionary
<TrackedObject
, List
<TrackedObject
>>();
738 internal void Add(TrackedObject
from, TrackedObject to
) {
739 List
<TrackedObject
> refs
;
740 if (!references
.TryGetValue(from, out refs
)) {
741 refs
= new List
<TrackedObject
>();
742 references
.Add(from, refs
);
744 if (!refs
.Contains(to
))
748 internal IEnumerable
<TrackedObject
> this[TrackedObject
from] {
750 List
<TrackedObject
> refs
;
751 if (references
.TryGetValue(from, out refs
)) {
758 internal void Clear() {
759 this.references
.Clear();
762 private static TrackedObject
[] Empty
= new TrackedObject
[] { }
;