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