1 //---------------------------------------------------------------------
2 // <copyright file="UpdateCommandOrderer.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9 
10 namespace System.Data.Mapping.Update.Internal
11 {
12     using System.Collections.Generic;
13     using System.Data.Common.Utils;
14     using System.Data.Metadata.Edm;
15     using System.Diagnostics;
16     using System.Linq;
17 
18     internal class UpdateCommandOrderer : Graph<UpdateCommand>
19     {
20         /// <summary>
21         /// Gets comparer used to resolve identifiers to actual 'owning' key values (e.g. across referential constraints)
22         /// </summary>
23         private readonly ForeignKeyValueComparer _keyComparer;
24 
25         /// <summary>
26         /// Maps from tables to all "source" referential constraints (where the table declares
27         /// foreign keys)
28         /// </summary>
29         private readonly KeyToListMap<EntitySetBase, ReferentialConstraint> _sourceMap;
30 
31         /// <summary>
32         /// Maps from tables to all "target" referential constraints (where the table is
33         /// referenced by a foreign key)
34         /// </summary>
35         private readonly KeyToListMap<EntitySetBase, ReferentialConstraint> _targetMap;
36 
37         /// <summary>
38         /// Tracks whether any function commands exist in the current payload.
39         /// </summary>
40         private readonly bool _hasFunctionCommands;
41 
42         /// <summary>
43         /// Gets translator producing this graph.
44         /// </summary>
45         private readonly UpdateTranslator _translator;
46 
UpdateCommandOrderer(IEnumerable<UpdateCommand> commands, UpdateTranslator translator)47         internal UpdateCommandOrderer(IEnumerable<UpdateCommand> commands, UpdateTranslator translator)
48             : base(EqualityComparer<UpdateCommand>.Default)
49         {
50             _translator = translator;
51             _keyComparer = new ForeignKeyValueComparer(_translator.KeyComparer);
52 
53             HashSet<EntitySet> tables = new HashSet<EntitySet>();
54             HashSet<EntityContainer> containers = new HashSet<EntityContainer>();
55 
56             // add all vertices (one vertex for every command)
57             foreach (UpdateCommand command in commands)
58             {
59                 if (null != command.Table)
60                 {
61                     tables.Add(command.Table);
62                     containers.Add(command.Table.EntityContainer);
63                 }
64                 AddVertex(command);
65                 if (command.Kind == UpdateCommandKind.Function)
66                 {
67                     _hasFunctionCommands = true;
68                 }
69             }
70 
71             // figure out which foreign keys are interesting in this scope
72             InitializeForeignKeyMaps(containers, tables, out _sourceMap, out _targetMap);
73 
74             // add edges for each ordering dependency amongst the commands
75             AddServerGenDependencies();
76             AddForeignKeyDependencies();
77             if (_hasFunctionCommands)
78             {
79                 AddModelDependencies();
80             }
81         }
82 
InitializeForeignKeyMaps(HashSet<EntityContainer> containers, HashSet<EntitySet> tables, out KeyToListMap<EntitySetBase, ReferentialConstraint> sourceMap, out KeyToListMap<EntitySetBase, ReferentialConstraint> targetMap)83         private static void InitializeForeignKeyMaps(HashSet<EntityContainer> containers, HashSet<EntitySet> tables, out KeyToListMap<EntitySetBase, ReferentialConstraint> sourceMap, out KeyToListMap<EntitySetBase, ReferentialConstraint> targetMap)
84         {
85             sourceMap = new KeyToListMap<EntitySetBase, ReferentialConstraint>(EqualityComparer<EntitySetBase>.Default);
86             targetMap = new KeyToListMap<EntitySetBase, ReferentialConstraint>(EqualityComparer<EntitySetBase>.Default);
87 
88             // Retrieve relationship ends from each container to populate edges in dependency
89             // graph
90             foreach (EntityContainer container in containers)
91             {
92                 foreach (EntitySetBase extent in container.BaseEntitySets)
93                 {
94                     AssociationSet associationSet = extent as AssociationSet;
95 
96                     if (null != associationSet)
97                     {
98                         AssociationSetEnd source = null;
99                         AssociationSetEnd target = null;
100 
101                         var ends = associationSet.AssociationSetEnds;
102 
103                         if (2 == ends.Count)
104                         {
105                             // source is equivalent to the "to" end of relationship, target is "from"
106                             AssociationType associationType = associationSet.ElementType;
107                             bool constraintFound = false;
108                             ReferentialConstraint fkConstraint = null;
109                             foreach (ReferentialConstraint constraint in associationType.ReferentialConstraints)
110                             {
111                                 if (constraintFound) { Debug.Fail("relationship set should have at most one constraint"); }
112                                 else { constraintFound = true; }
113                                 source = associationSet.AssociationSetEnds[constraint.ToRole.Name];
114                                 target = associationSet.AssociationSetEnds[constraint.FromRole.Name];
115                                 fkConstraint = constraint;
116                             }
117 
118                             Debug.Assert(constraintFound && null != target && null != source, "relationship set must have at least one constraint");
119                             // only understand binary (foreign key) relationships between entity sets
120                             if (null != target && null != source)
121                             {
122                                 if (tables.Contains(target.EntitySet)&&
123                                     tables.Contains(source.EntitySet))
124                                 {
125                                     // Remember metadata
126                                     sourceMap.Add(source.EntitySet, fkConstraint);
127                                     targetMap.Add(target.EntitySet, fkConstraint);
128                                 }
129                             }
130                         }
131                     }
132                 }
133             }
134         }
135 
136         // Adds edges to dependency graph for server-generated values.
137         //
138         // Determines which commands produce identifiers (key parts) and which commands
139         // consume them. Producers are potentially edge predecessors and consumers are potentially
140         // edge successors. The command objects report the identifiers they produce (OutputIdentifiers)
141         // and the identifiers they consume (InputIdentifiers)
AddServerGenDependencies()142         private void AddServerGenDependencies()
143         {
144             // Identify all "shared" output parameters (e.g., SQL Server identifiers)
145             Dictionary<int, UpdateCommand> predecessors = new Dictionary<int, UpdateCommand>();
146             foreach (UpdateCommand command in this.Vertices)
147             {
148                 foreach (int output in command.OutputIdentifiers)
149                 {
150                     try
151                     {
152                         predecessors.Add(output, command);
153                     }
154                     catch (ArgumentException duplicateKey)
155                     {
156                         // throw an exception indicating that a key value is generated in two locations
157                         // in the store
158                         throw EntityUtil.Update(System.Data.Entity.Strings.Update_AmbiguousServerGenIdentifier, duplicateKey, command.GetStateEntries(_translator));
159                     }
160                 }
161             }
162 
163             // Identify all dependent input parameters
164             foreach (UpdateCommand command in this.Vertices)
165             {
166                 foreach (int input in command.InputIdentifiers)
167                 {
168                     UpdateCommand from;
169                     if (predecessors.TryGetValue(input, out from))
170                     {
171                         AddEdge(from, command);
172                     }
173                 }
174             }
175         }
176 
177         // Adds edges to dependency graph based on foreign keys.
AddForeignKeyDependencies()178         private void AddForeignKeyDependencies()
179         {
180             KeyToListMap<ForeignKeyValue, UpdateCommand> predecessors = DetermineForeignKeyPredecessors();
181             AddForeignKeyEdges(predecessors);
182         }
183 
184         // Finds all successors to the given predecessors and registers the resulting dependency edges in this
185         // graph.
186         //
187         // - Commands (updates or inserts) inserting FK "sources" (referencing foreign key)
188         // - Commands (updates or deletes) deleting FK "targets" (referenced by the foreign key)
189         //
190         // To avoid violating constraints, FK references must be created before their referees, and
191         // cannot be deleted before their references.
AddForeignKeyEdges(KeyToListMap<ForeignKeyValue, UpdateCommand> predecessors)192         private void AddForeignKeyEdges(KeyToListMap<ForeignKeyValue, UpdateCommand> predecessors)
193         {
194             foreach (DynamicUpdateCommand command in this.Vertices.OfType<DynamicUpdateCommand>())
195             {
196                 // register all source successors
197                 if (ModificationOperator.Update == command.Operator ||
198                     ModificationOperator.Insert == command.Operator)
199                 {
200                     foreach (ReferentialConstraint fkConstraint in _sourceMap.EnumerateValues(command.Table))
201                     {
202                         ForeignKeyValue fk;
203                         if (ForeignKeyValue.TryCreateSourceKey(fkConstraint, command.CurrentValues, true, out fk))
204                         {
205                             // if this is an update and the source key is unchanged, there is no
206                             // need to add a dependency (from the perspective of the target, the update
207                             // is a no-op)
208                             ForeignKeyValue originalFK;
209                             if (ModificationOperator.Update != command.Operator ||
210                                 !ForeignKeyValue.TryCreateSourceKey(fkConstraint, command.OriginalValues, true, out originalFK) ||
211                                 !_keyComparer.Equals(originalFK, fk))
212                             {
213                                 foreach (UpdateCommand predecessor in predecessors.EnumerateValues(fk))
214                                 {
215                                     // don't add self-edges for FK dependencies, since a single operation
216                                     // in the store is atomic
217                                     if (predecessor != command)
218                                     {
219                                         AddEdge(predecessor, command);
220                                     }
221                                 }
222                             }
223                         }
224                     }
225                 }
226 
227                 // register all target successors
228                 if (ModificationOperator.Update == command.Operator ||
229                     ModificationOperator.Delete == command.Operator)
230                 {
231                     foreach (ReferentialConstraint fkConstraint in _targetMap.EnumerateValues(command.Table))
232                     {
233                         ForeignKeyValue fk;
234                         if (ForeignKeyValue.TryCreateTargetKey(fkConstraint, command.OriginalValues, false, out fk))
235                         {
236                             // if this is an update and the target key is unchanged, there is no
237                             // need to add a dependency (from the perspective of the source, the update
238                             // is a no-op)
239                             ForeignKeyValue currentFK;
240                             if (ModificationOperator.Update != command.Operator ||
241                                 !ForeignKeyValue.TryCreateTargetKey(fkConstraint, command.CurrentValues, false, out currentFK) ||
242                                 !_keyComparer.Equals(currentFK, fk))
243                             {
244 
245                                 foreach (UpdateCommand predecessor in predecessors.EnumerateValues(fk))
246                                 {
247                                     // don't add self-edges for FK dependencies, since a single operation
248                                     // in the store is atomic
249                                     if (predecessor != command)
250                                     {
251                                         AddEdge(predecessor, command);
252                                     }
253                                 }
254                             }
255                         }
256                     }
257                 }
258             }
259         }
260 
261         // Builds a map from foreign key instances to commands, with an entry for every command that may need to
262         // precede some other operation.
263         //
264         // Predecessor commands must precede other commands using those values. There are two kinds of
265         // predecessor:
266         //
267         // - Commands (updates or inserts) inserting FK "targets" (referenced by the foreign key)
268         // - Commands (updates or deletes) deleting FK "sources" (referencing the foreign key)
269         //
270         // To avoid violating constraints, FK values must be created before they are referenced, and
271         // cannot be deleted before their references
DetermineForeignKeyPredecessors()272         private KeyToListMap<ForeignKeyValue, UpdateCommand> DetermineForeignKeyPredecessors()
273         {
274             KeyToListMap<ForeignKeyValue, UpdateCommand> predecessors = new KeyToListMap<ForeignKeyValue, UpdateCommand>(
275                 _keyComparer);
276 
277             foreach (DynamicUpdateCommand command in this.Vertices.OfType<DynamicUpdateCommand>())
278             {
279                 if (ModificationOperator.Update == command.Operator ||
280                     ModificationOperator.Insert == command.Operator)
281                 {
282                     foreach (ReferentialConstraint fkConstraint in _targetMap.EnumerateValues(command.Table))
283                     {
284                         ForeignKeyValue fk;
285                         if (ForeignKeyValue.TryCreateTargetKey(fkConstraint, command.CurrentValues, true, out fk))
286                         {
287                             // if this is an update and the target key is unchanged, there is no
288                             // need to add a dependency (from the perspective of the target, the update
289                             // is a no-op)
290                             ForeignKeyValue originalFK;
291                             if (ModificationOperator.Update != command.Operator ||
292                                 !ForeignKeyValue.TryCreateTargetKey(fkConstraint, command.OriginalValues, true, out originalFK) ||
293                                 !_keyComparer.Equals(originalFK, fk))
294                             {
295                                 predecessors.Add(fk, command);
296                             }
297                         }
298                     }
299                 }
300 
301                 // register all source predecessors
302                 if (ModificationOperator.Update == command.Operator ||
303                     ModificationOperator.Delete == command.Operator)
304                 {
305                     foreach (ReferentialConstraint fkConstraint in _sourceMap.EnumerateValues(command.Table))
306                     {
307                         ForeignKeyValue fk;
308                         if (ForeignKeyValue.TryCreateSourceKey(fkConstraint, command.OriginalValues, false, out fk))
309                         {
310                             // if this is an update and the source key is unchanged, there is no
311                             // need to add a dependency (from the perspective of the source, the update
312                             // is a no-op)
313                             ForeignKeyValue currentFK;
314                             if (ModificationOperator.Update != command.Operator ||
315                                 !ForeignKeyValue.TryCreateSourceKey(fkConstraint, command.CurrentValues, false, out currentFK) ||
316                                 !_keyComparer.Equals(currentFK, fk))
317                             {
318                                 predecessors.Add(fk, command);
319                             }
320                         }
321                     }
322                 }
323             }
324             return predecessors;
325         }
326 
327         /// <summary>
328         /// For function commands, we infer constraints based on relationships and entities. For instance,
329         /// we always insert an entity before inserting a relationship referencing that entity. When dynamic
330         /// and function UpdateCommands are mixed, we also fall back on this same interpretation.
331         /// </summary>
AddModelDependencies()332         private void AddModelDependencies()
333         {
334             KeyToListMap<EntityKey, UpdateCommand> addedEntities = new KeyToListMap<EntityKey, UpdateCommand>(EqualityComparer<EntityKey>.Default);
335             KeyToListMap<EntityKey, UpdateCommand> deletedEntities = new KeyToListMap<EntityKey, UpdateCommand>(EqualityComparer<EntityKey>.Default);
336             KeyToListMap<EntityKey, UpdateCommand> addedRelationships = new KeyToListMap<EntityKey, UpdateCommand>(EqualityComparer<EntityKey>.Default);
337             KeyToListMap<EntityKey, UpdateCommand> deletedRelationships = new KeyToListMap<EntityKey, UpdateCommand>(EqualityComparer<EntityKey>.Default);
338 
339             foreach (UpdateCommand command in this.Vertices)
340             {
341                 command.GetRequiredAndProducedEntities(_translator, addedEntities, deletedEntities, addedRelationships, deletedRelationships);
342             }
343 
344             // Add entities before adding dependent relationships
345             AddModelDependencies(producedMap: addedEntities, requiredMap: addedRelationships);
346 
347             // Delete dependent relationships before deleting entities
348             AddModelDependencies(producedMap: deletedRelationships, requiredMap: deletedEntities);
349         }
350 
AddModelDependencies(KeyToListMap<EntityKey, UpdateCommand> producedMap, KeyToListMap<EntityKey, UpdateCommand> requiredMap)351         private void AddModelDependencies(KeyToListMap<EntityKey, UpdateCommand> producedMap, KeyToListMap<EntityKey, UpdateCommand> requiredMap)
352         {
353             foreach (var keyAndCommands in requiredMap.KeyValuePairs)
354             {
355                 EntityKey key = keyAndCommands.Key;
356                 List<UpdateCommand> commandsRequiringKey = keyAndCommands.Value;
357 
358                 foreach (UpdateCommand commandProducingKey in producedMap.EnumerateValues(key))
359                 {
360                     foreach (UpdateCommand commandRequiringKey in commandsRequiringKey)
361                     {
362                         // command cannot depend on itself and only function commands
363                         // need to worry about model dependencies (dynamic commands know about foreign keys)
364                         if (!object.ReferenceEquals(commandProducingKey, commandRequiringKey) &&
365                             (commandProducingKey.Kind == UpdateCommandKind.Function ||
366                              commandRequiringKey.Kind == UpdateCommandKind.Function))
367                         {
368                             // add a dependency
369                             AddEdge(commandProducingKey, commandRequiringKey);
370                         }
371                     }
372                 }
373             }
374         }
375 
376         /// <summary>
377         /// Describes an update command's foreign key (source or target)
378         /// </summary>
379         private struct ForeignKeyValue
380         {
381             /// <summary>
382             /// Constructor
383             /// </summary>
384             /// <param name="metadata">Sets Metadata</param>
385             /// <param name="record">Record containing key value</param>
386             /// <param name="isTarget">Indicates whether the source or target end of the constraint
387             /// is being pulled</param>
388             /// <param name="isInsert">Indicates whether this is an insert dependency or a delete
389             /// dependency</param>
ForeignKeyValueSystem.Data.Mapping.Update.Internal.UpdateCommandOrderer.ForeignKeyValue390             private ForeignKeyValue(ReferentialConstraint metadata, PropagatorResult record,
391                 bool isTarget, bool isInsert)
392             {
393                 Metadata = metadata;
394 
395                 // construct key
396                 IList<EdmProperty> keyProperties = isTarget ? metadata.FromProperties :
397                     metadata.ToProperties;
398                 PropagatorResult[] keyValues = new PropagatorResult[keyProperties.Count];
399                 bool hasNullMember = false;
400                 for (int i = 0; i < keyValues.Length; i++)
401                 {
402                     keyValues[i] = record.GetMemberValue(keyProperties[i]);
403                     if (keyValues[i].IsNull)
404                     {
405                         hasNullMember = true;
406                         break;
407                     }
408                 }
409 
410                 if (hasNullMember)
411                 {
412                     // set key to null to indicate that it is not behaving as a key
413                     // (in SQL, keys with null parts do not participate in constraints)
414                     Key = null;
415                 }
416                 else
417                 {
418                     Key = new CompositeKey(keyValues);
419                 }
420 
421                 IsInsert = isInsert;
422             }
423 
424             /// <summary>
425             /// Initialize foreign key object for the target of a foreign key.
426             /// </summary>
427             /// <param name="metadata">Sets Metadata</param>
428             /// <param name="record">Record containing key value</param>
429             /// <param name="isInsert">Indicates whether the key value is being inserted or deleted</param>
430             /// <param name="key">Outputs key object</param>
431             /// <returns>true if the record contains key values for this constraint; false otherwise</returns>
TryCreateTargetKeySystem.Data.Mapping.Update.Internal.UpdateCommandOrderer.ForeignKeyValue432             internal static bool TryCreateTargetKey(ReferentialConstraint metadata, PropagatorResult record, bool isInsert, out ForeignKeyValue key)
433             {
434                 key = new ForeignKeyValue(metadata, record, true, isInsert);
435                 if (null == key.Key)
436                 {
437                     return false;
438                 }
439                 return true;
440             }
441 
442             /// <summary>
443             /// Initialize foreign key object for the source of a foreign key.
444             /// </summary>
445             /// <param name="metadata">Sets Metadata</param>
446             /// <param name="record">Record containing key value</param>
447             /// <param name="isInsert">Indicates whether the key value is being inserted or deleted</param>
448             /// <param name="key">Outputs key object</param>
449             /// <returns>true if the record contains key values for this constraint; false otherwise</returns>
TryCreateSourceKeySystem.Data.Mapping.Update.Internal.UpdateCommandOrderer.ForeignKeyValue450             internal static bool TryCreateSourceKey(ReferentialConstraint metadata, PropagatorResult record, bool isInsert, out ForeignKeyValue key)
451             {
452                 key = new ForeignKeyValue(metadata, record, false, isInsert);
453                 if (null == key.Key)
454                 {
455                     return false;
456                 }
457                 return true;
458             }
459 
460             /// <summary>
461             /// Foreign key metadata.
462             /// </summary>
463             internal readonly ReferentialConstraint Metadata;
464 
465             /// <summary>
466             /// Foreign key value.
467             /// </summary>
468             internal readonly CompositeKey Key;
469 
470             /// <summary>
471             /// Indicates whether this is an inserted or deleted key value.
472             /// </summary>
473             internal readonly bool IsInsert;
474         }
475 
476         /// <summary>
477         /// Equality comparer for ForeignKey class.
478         /// </summary>
479         private class ForeignKeyValueComparer : IEqualityComparer<ForeignKeyValue>
480         {
481             private readonly IEqualityComparer<CompositeKey> _baseComparer;
482 
ForeignKeyValueComparer(IEqualityComparer<CompositeKey> baseComparer)483             internal ForeignKeyValueComparer(IEqualityComparer<CompositeKey> baseComparer)
484             {
485                 _baseComparer = EntityUtil.CheckArgumentNull(baseComparer, "baseComparer");
486             }
487 
Equals(ForeignKeyValue x, ForeignKeyValue y)488             public bool Equals(ForeignKeyValue x, ForeignKeyValue y)
489             {
490                 return x.IsInsert == y.IsInsert && x.Metadata == y.Metadata &&
491                     _baseComparer.Equals(x.Key, y.Key);
492             }
493 
GetHashCode(ForeignKeyValue obj)494             public int GetHashCode(ForeignKeyValue obj)
495             {
496                 return _baseComparer.GetHashCode(obj.Key);
497             }
498         }
499     }
500 }
501