1 //--------------------------------------------------------------------- 2 // <copyright file="UpdateTranslator.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 // 6 // @owner Microsoft 7 // @backupOwner Microsoft 8 //--------------------------------------------------------------------- 9 10 using System.Collections.Generic; 11 using System.Data.Objects; 12 using System.Data.Common.Utils; 13 using System.Data.Common.CommandTrees; 14 using System.Data.Common; 15 using System.Threading; 16 using System.Collections.ObjectModel; 17 using System.Diagnostics; 18 using System.Data.Metadata.Edm; 19 using System.Data.EntityClient; 20 using System.Data.Spatial; 21 using System.Globalization; 22 using System.Data.Entity; 23 using System.Linq; 24 25 namespace System.Data.Mapping.Update.Internal 26 { 27 /// <summary> 28 /// This class performs to following tasks to persist C-Space changes to the store: 29 /// <list> 30 /// <item>Extract changes from the entity state manager</item> 31 /// <item>Group changes by C-Space extent</item> 32 /// <item>For each affected S-Space table, perform propagation (get changes in S-Space terms)</item> 33 /// <item>Merge S-Space inserts and deletes into updates where appropriate</item> 34 /// <item>Produce S-Space commands implementating the modifications (insert, delete and update SQL statements)</item> 35 /// </list> 36 /// </summary> 37 internal partial class UpdateTranslator 38 { 39 #region Constructors 40 /// <summary> 41 /// Constructs a grouper based on the contents of the given entity state manager. 42 /// </summary> 43 /// <param name="stateManager">Entity state manager containing changes to be processed.</param> 44 /// <param name="metadataWorkspace">Metadata workspace.</param> 45 /// <param name="connection">Map connection</param> 46 /// <param name="commandTimeout">Timeout for update commands; null means 'use provider default'</param> UpdateTranslator(IEntityStateManager stateManager, MetadataWorkspace metadataWorkspace, EntityConnection connection, int? commandTimeout)47 private UpdateTranslator(IEntityStateManager stateManager, MetadataWorkspace metadataWorkspace, EntityConnection connection, int? commandTimeout) 48 { 49 EntityUtil.CheckArgumentNull(stateManager, "stateManager"); 50 EntityUtil.CheckArgumentNull(metadataWorkspace, "metadataWorkspace"); 51 EntityUtil.CheckArgumentNull(connection, "connection"); 52 53 // propagation state 54 m_changes = new Dictionary<EntitySetBase, ChangeNode>(); 55 m_functionChanges = new Dictionary<EntitySetBase, List<ExtractedStateEntry>>(); 56 m_stateEntries = new List<IEntityStateEntry>(); 57 m_knownEntityKeys = new Set<EntityKey>(); 58 m_requiredEntities = new Dictionary<EntityKey, AssociationSet>(); 59 m_optionalEntities = new Set<EntityKey>(); 60 m_includedValueEntities = new Set<EntityKey>(); 61 62 // workspace state 63 m_metadataWorkspace = metadataWorkspace; 64 m_viewLoader = metadataWorkspace.GetUpdateViewLoader(); 65 m_stateManager = stateManager; 66 67 // ancillary propagation services 68 m_recordConverter = new RecordConverter(this); 69 m_constraintValidator = new RelationshipConstraintValidator(this); 70 71 m_providerServices = DbProviderServices.GetProviderServices(connection.StoreProviderFactory); 72 m_connection = connection; 73 m_commandTimeout = commandTimeout; 74 75 // metadata cache 76 m_extractorMetadata = new Dictionary<Tuple<EntitySetBase, StructuralType>, ExtractorMetadata>(); ; 77 78 // key management 79 KeyManager = new KeyManager(this); 80 KeyComparer = CompositeKey.CreateComparer(KeyManager); 81 } 82 83 #endregion 84 85 #region Fields 86 // propagation state 87 private readonly Dictionary<EntitySetBase, ChangeNode> m_changes; 88 private readonly Dictionary<EntitySetBase, List<ExtractedStateEntry>> m_functionChanges; 89 private readonly List<IEntityStateEntry> m_stateEntries; 90 private readonly Set<EntityKey> m_knownEntityKeys; 91 private readonly Dictionary<EntityKey, AssociationSet> m_requiredEntities; 92 private readonly Set<EntityKey> m_optionalEntities; 93 private readonly Set<EntityKey> m_includedValueEntities; 94 95 // workspace state 96 private readonly MetadataWorkspace m_metadataWorkspace; 97 private readonly ViewLoader m_viewLoader; 98 private readonly IEntityStateManager m_stateManager; 99 100 // ancillary propagation services 101 private readonly RecordConverter m_recordConverter; 102 private readonly RelationshipConstraintValidator m_constraintValidator; 103 104 // provider information 105 private readonly DbProviderServices m_providerServices; 106 private readonly EntityConnection m_connection; 107 private readonly int? m_commandTimeout; 108 private Dictionary<StorageModificationFunctionMapping, DbCommandDefinition> m_modificationFunctionCommandDefinitions; 109 110 // metadata cache 111 private readonly Dictionary<Tuple<EntitySetBase, StructuralType>, ExtractorMetadata> m_extractorMetadata; 112 113 // static members 114 private static readonly List<string> s_emptyMemberList = new List<string>(); 115 #endregion 116 117 #region Properties 118 /// <summary> 119 /// Gets workspace used in this session. 120 /// </summary> 121 internal MetadataWorkspace MetadataWorkspace 122 { 123 get { return m_metadataWorkspace; } 124 } 125 126 /// <summary> 127 /// Gets key manager that handles interpretation of keys (including resolution of 128 /// referential-integrity/foreign key constraints) 129 /// </summary> 130 internal readonly KeyManager KeyManager; 131 132 /// <summary> 133 /// Gets the view loader metadata wrapper for the current workspace. 134 /// </summary> 135 internal ViewLoader ViewLoader 136 { 137 get { return m_viewLoader; } 138 } 139 140 /// <summary> 141 /// Gets record converter which translates state entry records into propagator results. 142 /// </summary> 143 internal RecordConverter RecordConverter 144 { 145 get { return m_recordConverter; } 146 } 147 148 /// <summary> 149 /// Gets command timeout for update commands. If null, use default. 150 /// </summary> 151 internal int? CommandTimeout 152 { 153 get { return m_commandTimeout; } 154 } 155 156 internal readonly IEqualityComparer<CompositeKey> KeyComparer; 157 #endregion 158 159 #region Methods 160 /// <summary> 161 /// Registers any referential constraints contained in the state entry (so that 162 /// constrained members have the same identifier values). Only processes relationships 163 /// with referential constraints defined. 164 /// </summary> 165 /// <param name="stateEntry">State entry</param> RegisterReferentialConstraints(IEntityStateEntry stateEntry)166 internal void RegisterReferentialConstraints(IEntityStateEntry stateEntry) 167 { 168 if (stateEntry.IsRelationship) 169 { 170 AssociationSet associationSet = (AssociationSet)stateEntry.EntitySet; 171 if (0 < associationSet.ElementType.ReferentialConstraints.Count) 172 { 173 DbDataRecord record = stateEntry.State == EntityState.Added ? 174 (DbDataRecord)stateEntry.CurrentValues : stateEntry.OriginalValues; 175 foreach (ReferentialConstraint constraint in associationSet.ElementType.ReferentialConstraints) 176 { 177 // retrieve keys at the ends 178 EntityKey principalKey = (EntityKey)record[constraint.FromRole.Name]; 179 EntityKey dependentKey = (EntityKey)record[constraint.ToRole.Name]; 180 181 // associate keys, where the from side 'owns' the to side 182 using (ReadOnlyMetadataCollection<EdmProperty>.Enumerator principalPropertyEnum = constraint.FromProperties.GetEnumerator()) 183 using (ReadOnlyMetadataCollection<EdmProperty>.Enumerator dependentPropertyEnum = constraint.ToProperties.GetEnumerator()) 184 { 185 while (principalPropertyEnum.MoveNext() && dependentPropertyEnum.MoveNext()) 186 { 187 int principalKeyMemberCount; 188 int dependentKeyMemberCount; 189 190 // get offsets for from and to key properties 191 int principalOffset = GetKeyMemberOffset(constraint.FromRole, principalPropertyEnum.Current, 192 out principalKeyMemberCount); 193 int dependentOffset = GetKeyMemberOffset(constraint.ToRole, dependentPropertyEnum.Current, 194 out dependentKeyMemberCount); 195 196 int principalIdentifier = this.KeyManager.GetKeyIdentifierForMemberOffset(principalKey, principalOffset, principalKeyMemberCount); 197 int dependentIdentifier = this.KeyManager.GetKeyIdentifierForMemberOffset(dependentKey, dependentOffset, dependentKeyMemberCount); 198 199 // register equivalence of identifiers 200 this.KeyManager.AddReferentialConstraint(stateEntry, dependentIdentifier, principalIdentifier); 201 } 202 } 203 } 204 } 205 } 206 else if (!stateEntry.IsKeyEntry) 207 { 208 if (stateEntry.State == EntityState.Added || stateEntry.State == EntityState.Modified) 209 { 210 RegisterEntityReferentialConstraints(stateEntry, true); 211 } 212 if (stateEntry.State == EntityState.Deleted || stateEntry.State == EntityState.Modified) 213 { 214 RegisterEntityReferentialConstraints(stateEntry, false); 215 } 216 } 217 } 218 RegisterEntityReferentialConstraints(IEntityStateEntry stateEntry, bool currentValues)219 private void RegisterEntityReferentialConstraints(IEntityStateEntry stateEntry, bool currentValues) 220 { 221 IExtendedDataRecord record = currentValues 222 ? (IExtendedDataRecord)stateEntry.CurrentValues 223 : (IExtendedDataRecord)stateEntry.OriginalValues; 224 EntitySet entitySet = (EntitySet)stateEntry.EntitySet; 225 EntityKey dependentKey = stateEntry.EntityKey; 226 227 foreach (var foreignKey in entitySet.ForeignKeyDependents) 228 { 229 AssociationSet associationSet = foreignKey.Item1; 230 ReferentialConstraint constraint = foreignKey.Item2; 231 EntityType dependentType = MetadataHelper.GetEntityTypeForEnd((AssociationEndMember)constraint.ToRole); 232 if (dependentType.IsAssignableFrom(record.DataRecordInfo.RecordType.EdmType)) 233 { 234 EntityKey principalKey = null; 235 236 // First, check for an explicit reference 237 if (!currentValues || !m_stateManager.TryGetReferenceKey(dependentKey, (AssociationEndMember)constraint.FromRole, out principalKey)) 238 { 239 // build a key based on the foreign key values 240 EntityType principalType = MetadataHelper.GetEntityTypeForEnd((AssociationEndMember)constraint.FromRole); 241 bool hasNullValue = false; 242 object[] keyValues = new object[principalType.KeyMembers.Count]; 243 for (int i = 0, n = keyValues.Length; i < n; i++) 244 { 245 EdmProperty keyMember = (EdmProperty)principalType.KeyMembers[i]; 246 247 // Find corresponding foreign key value 248 int constraintOrdinal = constraint.FromProperties.IndexOf((EdmProperty)keyMember); 249 int recordOrdinal = record.GetOrdinal(constraint.ToProperties[constraintOrdinal].Name); 250 if (record.IsDBNull(recordOrdinal)) 251 { 252 hasNullValue = true; 253 break; 254 } 255 keyValues[i] = record.GetValue(recordOrdinal); 256 } 257 258 if (!hasNullValue) 259 { 260 EntitySet principalSet = associationSet.AssociationSetEnds[constraint.FromRole.Name].EntitySet; 261 if (1 == keyValues.Length) 262 { 263 principalKey = new EntityKey(principalSet, keyValues[0]); 264 } 265 else 266 { 267 principalKey = new EntityKey(principalSet, keyValues); 268 } 269 } 270 } 271 272 if (null != principalKey) 273 { 274 // find the right principal key... (first, existing entities; then, added entities; finally, just the key) 275 IEntityStateEntry existingPrincipal; 276 EntityKey tempKey; 277 if (m_stateManager.TryGetEntityStateEntry(principalKey, out existingPrincipal)) 278 { 279 // nothing to do. the principal key will resolve to the existing entity 280 } 281 else if (currentValues && this.KeyManager.TryGetTempKey(principalKey, out tempKey)) 282 { 283 // if we aren't dealing with current values, we cannot resolve to a temp key (original values 284 // cannot indicate a relationship to an 'added' entity). 285 if (null == tempKey) 286 { 287 throw EntityUtil.Update(Strings.Update_AmbiguousForeignKey(constraint.ToRole.DeclaringType.FullName), null, stateEntry); 288 } 289 else 290 { 291 principalKey = tempKey; 292 } 293 } 294 295 // pull the principal end into the update pipeline (supports value propagation) 296 AddValidAncillaryKey(principalKey, m_optionalEntities); 297 298 // associate keys, where the from side 'owns' the to side 299 for (int i = 0, n = constraint.FromProperties.Count; i < n; i++) 300 { 301 var principalProperty = constraint.FromProperties[i]; 302 var dependentProperty = constraint.ToProperties[i]; 303 304 int principalKeyMemberCount; 305 306 // get offsets for from and to key properties 307 int principalOffset = GetKeyMemberOffset(constraint.FromRole, principalProperty, out principalKeyMemberCount); 308 int principalIdentifier = this.KeyManager.GetKeyIdentifierForMemberOffset(principalKey, principalOffset, principalKeyMemberCount); 309 int dependentIdentifier; 310 311 if (entitySet.ElementType.KeyMembers.Contains(dependentProperty)) 312 { 313 int dependentKeyMemberCount; 314 int dependentOffset = GetKeyMemberOffset(constraint.ToRole, dependentProperty, 315 out dependentKeyMemberCount); 316 dependentIdentifier = this.KeyManager.GetKeyIdentifierForMemberOffset(dependentKey, dependentOffset, dependentKeyMemberCount); 317 } 318 else 319 { 320 dependentIdentifier = this.KeyManager.GetKeyIdentifierForMember(dependentKey, dependentProperty.Name, currentValues); 321 } 322 323 // don't allow the user to insert or update an entity that refers to a deleted principal 324 if (currentValues && null != existingPrincipal && existingPrincipal.State == EntityState.Deleted && 325 (stateEntry.State == EntityState.Added || stateEntry.State == EntityState.Modified)) 326 { 327 throw EntityUtil.Update( 328 Strings.Update_InsertingOrUpdatingReferenceToDeletedEntity(associationSet.ElementType.FullName), 329 null, 330 stateEntry, 331 existingPrincipal); 332 } 333 334 // register equivalence of identifiers 335 this.KeyManager.AddReferentialConstraint(stateEntry, dependentIdentifier, principalIdentifier); 336 } 337 } 338 } 339 } 340 } 341 342 // requires: role must not be null and property must be a key member for the role end GetKeyMemberOffset(RelationshipEndMember role, EdmProperty property, out int keyMemberCount)343 private static int GetKeyMemberOffset(RelationshipEndMember role, EdmProperty property, out int keyMemberCount) 344 { 345 Debug.Assert(null != role); 346 Debug.Assert(null != property); 347 Debug.Assert(BuiltInTypeKind.RefType == role.TypeUsage.EdmType.BuiltInTypeKind, 348 "relationship ends must be of RefType"); 349 RefType endType = (RefType)role.TypeUsage.EdmType; 350 Debug.Assert(BuiltInTypeKind.EntityType == endType.ElementType.BuiltInTypeKind, 351 "relationship ends must reference EntityType"); 352 EntityType entityType = (EntityType)endType.ElementType; 353 keyMemberCount = entityType.KeyMembers.Count; 354 return entityType.KeyMembers.IndexOf(property); 355 } 356 357 /// <summary> 358 /// Yields all relationship state entries with the given key as an end. 359 /// </summary> 360 /// <param name="entityKey"></param> 361 /// <returns></returns> GetRelationships(EntityKey entityKey)362 internal IEnumerable<IEntityStateEntry> GetRelationships(EntityKey entityKey) 363 { 364 return m_stateManager.FindRelationshipsByKey(entityKey); 365 } 366 367 /// <summary> 368 /// Persists stateManager changes to the store. 369 /// </summary> 370 /// <param name="stateManager">StateManager containing changes to persist.</param> 371 /// <param name="adapter">Map adapter requesting the changes.</param> 372 /// <returns>Total number of state entries affected</returns> Update(IEntityStateManager stateManager, IEntityAdapter adapter)373 internal static Int32 Update(IEntityStateManager stateManager, IEntityAdapter adapter) 374 { 375 // provider/connection details 376 EntityConnection connection = (EntityConnection)adapter.Connection; 377 MetadataWorkspace metadataWorkspace = connection.GetMetadataWorkspace(); 378 int? commandTimeout = adapter.CommandTimeout; 379 380 UpdateTranslator translator = new UpdateTranslator(stateManager, metadataWorkspace, connection, commandTimeout); 381 382 // tracks values for identifiers in this session 383 Dictionary<int, object> identifierValues = new Dictionary<int, object>(); 384 385 // tracks values for generated values in this session 386 List<KeyValuePair<PropagatorResult, object>> generatedValues = new List<KeyValuePair<PropagatorResult, object>>(); 387 388 IEnumerable<UpdateCommand> orderedCommands = translator.ProduceCommands(); 389 390 // used to track the source of commands being processed in case an exception is thrown 391 UpdateCommand source = null; 392 try 393 { 394 foreach (UpdateCommand command in orderedCommands) 395 { 396 // Remember the data sources so that we can throw meaningful exception 397 source = command; 398 long rowsAffected = command.Execute(translator, connection, identifierValues, generatedValues); 399 translator.ValidateRowsAffected(rowsAffected, source); 400 } 401 } 402 catch (Exception e) 403 { 404 // we should not be wrapping all exceptions 405 if (UpdateTranslator.RequiresContext(e)) 406 { 407 throw EntityUtil.Update(System.Data.Entity.Strings.Update_GeneralExecutionException, e, translator.DetermineStateEntriesFromSource(source)); 408 } 409 throw; 410 } 411 412 translator.BackPropagateServerGen(generatedValues); 413 414 int totalStateEntries = translator.AcceptChanges(adapter); 415 416 return totalStateEntries; 417 } 418 ProduceCommands()419 private IEnumerable<UpdateCommand> ProduceCommands() 420 { 421 // load all modified state entries 422 PullModifiedEntriesFromStateManager(); 423 PullUnchangedEntriesFromStateManager(); 424 425 // check constraints 426 m_constraintValidator.ValidateConstraints(); 427 this.KeyManager.ValidateReferentialIntegrityGraphAcyclic(); 428 429 // gather all commands (aggregate in a dependency orderer to determine operation order 430 IEnumerable<UpdateCommand> dynamicCommands = this.ProduceDynamicCommands(); 431 IEnumerable<UpdateCommand> functionCommands = this.ProduceFunctionCommands(); 432 UpdateCommandOrderer orderer = new UpdateCommandOrderer(dynamicCommands.Concat(functionCommands), this); 433 IEnumerable<UpdateCommand> orderedCommands; 434 IEnumerable<UpdateCommand> remainder; 435 if (!orderer.TryTopologicalSort(out orderedCommands, out remainder)) 436 { 437 // throw an exception if it is not possible to perform dependency ordering 438 throw DependencyOrderingError(remainder); 439 } 440 441 return orderedCommands; 442 } 443 444 // effects: given rows affected, throws if the count suggests a concurrency failure. 445 // Throws a concurrency exception based on the current command sources (which allow 446 // us to populated the EntityStateEntries on UpdateException) ValidateRowsAffected(long rowsAffected, UpdateCommand source)447 private void ValidateRowsAffected(long rowsAffected, UpdateCommand source) 448 { 449 // 0 rows affected indicates a concurrency failure; negative values suggest rowcount is off; 450 // positive values suggest at least one row was affected (we generally expect exactly one, 451 // but triggers/view logic/logging may change this value) 452 if (0 == rowsAffected) 453 { 454 var stateEntries = DetermineStateEntriesFromSource(source); 455 throw EntityUtil.UpdateConcurrency(rowsAffected, null, stateEntries); 456 } 457 } 458 DetermineStateEntriesFromSource(UpdateCommand source)459 private IEnumerable<IEntityStateEntry> DetermineStateEntriesFromSource(UpdateCommand source) 460 { 461 if (null == source) 462 { 463 return Enumerable.Empty<IEntityStateEntry>(); 464 } 465 return source.GetStateEntries(this); 466 } 467 468 // effects: Given a list of pairs describing the contexts for server generated values and their actual 469 // values, backpropagates to the relevant state entries BackPropagateServerGen(List<KeyValuePair<PropagatorResult, object>> generatedValues)470 private void BackPropagateServerGen(List<KeyValuePair<PropagatorResult, object>> generatedValues) 471 { 472 foreach (KeyValuePair<PropagatorResult, object> generatedValue in generatedValues) 473 { 474 PropagatorResult context; 475 476 // check if a redirect to "owner" result is possible 477 if (PropagatorResult.NullIdentifier == generatedValue.Key.Identifier || 478 !KeyManager.TryGetIdentifierOwner(generatedValue.Key.Identifier, out context)) 479 { 480 // otherwise, just use the straightforward context 481 context = generatedValue.Key; 482 } 483 484 object value = generatedValue.Value; 485 if (context.Identifier == PropagatorResult.NullIdentifier) 486 { 487 SetServerGenValue(context, value); 488 } 489 else 490 { 491 // check if we need to back propagate this value to any other positions (e.g. for foreign keys) 492 foreach (int dependent in this.KeyManager.GetDependents(context.Identifier)) 493 { 494 if (this.KeyManager.TryGetIdentifierOwner(dependent, out context)) 495 { 496 SetServerGenValue(context, value); 497 } 498 } 499 } 500 } 501 } 502 SetServerGenValue(PropagatorResult context, object value)503 private void SetServerGenValue(PropagatorResult context, object value) 504 { 505 if (context.RecordOrdinal != PropagatorResult.NullOrdinal) 506 { 507 CurrentValueRecord targetRecord = context.Record; 508 509 // determine if type compensation is required 510 IExtendedDataRecord recordWithMetadata = (IExtendedDataRecord)targetRecord; 511 EdmMember member = recordWithMetadata.DataRecordInfo.FieldMetadata[context.RecordOrdinal].FieldType; 512 513 value = value ?? DBNull.Value; // records expect DBNull rather than null 514 value = AlignReturnValue(value, member, context); 515 targetRecord.SetValue(context.RecordOrdinal, value); 516 } 517 } 518 519 /// <summary> 520 /// Aligns a value returned from the store with the expected type for the member. 521 /// </summary> 522 /// <param name="value">Value to convert.</param> 523 /// <param name="member">Metadata for the member being set.</param> 524 /// <param name="context">The context generating the return value.</param> 525 /// <returns>Converted return value</returns> AlignReturnValue(object value, EdmMember member, PropagatorResult context)526 private object AlignReturnValue(object value, EdmMember member, PropagatorResult context) 527 { 528 if (DBNull.Value.Equals(value)) 529 { 530 // check if there is a nullability constraint on the value 531 if (BuiltInTypeKind.EdmProperty == member.BuiltInTypeKind && 532 !((EdmProperty)member).Nullable) 533 { 534 throw EntityUtil.Update(System.Data.Entity.Strings.Update_NullReturnValueForNonNullableMember( 535 member.Name, 536 member.DeclaringType.FullName), null); 537 } 538 } 539 else if (!Helper.IsSpatialType(member.TypeUsage)) 540 { 541 Type clrType; 542 Type clrEnumType = null; 543 if (Helper.IsEnumType(member.TypeUsage.EdmType)) 544 { 545 PrimitiveType underlyingType = Helper.AsPrimitive(member.TypeUsage.EdmType); 546 clrEnumType = context.Record.GetFieldType(context.RecordOrdinal); 547 clrType = underlyingType.ClrEquivalentType; 548 Debug.Assert(clrEnumType.IsEnum); 549 } 550 else 551 { 552 // convert the value to the appropriate CLR type 553 Debug.Assert(BuiltInTypeKind.PrimitiveType == member.TypeUsage.EdmType.BuiltInTypeKind, 554 "we only allow return values that are instances of EDM primitive or enum types"); 555 PrimitiveType primitiveType = (PrimitiveType)member.TypeUsage.EdmType; 556 clrType = primitiveType.ClrEquivalentType; 557 } 558 559 try 560 { 561 value = Convert.ChangeType(value, clrType, CultureInfo.InvariantCulture); 562 if (clrEnumType != null) 563 { 564 value = Enum.ToObject(clrEnumType, value); 565 } 566 } 567 catch (Exception e) 568 { 569 // we should not be wrapping all exceptions 570 if (UpdateTranslator.RequiresContext(e)) 571 { 572 Type userClrType = clrEnumType ?? clrType; 573 throw EntityUtil.Update(System.Data.Entity.Strings.Update_ReturnValueHasUnexpectedType( 574 value.GetType().FullName, 575 userClrType.FullName, 576 member.Name, 577 member.DeclaringType.FullName), e); 578 } 579 throw; 580 } 581 } 582 583 // return the adjusted value 584 return value; 585 } 586 587 /// <summary> 588 /// Accept changes to entities and relationships processed by this translator instance. 589 /// </summary> 590 /// <param name="adapter">Data adapter</param> 591 /// <returns>Number of state entries affected.</returns> AcceptChanges(IEntityAdapter adapter)592 private int AcceptChanges(IEntityAdapter adapter) 593 { 594 int affectedCount = 0; 595 foreach (IEntityStateEntry stateEntry in m_stateEntries) 596 { 597 // only count and accept changes for state entries that are being explicitly modified 598 if (EntityState.Unchanged != stateEntry.State) 599 { 600 if (adapter.AcceptChangesDuringUpdate) 601 { 602 stateEntry.AcceptChanges(); 603 } 604 affectedCount++; 605 } 606 } 607 return affectedCount; 608 } 609 610 /// <summary> 611 /// Gets extents for which this translator has identified changes to be handled 612 /// by the standard update pipeline. 613 /// </summary> 614 /// <returns>Enumeration of modified C-Space extents.</returns> GetDynamicModifiedExtents()615 private IEnumerable<EntitySetBase> GetDynamicModifiedExtents() 616 { 617 return m_changes.Keys; 618 } 619 620 /// <summary> 621 /// Gets extents for which this translator has identified changes to be handled 622 /// by function mappings. 623 /// </summary> 624 /// <returns>Enumreation of modified C-Space extents.</returns> GetFunctionModifiedExtents()625 private IEnumerable<EntitySetBase> GetFunctionModifiedExtents() 626 { 627 return m_functionChanges.Keys; 628 } 629 630 /// <summary> 631 /// Produce dynamic store commands for this translator's changes. 632 /// </summary> 633 /// <returns>Database commands in a safe order</returns> ProduceDynamicCommands()634 private IEnumerable<UpdateCommand> ProduceDynamicCommands() 635 { 636 // Initialize DBCommand update compiler 637 UpdateCompiler updateCompiler = new UpdateCompiler(this); 638 639 // Determine affected 640 Set<EntitySet> tables = new Set<EntitySet>(); 641 642 foreach (EntitySetBase extent in GetDynamicModifiedExtents()) 643 { 644 Set<EntitySet> affectedTables = m_viewLoader.GetAffectedTables(extent, m_metadataWorkspace); 645 //Since these extents don't have Functions defined for update operations, 646 //the affected tables should be provided via MSL. 647 //If we dont find any throw an exception 648 if (affectedTables.Count == 0) 649 { 650 throw EntityUtil.Update(System.Data.Entity.Strings.Update_MappingNotFound( 651 extent.Name), null /*stateEntries*/); 652 } 653 654 foreach (EntitySet table in affectedTables) 655 { 656 tables.Add(table); 657 } 658 } 659 660 // Determine changes to apply to each table 661 foreach (EntitySet table in tables) 662 { 663 DbQueryCommandTree umView = m_connection.GetMetadataWorkspace().GetCqtView(table); 664 665 // Propagate changes to root of tree (at which point they are S-Space changes) 666 ChangeNode changeNode = Propagator.Propagate(this, table, umView); 667 668 // Process changes for the table 669 TableChangeProcessor change = new TableChangeProcessor(table); 670 foreach (UpdateCommand command in change.CompileCommands(changeNode, updateCompiler)) 671 { 672 yield return command; 673 } 674 } 675 } 676 677 // Generates and caches a command definition for the given function GenerateCommandDefinition(StorageModificationFunctionMapping functionMapping)678 internal DbCommandDefinition GenerateCommandDefinition(StorageModificationFunctionMapping functionMapping) 679 { 680 if (null == m_modificationFunctionCommandDefinitions) 681 { 682 m_modificationFunctionCommandDefinitions = new Dictionary<StorageModificationFunctionMapping,DbCommandDefinition>(); 683 } 684 DbCommandDefinition commandDefinition; 685 if (!m_modificationFunctionCommandDefinitions.TryGetValue(functionMapping, out commandDefinition)) 686 { 687 // synthesize a RowType for this mapping 688 TypeUsage resultType = null; 689 if (null != functionMapping.ResultBindings && 0 < functionMapping.ResultBindings.Count) 690 { 691 List<EdmProperty> properties = new List<EdmProperty>(functionMapping.ResultBindings.Count); 692 foreach (StorageModificationFunctionResultBinding resultBinding in functionMapping.ResultBindings) 693 { 694 properties.Add(new EdmProperty(resultBinding.ColumnName, resultBinding.Property.TypeUsage)); 695 } 696 RowType rowType = new RowType(properties); 697 CollectionType collectionType = new CollectionType(rowType); 698 resultType = TypeUsage.Create(collectionType); 699 } 700 701 // add function parameters 702 IEnumerable<KeyValuePair<string, TypeUsage>> functionParams = 703 functionMapping.Function.Parameters.Select(paramInfo => new KeyValuePair<string, TypeUsage>(paramInfo.Name, paramInfo.TypeUsage)); 704 705 // construct DbFunctionCommandTree including implict return type 706 DbFunctionCommandTree tree = new DbFunctionCommandTree(m_metadataWorkspace, DataSpace.SSpace, 707 functionMapping.Function, resultType, functionParams); 708 709 commandDefinition = m_providerServices.CreateCommandDefinition(tree); 710 } 711 return commandDefinition; 712 } 713 714 // Produces all function commands in a safe order ProduceFunctionCommands()715 private IEnumerable<UpdateCommand> ProduceFunctionCommands() 716 { 717 foreach (EntitySetBase extent in GetFunctionModifiedExtents()) 718 { 719 // Get a handle on the appropriate translator 720 ModificationFunctionMappingTranslator translator = m_viewLoader.GetFunctionMappingTranslator(extent, m_metadataWorkspace); 721 722 if (null != translator) 723 { 724 // Compile commands 725 foreach (ExtractedStateEntry stateEntry in GetExtentFunctionModifications(extent)) 726 { 727 FunctionUpdateCommand command = translator.Translate(this, stateEntry); 728 if (null != command) 729 { 730 yield return command; 731 } 732 } 733 } 734 } 735 } 736 737 /// <summary> 738 /// Gets a metadata wrapper for the given type. The wrapper makes 739 /// certain tasks in the update pipeline more efficient. 740 /// </summary> 741 /// <param name="type">Structural type</param> 742 /// <returns>Metadata wrapper</returns> GetExtractorMetadata(EntitySetBase entitySetBase, StructuralType type)743 internal ExtractorMetadata GetExtractorMetadata(EntitySetBase entitySetBase, StructuralType type) 744 { 745 ExtractorMetadata metadata; 746 var key = Tuple.Create(entitySetBase, type); 747 if (!m_extractorMetadata.TryGetValue(key, out metadata)) 748 { 749 metadata = new ExtractorMetadata(entitySetBase, type, this); 750 m_extractorMetadata.Add(key, metadata); 751 } 752 return metadata; 753 } 754 755 /// <summary> 756 /// Returns error when it is not possible to order update commands. Argument is the 'remainder', or commands 757 /// that could not be ordered due to a cycle. 758 /// </summary> DependencyOrderingError(IEnumerable<UpdateCommand> remainder)759 private UpdateException DependencyOrderingError(IEnumerable<UpdateCommand> remainder) 760 { 761 Debug.Assert(null != remainder && remainder.Count() > 0, "must provide non-empty remainder"); 762 763 HashSet<IEntityStateEntry> stateEntries = new HashSet<IEntityStateEntry>(); 764 765 foreach (UpdateCommand command in remainder) 766 { 767 stateEntries.UnionWith(command.GetStateEntries(this)); 768 } 769 770 // throw exception containing all related state entries 771 throw EntityUtil.Update(System.Data.Entity.Strings.Update_ConstraintCycle, null, stateEntries); 772 } 773 774 /// <summary> 775 /// Creates a command in the current context. 776 /// </summary> 777 /// <param name="commandTree">DbCommand tree</param> 778 /// <returns>DbCommand produced by the current provider.</returns> CreateCommand(DbModificationCommandTree commandTree)779 internal DbCommand CreateCommand(DbModificationCommandTree commandTree) 780 { 781 DbCommand command; 782 Debug.Assert(null != m_providerServices, "constructor ensures either the command definition " + 783 "builder or provider service is available"); 784 Debug.Assert(null != m_connection.StoreConnection, "EntityAdapter.Update ensures the store connection is set"); 785 try 786 { 787 command = m_providerServices.CreateCommand(commandTree); 788 } 789 catch (Exception e) 790 { 791 // we should not be wrapping all exceptions 792 if (UpdateTranslator.RequiresContext(e)) 793 { 794 // we don't wan't folks to have to know all the various types of exceptions that can 795 // occur, so we just rethrow a CommandDefinitionException and make whatever we caught 796 // the inner exception of it. 797 throw EntityUtil.CommandCompilation(System.Data.Entity.Strings.EntityClient_CommandDefinitionPreparationFailed, e); 798 } 799 throw; 800 } 801 return command; 802 } 803 804 /// <summary> 805 /// Helper method to allow the setting of parameter values to update stored procedures. 806 /// Allows the DbProvider an opportunity to rewrite the parameter to suit provider specific needs. 807 /// </summary> 808 /// <param name="parameter">Parameter to set.</param> 809 /// <param name="typeUsage">The type of the parameter.</param> 810 /// <param name="value">The value to which to set the parameter.</param> SetParameterValue(DbParameter parameter, TypeUsage typeUsage, object value)811 internal void SetParameterValue(DbParameter parameter, TypeUsage typeUsage, object value) 812 { 813 m_providerServices.SetParameterValue(parameter, typeUsage, value); 814 } 815 816 /// <summary> 817 /// Determines whether the given exception requires additional context from the update pipeline (in other 818 /// words, whether the exception should be wrapped in an UpdateException). 819 /// </summary> 820 /// <param name="e">Exception to test.</param> 821 /// <returns>true if exception should be wrapped; false otherwise</returns> RequiresContext(Exception e)822 internal static bool RequiresContext(Exception e) 823 { 824 // if the exception isn't catchable, never wrap 825 if (!EntityUtil.IsCatchableExceptionType(e)) { return false; } 826 827 // update and incompatible provider exceptions already contain the necessary context 828 return !(e is UpdateException) && !(e is ProviderIncompatibleException); 829 } 830 831 #region Private initialization methods 832 /// <summary> 833 /// Retrieve all modified entries from the state manager. 834 /// </summary> PullModifiedEntriesFromStateManager()835 private void PullModifiedEntriesFromStateManager() 836 { 837 // do a first pass over added entries to register 'by value' entity key targets that may be resolved as 838 // via a foreign key 839 foreach (IEntityStateEntry addedEntry in m_stateManager.GetEntityStateEntries(EntityState.Added)) 840 { 841 if (!addedEntry.IsRelationship && !addedEntry.IsKeyEntry) 842 { 843 this.KeyManager.RegisterKeyValueForAddedEntity(addedEntry); 844 } 845 } 846 847 // do a second pass over entries to register referential integrity constraints 848 // for server-generation 849 foreach (IEntityStateEntry modifiedEntry in m_stateManager.GetEntityStateEntries(EntityState.Modified | EntityState.Added | EntityState.Deleted)) 850 { 851 RegisterReferentialConstraints(modifiedEntry); 852 } 853 854 foreach (IEntityStateEntry modifiedEntry in m_stateManager.GetEntityStateEntries(EntityState.Modified | EntityState.Added | EntityState.Deleted)) 855 { 856 LoadStateEntry(modifiedEntry); 857 } 858 } 859 860 861 /// <summary> 862 /// Retrieve all required/optional/value entries into the state manager. These are entries that -- 863 /// although unmodified -- affect or are affected by updates. 864 /// </summary> PullUnchangedEntriesFromStateManager()865 private void PullUnchangedEntriesFromStateManager() 866 { 867 foreach (KeyValuePair<EntityKey, AssociationSet> required in m_requiredEntities) 868 { 869 EntityKey key = required.Key; 870 871 if (!m_knownEntityKeys.Contains(key)) 872 { 873 // pull the value into the translator if we don't already it 874 IEntityStateEntry requiredEntry; 875 876 if (m_stateManager.TryGetEntityStateEntry(key, out requiredEntry) && !requiredEntry.IsKeyEntry) 877 { 878 // load the object as a no-op update 879 LoadStateEntry(requiredEntry); 880 } 881 else 882 { 883 // throw an exception 884 throw EntityUtil.UpdateMissingEntity(required.Value.Name, TypeHelpers.GetFullName(key.EntityContainerName, key.EntitySetName)); 885 } 886 } 887 } 888 889 foreach (EntityKey key in m_optionalEntities) 890 { 891 if (!m_knownEntityKeys.Contains(key)) 892 { 893 IEntityStateEntry optionalEntry; 894 895 if (m_stateManager.TryGetEntityStateEntry(key, out optionalEntry) && !optionalEntry.IsKeyEntry) 896 { 897 // load the object as a no-op update 898 LoadStateEntry(optionalEntry); 899 } 900 } 901 } 902 903 foreach (EntityKey key in m_includedValueEntities) 904 { 905 if (!m_knownEntityKeys.Contains(key)) 906 { 907 IEntityStateEntry valueEntry; 908 909 if (m_stateManager.TryGetEntityStateEntry(key, out valueEntry)) 910 { 911 // Convert state entry so that its values are known to the update pipeline. 912 var result = m_recordConverter.ConvertCurrentValuesToPropagatorResult(valueEntry, ModifiedPropertiesBehavior.NoneModified); 913 } 914 } 915 } 916 } 917 918 /// <summary> 919 /// Validates and tracks a state entry being processed by this translator. 920 /// </summary> 921 /// <param name="stateEntry"></param> ValidateAndRegisterStateEntry(IEntityStateEntry stateEntry)922 private void ValidateAndRegisterStateEntry(IEntityStateEntry stateEntry) 923 { 924 EntityUtil.CheckArgumentNull(stateEntry, "stateEntry"); 925 926 EntitySetBase extent = stateEntry.EntitySet; 927 if (null == extent) 928 { 929 throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.InvalidStateEntry, 1); 930 } 931 932 // Determine the key. May be null if the state entry does not represent an entity. 933 EntityKey entityKey = stateEntry.EntityKey; 934 IExtendedDataRecord record = null; 935 936 // verify the structure of the entry values 937 if (0 != ((EntityState.Added | EntityState.Modified | EntityState.Unchanged) & stateEntry.State)) 938 { 939 // added, modified and unchanged entries have current values 940 record = (IExtendedDataRecord)stateEntry.CurrentValues; 941 ValidateRecord(extent, record, stateEntry); 942 } 943 if (0 != ((EntityState.Modified | EntityState.Deleted | EntityState.Unchanged) & stateEntry.State)) 944 { 945 // deleted, modified and unchanged entries have original values 946 record = (IExtendedDataRecord)stateEntry.OriginalValues; 947 ValidateRecord(extent, record, stateEntry); 948 } 949 Debug.Assert(null != record, "every state entry must contain a record"); 950 951 // check for required ends of relationships 952 AssociationSet associationSet = extent as AssociationSet; 953 if (null != associationSet) 954 { 955 AssociationSetMetadata associationSetMetadata = m_viewLoader.GetAssociationSetMetadata(associationSet, m_metadataWorkspace); 956 957 if (associationSetMetadata.HasEnds) 958 { 959 foreach (FieldMetadata field in record.DataRecordInfo.FieldMetadata) 960 { 961 // ends of relationship record must be EntityKeys 962 EntityKey end = (EntityKey)record.GetValue(field.Ordinal); 963 964 // ends of relationships must have AssociationEndMember metadata 965 AssociationEndMember endMetadata = (AssociationEndMember)field.FieldType; 966 967 if (associationSetMetadata.RequiredEnds.Contains(endMetadata)) 968 { 969 if (!m_requiredEntities.ContainsKey(end)) 970 { 971 m_requiredEntities.Add(end, associationSet); 972 } 973 } 974 975 else if (associationSetMetadata.OptionalEnds.Contains(endMetadata)) 976 { 977 AddValidAncillaryKey(end, m_optionalEntities); 978 } 979 980 else if (associationSetMetadata.IncludedValueEnds.Contains(endMetadata)) 981 { 982 AddValidAncillaryKey(end, m_includedValueEntities); 983 } 984 } 985 } 986 987 // register relationship with validator 988 m_constraintValidator.RegisterAssociation(associationSet, record, stateEntry); 989 } 990 else 991 { 992 // register entity with validator 993 m_constraintValidator.RegisterEntity(stateEntry); 994 } 995 996 // add to the list of entries being tracked 997 m_stateEntries.Add(stateEntry); 998 if (null != (object)entityKey) { m_knownEntityKeys.Add(entityKey); } 999 } 1000 1001 /// <summary> 1002 /// effects: given an entity key and a set, adds key to the set iff. the corresponding entity 1003 /// is: 1004 /// 1005 /// not a stub (or 'key') entry, and; 1006 /// not a core element in the update pipeline (it's not being directly modified) 1007 /// </summary> AddValidAncillaryKey(EntityKey key, Set<EntityKey> keySet)1008 private void AddValidAncillaryKey(EntityKey key, Set<EntityKey> keySet) 1009 { 1010 // Note: an entity is ancillary iff. it is unchanged (otherwise it is tracked as a "standard" changed entity) 1011 IEntityStateEntry endEntry; 1012 if (m_stateManager.TryGetEntityStateEntry(key, out endEntry) && // make sure the entity is tracked 1013 !endEntry.IsKeyEntry && // make sure the entity is not a stub 1014 endEntry.State == EntityState.Unchanged) // if the entity is being modified, it's already included anyways 1015 { 1016 keySet.Add(key); 1017 } 1018 } 1019 ValidateRecord(EntitySetBase extent, IExtendedDataRecord record, IEntityStateEntry entry)1020 private void ValidateRecord(EntitySetBase extent, IExtendedDataRecord record, IEntityStateEntry entry) 1021 { 1022 Debug.Assert(null != extent, "must be verified by caller"); 1023 1024 DataRecordInfo recordInfo; 1025 if ((null == record) || 1026 (null == (recordInfo = record.DataRecordInfo)) || 1027 (null == recordInfo.RecordType)) 1028 { 1029 throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.InvalidStateEntry, 2); 1030 } 1031 1032 VerifyExtent(MetadataWorkspace, extent); 1033 1034 // additional validation happens lazily as values are loaded from the record 1035 } 1036 1037 // Verifies the given extent is present in the given workspace. VerifyExtent(MetadataWorkspace workspace, EntitySetBase extent)1038 private static void VerifyExtent(MetadataWorkspace workspace, EntitySetBase extent) 1039 { 1040 // get the container to which the given extent belongs 1041 EntityContainer actualContainer = extent.EntityContainer; 1042 1043 // try to retrieve the container in the given workspace 1044 EntityContainer referenceContainer = null; 1045 if (null != actualContainer) 1046 { 1047 workspace.TryGetEntityContainer( 1048 actualContainer.Name, actualContainer.DataSpace, out referenceContainer); 1049 } 1050 1051 // determine if the given extent lives in a container from the given workspace 1052 // (the item collections for each container are reference equivalent when they are declared in the 1053 // same item collection) 1054 if (null == actualContainer || null == referenceContainer || 1055 !Object.ReferenceEquals(actualContainer, referenceContainer)) 1056 { 1057 // 1058 1059 1060 1061 throw EntityUtil.Update(System.Data.Entity.Strings.Update_WorkspaceMismatch, null); 1062 } 1063 } 1064 LoadStateEntry(IEntityStateEntry stateEntry)1065 private void LoadStateEntry(IEntityStateEntry stateEntry) 1066 { 1067 Debug.Assert(null != stateEntry, "state entry must exist"); 1068 1069 // make sure the state entry doesn't contain invalid data and register it with the 1070 // update pipeline 1071 ValidateAndRegisterStateEntry(stateEntry); 1072 1073 // use data structure internal to the update pipeline instead of the raw state entry 1074 ExtractedStateEntry extractedStateEntry = new ExtractedStateEntry(this, stateEntry); 1075 1076 // figure out if this state entry is being handled by a function (stored procedure) or 1077 // through dynamic SQL 1078 EntitySetBase extent = stateEntry.EntitySet; 1079 if (null == m_viewLoader.GetFunctionMappingTranslator(extent, m_metadataWorkspace)) 1080 { 1081 // if there is no function mapping, register a ChangeNode (used for update 1082 // propagation and dynamic SQL generation) 1083 ChangeNode changeNode = GetExtentModifications(extent); 1084 if (null != extractedStateEntry.Original) 1085 { 1086 changeNode.Deleted.Add(extractedStateEntry.Original); 1087 } 1088 if (null != extractedStateEntry.Current) 1089 { 1090 changeNode.Inserted.Add(extractedStateEntry.Current); 1091 } 1092 } 1093 else 1094 { 1095 // for function updates, store off the extracted state entry in its entirety 1096 // (used when producing FunctionUpdateCommands) 1097 List<ExtractedStateEntry> functionEntries = GetExtentFunctionModifications(extent); 1098 functionEntries.Add(extractedStateEntry); 1099 } 1100 } 1101 1102 1103 1104 /// <summary> 1105 /// Retrieve a change node for an extent. If none exists, creates and registers a new one. 1106 /// </summary> 1107 /// <param name="extent">Extent for which to return a change node.</param> 1108 /// <returns>Change node for requested extent.</returns> GetExtentModifications(EntitySetBase extent)1109 internal ChangeNode GetExtentModifications(EntitySetBase extent) 1110 { 1111 EntityUtil.CheckArgumentNull(extent, "extent"); 1112 Debug.Assert(null != m_changes, "(UpdateTranslator/GetChangeNodeForExtent) method called before translator initialized"); 1113 1114 ChangeNode changeNode; 1115 1116 if (!m_changes.TryGetValue(extent, out changeNode)) 1117 { 1118 changeNode = new ChangeNode(TypeUsage.Create(extent.ElementType)); 1119 m_changes.Add(extent, changeNode); 1120 } 1121 1122 return changeNode; 1123 } 1124 1125 /// <summary> 1126 /// Retrieve a list of state entries being processed by custom user functions. 1127 /// </summary> 1128 /// <param name="extent">Extent for which to return entries.</param> 1129 /// <returns>List storing the entries.</returns> GetExtentFunctionModifications(EntitySetBase extent)1130 internal List<ExtractedStateEntry> GetExtentFunctionModifications(EntitySetBase extent) 1131 { 1132 EntityUtil.CheckArgumentNull(extent, "extent"); 1133 Debug.Assert(null != m_functionChanges, "method called before translator initialized"); 1134 1135 List<ExtractedStateEntry> entries; 1136 1137 if (!m_functionChanges.TryGetValue(extent, out entries)) 1138 { 1139 entries = new List<ExtractedStateEntry>(); 1140 m_functionChanges.Add(extent, entries); 1141 } 1142 1143 return entries; 1144 } 1145 #endregion 1146 #endregion 1147 } 1148 1149 /// <summary> 1150 /// Enumeration of possible operators. 1151 /// </summary> 1152 /// <remarks> 1153 /// The values are used to determine the order of operations (in the absence of any strong dependencies). 1154 /// The chosen order is based on the observation that hidden dependencies (e.g. due to temporary keys in 1155 /// the state manager or unknown FKs) favor deletes before inserts and updates before deletes. For instance, 1156 /// a deleted entity may have the same real key value as an inserted entity. Similarly, a self-reference 1157 /// may require a new dependent row to be updated before the prinpical row is inserted. Obviously, the actual 1158 /// constraints are required to make reliable decisions so this ordering is merely a heuristic. 1159 /// </remarks> 1160 internal enum ModificationOperator : byte 1161 { 1162 Update = 0, 1163 Delete = 1, 1164 Insert = 2, 1165 } 1166 } 1167