1 //--------------------------------------------------------------------- 2 // <copyright file="EntityEntry.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 // 6 // @owner Microsoft 7 // @backupOwner Microsoft 8 //--------------------------------------------------------------------- 9 namespace System.Data.Objects 10 { 11 using System.Collections; 12 using System.Collections.Generic; 13 using System.ComponentModel; 14 using System.Data.Common; 15 using System.Data.Common.Utils; 16 using System.Data.Metadata.Edm; 17 using System.Data.Objects.DataClasses; 18 using System.Data.Objects.Internal; 19 using System.Diagnostics; 20 using System.Linq; 21 22 internal sealed class EntityEntry : ObjectStateEntry 23 { 24 private StateManagerTypeMetadata _cacheTypeMetadata; 25 private EntityKey _entityKey; // !null if IsKeyEntry or Entity 26 private IEntityWrapper _wrappedEntity; // Contains null entity if IsKeyEntry 27 28 // entity entry change tracking 29 private BitArray _modifiedFields; // only and always exists if state is Modified or after Delete() on Modified 30 private List<StateManagerValue> _originalValues; // only exists if _modifiedFields has a true-bit 31 32 // The _originalComplexObjects should always contain references to the values of complex objects which are "original" 33 // at the moment of calling GetComplexObjectSnapshot(). They are used to get original scalar values from _originalValues 34 // and to check if complex object instance was changed. 35 private Dictionary<object, Dictionary<int, object>> _originalComplexObjects; // used for POCO Complex Objects change tracking 36 37 private bool _requiresComplexChangeTracking; 38 private bool _requiresScalarChangeTracking; 39 private bool _requiresAnyChangeTracking; 40 41 #region RelationshipEnd fields 42 43 /// <summary> 44 /// Singlely-linked list of RelationshipEntry. 45 /// One of the ends in the RelationshipEntry must equal this.EntityKey 46 /// </summary> 47 private RelationshipEntry _headRelationshipEnds; 48 49 /// <summary> 50 /// Number of RelationshipEntry in the _relationshipEnds list. 51 /// </summary> 52 private int _countRelationshipEnds; 53 54 #endregion 55 56 #region Constructors 57 58 // EntityEntry EntityEntry(IEntityWrapper wrappedEntity, EntityKey entityKey, EntitySet entitySet, ObjectStateManager cache, StateManagerTypeMetadata typeMetadata, EntityState state)59 internal EntityEntry(IEntityWrapper wrappedEntity, EntityKey entityKey, EntitySet entitySet, ObjectStateManager cache, 60 StateManagerTypeMetadata typeMetadata, EntityState state) 61 : base(cache, entitySet, state) 62 { 63 Debug.Assert(wrappedEntity != null, "entity wrapper cannot be null."); 64 Debug.Assert(wrappedEntity.Entity != null, "entity cannot be null."); 65 Debug.Assert(typeMetadata != null, "typeMetadata cannot be null."); 66 Debug.Assert(entitySet != null, "entitySet cannot be null."); 67 Debug.Assert((null == (object)entityKey) || (entityKey.EntitySetName == entitySet.Name), "different entitySet"); 68 69 _wrappedEntity = wrappedEntity; 70 _cacheTypeMetadata = typeMetadata; 71 _entityKey = entityKey; 72 73 wrappedEntity.ObjectStateEntry = this; 74 75 SetChangeTrackingFlags(); 76 } 77 78 /// <summary> 79 /// Looks at the type of entity represented by this entry and sets flags defining the type of 80 /// change tracking that will be needed. The three main types are: 81 /// - Pure POCO objects or non-change-tracking proxies which need DetectChanges for everything. 82 /// - Entities derived from EntityObject which don't need DetectChanges at all. 83 /// - Change tracking proxies, which only need DetectChanges for complex properties. 84 /// </summary> SetChangeTrackingFlags()85 private void SetChangeTrackingFlags() 86 { 87 _requiresScalarChangeTracking = Entity != null && !(Entity is IEntityWithChangeTracker); 88 89 _requiresComplexChangeTracking = Entity != null && 90 (_requiresScalarChangeTracking || 91 (WrappedEntity.IdentityType != Entity.GetType() && 92 _cacheTypeMetadata.Members.Any(m => m.IsComplex))); 93 94 _requiresAnyChangeTracking = Entity != null && 95 (!(Entity is IEntityWithRelationships) || 96 _requiresComplexChangeTracking || 97 _requiresScalarChangeTracking); 98 } 99 100 // KeyEntry EntityEntry(EntityKey entityKey, EntitySet entitySet, ObjectStateManager cache, StateManagerTypeMetadata typeMetadata)101 internal EntityEntry(EntityKey entityKey, EntitySet entitySet, ObjectStateManager cache, StateManagerTypeMetadata typeMetadata) 102 : base(cache, entitySet, EntityState.Unchanged) 103 { 104 Debug.Assert((object)entityKey != null, "entityKey cannot be null."); 105 Debug.Assert(entitySet != null, "extent cannot be null."); 106 Debug.Assert(typeMetadata != null, "typeMetadata cannot be null."); 107 Debug.Assert(entityKey.EntitySetName == entitySet.Name, "different entitySet"); 108 109 _wrappedEntity = EntityWrapperFactory.NullWrapper; 110 _entityKey = entityKey; 111 _cacheTypeMetadata = typeMetadata; 112 113 SetChangeTrackingFlags(); 114 } 115 116 #endregion 117 118 #region Public members 119 120 override public bool IsRelationship 121 { 122 get 123 { 124 ValidateState(); 125 return false; 126 } 127 } 128 129 override public object Entity 130 { 131 get 132 { 133 ValidateState(); 134 return _wrappedEntity.Entity; 135 } 136 } 137 138 /// <summary> 139 /// The EntityKey associated with the ObjectStateEntry 140 /// </summary> 141 override public EntityKey EntityKey 142 { 143 get 144 { 145 ValidateState(); 146 return _entityKey; 147 } 148 internal set 149 { 150 _entityKey = value; 151 } 152 } 153 154 internal IEnumerable<Tuple<AssociationSet, ReferentialConstraint>> ForeignKeyDependents 155 { 156 get 157 { 158 foreach (var foreignKey in ((EntitySet)EntitySet).ForeignKeyDependents) 159 { 160 AssociationSet associationSet = foreignKey.Item1; 161 ReferentialConstraint constraint = foreignKey.Item2; 162 EntityType dependentType = MetadataHelper.GetEntityTypeForEnd((AssociationEndMember)constraint.ToRole); 163 if (dependentType.IsAssignableFrom(_cacheTypeMetadata.DataRecordInfo.RecordType.EdmType)) 164 { 165 yield return foreignKey; 166 } 167 } 168 } 169 } 170 171 internal IEnumerable<Tuple<AssociationSet, ReferentialConstraint>> ForeignKeyPrincipals 172 { 173 get 174 { 175 foreach (var foreignKey in ((EntitySet)EntitySet).ForeignKeyPrincipals) 176 { 177 AssociationSet associationSet = foreignKey.Item1; 178 ReferentialConstraint constraint = foreignKey.Item2; 179 EntityType dependentType = MetadataHelper.GetEntityTypeForEnd((AssociationEndMember)constraint.FromRole); 180 if (dependentType.IsAssignableFrom(_cacheTypeMetadata.DataRecordInfo.RecordType.EdmType)) 181 { 182 yield return foreignKey; 183 } 184 } 185 } 186 } 187 GetModifiedProperties()188 override public IEnumerable<string> GetModifiedProperties() 189 { 190 ValidateState(); 191 if (EntityState.Modified == this.State && _modifiedFields != null) 192 { 193 Debug.Assert(null != _modifiedFields, "null fields"); 194 for (int i = 0; i < _modifiedFields.Count; i++) 195 { 196 if (_modifiedFields[i]) 197 { 198 yield return (GetCLayerName(i, _cacheTypeMetadata)); 199 } 200 } 201 } 202 } 203 204 /// <summary> 205 /// Marks specified property as modified. 206 /// </summary> 207 /// <param name="propertyName">This API recognizes the names in terms of OSpace</param> 208 /// <exception cref="InvalidOperationException">If State is not Modified or Unchanged</exception> 209 /// SetModifiedProperty(string propertyName)210 override public void SetModifiedProperty(string propertyName) 211 { 212 int ordinal = ValidateAndGetOrdinalForProperty(propertyName, "SetModifiedProperty"); 213 214 Debug.Assert(State == EntityState.Unchanged || State == EntityState.Modified, "ValidateAndGetOrdinalForProperty should have thrown."); 215 216 if (EntityState.Unchanged == State) 217 { 218 State = EntityState.Modified; 219 _cache.ChangeState(this, EntityState.Unchanged, State); 220 } 221 222 SetModifiedPropertyInternal(ordinal); 223 } 224 SetModifiedPropertyInternal(int ordinal)225 internal void SetModifiedPropertyInternal(int ordinal) 226 { 227 if (null == _modifiedFields) 228 { 229 _modifiedFields = new BitArray(GetFieldCount(_cacheTypeMetadata)); 230 } 231 232 _modifiedFields[ordinal] = true; 233 } 234 ValidateAndGetOrdinalForProperty(string propertyName, string methodName)235 private int ValidateAndGetOrdinalForProperty(string propertyName, string methodName) 236 { 237 EntityUtil.CheckArgumentNull(propertyName, "propertyName"); 238 239 // Throw for detached entities 240 ValidateState(); 241 242 if (IsKeyEntry) 243 { 244 throw EntityUtil.CannotModifyKeyEntryState(); 245 } 246 247 int ordinal = _cacheTypeMetadata.GetOrdinalforOLayerMemberName(propertyName); 248 if (ordinal == -1) 249 { 250 throw EntityUtil.InvalidModifiedPropertyName(propertyName); 251 } 252 253 if (State == EntityState.Added || State == EntityState.Deleted) 254 { 255 // Threw for detached above; this throws for Added or Deleted entities 256 throw EntityUtil.SetModifiedStates(methodName); 257 } 258 259 return ordinal; 260 } 261 262 /// <summary> 263 /// Rejects any changes made to the property with the given name since the property was last loaded, 264 /// attached, saved, or changes were accepted. The orginal value of the property is stored and the 265 /// property will no longer be marked as modified. 266 /// </summary> 267 /// <remarks> 268 /// If the result is that no properties of the entity are marked as modified, then the entity will 269 /// be marked as Unchanged. 270 /// Changes to properties can only rejected for entities that are in the Modified or Unchanged state. 271 /// Calling this method for entities in other states (Added, Deleted, or Detached) will result in 272 /// an exception being thrown. 273 /// Rejecting changes to properties of an Unchanged entity or unchanged properties of a Modifed 274 /// is a no-op. 275 /// </remarks> 276 /// <param name="propertyName">The name of the property to change.</param> RejectPropertyChanges(string propertyName)277 override public void RejectPropertyChanges(string propertyName) 278 { 279 int ordinal = ValidateAndGetOrdinalForProperty(propertyName, "RejectPropertyChanges"); 280 281 if (State == EntityState.Unchanged) 282 { 283 // No-op for unchanged entities since all properties must be unchanged. 284 return; 285 } 286 287 Debug.Assert(State == EntityState.Modified, "Should have handled all other states above."); 288 289 if (_modifiedFields != null && _modifiedFields[ordinal]) 290 { 291 // Reject the change by setting the current value to the original value 292 DetectChangesInComplexProperties(); 293 var originalValue = GetOriginalEntityValue(_cacheTypeMetadata, ordinal, _wrappedEntity.Entity, ObjectStateValueRecord.OriginalReadonly); 294 SetCurrentEntityValue(_cacheTypeMetadata, ordinal, _wrappedEntity.Entity, originalValue); 295 _modifiedFields[ordinal] = false; 296 297 // Check if any properties remain modified. If any are modified, then we leave the entity state as Modified and we are done. 298 for (int i = 0; i < _modifiedFields.Count; i++) 299 { 300 if (_modifiedFields[i]) 301 { 302 return; 303 } 304 } 305 306 // No properties are modified so change the state of the entity to Unchanged. 307 ChangeObjectState(EntityState.Unchanged); 308 } 309 } 310 311 /// <summary> 312 /// Original values of entity 313 /// </summary> 314 /// <param></param> 315 /// <returns> DbDataRecord </returns> 316 [DebuggerBrowsable(DebuggerBrowsableState.Never)] // don't have debugger view expand this 317 override public DbDataRecord OriginalValues 318 { 319 get 320 { 321 return InternalGetOriginalValues(true /*readOnly*/); 322 } 323 } 324 325 /// <summary> 326 /// Gets a version of the OriginalValues property that can be updated 327 /// </summary> GetUpdatableOriginalValues()328 public override OriginalValueRecord GetUpdatableOriginalValues() 329 { 330 return (OriginalValueRecord)InternalGetOriginalValues(false /*readOnly*/); 331 } 332 InternalGetOriginalValues(bool readOnly)333 private DbDataRecord InternalGetOriginalValues(bool readOnly) 334 { 335 ValidateState(); 336 if (this.State == EntityState.Added) 337 { 338 throw EntityUtil.OriginalValuesDoesNotExist(); 339 } 340 341 if (this.IsKeyEntry) 342 { 343 throw EntityUtil.CannotAccessKeyEntryValues(); 344 } 345 else 346 { 347 DetectChangesInComplexProperties(); 348 349 if (readOnly) 350 { 351 return new ObjectStateEntryDbDataRecord(this, _cacheTypeMetadata, _wrappedEntity.Entity); 352 } 353 else 354 { 355 return new ObjectStateEntryOriginalDbUpdatableDataRecord_Public(this, _cacheTypeMetadata, _wrappedEntity.Entity, s_EntityRoot); 356 } 357 } 358 } 359 DetectChangesInComplexProperties()360 private void DetectChangesInComplexProperties() 361 { 362 if (this.RequiresScalarChangeTracking) 363 { 364 // POCO: the snapshot of complex objects has to be updated 365 // without chaning state of the entry or marking properties as modified. 366 // The IsOriginalValuesGetter is used in EntityMemberChanged to skip the state transition. 367 // The snapshot has to be updated in case the complex object instance was changed (not only scalar values). 368 this.ObjectStateManager.TransactionManager.BeginOriginalValuesGetter(); 369 try 370 { 371 // Process only complex objects. The method will not change the state of the entry. 372 this.DetectChangesInProperties(true /*detectOnlyComplexProperties*/); 373 } 374 finally 375 { 376 this.ObjectStateManager.TransactionManager.EndOriginalValuesGetter(); 377 } 378 } 379 } 380 381 /// <summary> 382 /// Current values of entity/ DataRow 383 /// </summary> 384 /// <param></param> 385 /// <returns> DbUpdatableDataRecord </returns> 386 [DebuggerBrowsable(DebuggerBrowsableState.Never)] // don't have debugger view expand this 387 override public CurrentValueRecord CurrentValues 388 { 389 get 390 { 391 ValidateState(); 392 if (this.State == EntityState.Deleted) 393 { 394 throw EntityUtil.CurrentValuesDoesNotExist(); 395 } 396 397 if (this.IsKeyEntry) 398 { 399 throw EntityUtil.CannotAccessKeyEntryValues(); 400 } 401 else 402 { 403 return new ObjectStateEntryDbUpdatableDataRecord(this, _cacheTypeMetadata, _wrappedEntity.Entity); 404 } 405 } 406 } 407 Delete()408 override public void Delete() 409 { 410 // doFixup flag is used for Cache and Collection & Ref consistency 411 // When some entity is deleted if "doFixup" is true then Delete method 412 // calls the Collection & Ref code to do the necessary fix-ups. 413 // "doFixup" equals to False is only called from EntityCollection & Ref code 414 Delete(/*doFixup*/true); 415 } 416 417 /// <summary> 418 /// API to accept the current values as original values and mark the entity as Unchanged. 419 /// </summary> 420 /// <param></param> 421 /// <returns></returns> AcceptChanges()422 override public void AcceptChanges() 423 { 424 ValidateState(); 425 426 if (ObjectStateManager.EntryHasConceptualNull(this)) 427 { 428 throw new InvalidOperationException(System.Data.Entity.Strings.ObjectContext_CommitWithConceptualNull); 429 } 430 431 Debug.Assert(!this.IsKeyEntry || State == EntityState.Unchanged, "Key ObjectStateEntries must always be unchanged."); 432 433 switch (State) 434 { 435 case EntityState.Deleted: 436 this.CascadeAcceptChanges(); 437 // Current entry could be already detached if this is relationship entry and if one end of relationship was a KeyEntry 438 if (_cache != null) 439 { 440 _cache.ChangeState(this, EntityState.Deleted, EntityState.Detached); 441 } 442 break; 443 case EntityState.Added: 444 // If this entry represents an entity, perform key fixup. 445 Debug.Assert(Entity != null, "Non-relationship entries should have a non-null entity."); 446 Debug.Assert((object)_entityKey != null, "All entities in the state manager should have a non-null EntityKey."); 447 Debug.Assert(_entityKey.IsTemporary, "All entities in the Added state should have a temporary EntityKey."); 448 449 // Retrieve referential constraint properties from Principal entities (possibly recursively) 450 // and check referential constraint properties in the Dependent entities (1 level only) 451 // We have to do this before fixing up keys to preserve v1 behavior around when stubs are promoted. 452 // However, we can't check FKs until after fixup, which happens after key fixup. Therefore, 453 // we keep track of whether or not we need to go check again after fixup. Also, checking for independent associations 454 // happens using RelationshipEntries, while checking for constraints in FKs has to use the graph. 455 bool skippedFKs = RetrieveAndCheckReferentialConstraintValuesInAcceptChanges(); 456 457 _cache.FixupKey(this); 458 459 _modifiedFields = null; 460 _originalValues = null; 461 _originalComplexObjects = null; 462 State = EntityState.Unchanged; 463 464 if (skippedFKs) 465 { 466 // If we skipped checking constraints on any FK relationships above, then 467 // do it now on the fixuped RelatedEnds. 468 RelationshipManager.CheckReferentialConstraintProperties(this); 469 } 470 471 _wrappedEntity.TakeSnapshot(this); 472 473 break; 474 case EntityState.Modified: 475 _cache.ChangeState(this, EntityState.Modified, EntityState.Unchanged); 476 _modifiedFields = null; 477 _originalValues = null; 478 _originalComplexObjects = null; 479 State = EntityState.Unchanged; 480 _cache.FixupReferencesByForeignKeys(this); 481 482 // Need to check constraints here because fixup could have got us into an invalid state 483 RelationshipManager.CheckReferentialConstraintProperties(this); 484 _wrappedEntity.TakeSnapshot(this); 485 486 break; 487 case EntityState.Unchanged: 488 break; 489 } 490 } 491 SetModified()492 override public void SetModified() 493 { 494 ValidateState(); 495 496 if (this.IsKeyEntry) 497 { 498 throw EntityUtil.CannotModifyKeyEntryState(); 499 } 500 else 501 { 502 if (EntityState.Unchanged == State) 503 { 504 State = EntityState.Modified; 505 _cache.ChangeState(this, EntityState.Unchanged, State); 506 } 507 else if (EntityState.Modified != State) 508 { 509 throw EntityUtil.SetModifiedStates("SetModified"); 510 } 511 } 512 } 513 514 override public RelationshipManager RelationshipManager 515 { 516 get 517 { 518 ValidateState(); 519 if (IsKeyEntry) 520 { 521 throw new InvalidOperationException(System.Data.Entity.Strings.ObjectStateEntry_RelationshipAndKeyEntriesDoNotHaveRelationshipManagers); 522 } 523 if (WrappedEntity.Entity == null) 524 { 525 throw new InvalidOperationException(System.Data.Entity.Strings.ObjectStateManager_CannotGetRelationshipManagerForDetachedPocoEntity); 526 } 527 return WrappedEntity.RelationshipManager; 528 } 529 } 530 531 internal override BitArray ModifiedProperties 532 { 533 get { return _modifiedFields; } 534 } 535 536 /// <summary> 537 /// Changes state of the entry to the specified <paramref name="state"/> 538 /// </summary> 539 /// <param name="state">The requested state</param> ChangeState(EntityState state)540 public override void ChangeState(EntityState state) 541 { 542 EntityUtil.CheckValidStateForChangeEntityState(state); 543 544 if (this.State == EntityState.Detached && state == EntityState.Detached) 545 { 546 return; 547 } 548 549 ValidateState(); 550 551 // store a referece to the cache because this.ObjectStatemanager will be null if the requested state is Detached 552 ObjectStateManager osm = this.ObjectStateManager; 553 osm.TransactionManager.BeginLocalPublicAPI(); 554 try 555 { 556 this.ChangeObjectState(state); 557 } 558 finally 559 { 560 osm.TransactionManager.EndLocalPublicAPI(); 561 } 562 } 563 564 /// <summary> 565 /// Apply modified properties to the original object. 566 /// </summary> 567 /// <param name="currentEntity">object with modified properties</param> ApplyCurrentValues(object currentEntity)568 public override void ApplyCurrentValues(object currentEntity) 569 { 570 EntityUtil.CheckArgumentNull(currentEntity, "currentEntity"); 571 572 ValidateState(); 573 574 if (this.IsKeyEntry) 575 { 576 throw EntityUtil.CannotAccessKeyEntryValues(); 577 } 578 579 IEntityWrapper wrappedEntity = EntityWrapperFactory.WrapEntityUsingStateManager(currentEntity, this.ObjectStateManager); 580 581 this.ApplyCurrentValuesInternal(wrappedEntity); 582 } 583 584 /// <summary> 585 /// Apply original values to the entity. 586 /// </summary> 587 /// <param name="originalEntity">The object with original values</param> ApplyOriginalValues(object originalEntity)588 public override void ApplyOriginalValues(object originalEntity) 589 { 590 EntityUtil.CheckArgumentNull(originalEntity, "originalEntity"); 591 592 ValidateState(); 593 594 if (this.IsKeyEntry) 595 { 596 throw EntityUtil.CannotAccessKeyEntryValues(); 597 } 598 599 IEntityWrapper wrappedEntity = EntityWrapperFactory.WrapEntityUsingStateManager(originalEntity, this.ObjectStateManager); 600 601 this.ApplyOriginalValuesInternal(wrappedEntity); 602 } 603 604 #endregion // Public members 605 606 #region RelationshipEnd methods 607 608 /// <summary> 609 /// Add a RelationshipEntry (one of its ends must equal this.EntityKey) 610 /// </summary> AddRelationshipEnd(RelationshipEntry item)611 internal void AddRelationshipEnd(RelationshipEntry item) 612 { 613 #if DEBUG 614 Debug.Assert(null != item, "null item"); 615 Debug.Assert(null != item.RelationshipWrapper, "null RelationshipWrapper"); 616 Debug.Assert(0 <= _countRelationshipEnds, "negative _relationshipEndCount"); 617 Debug.Assert(EntityKey.Equals(item.RelationshipWrapper.Key0) || EntityKey.Equals(item.RelationshipWrapper.Key1), "entity key doesn't match"); 618 619 for (RelationshipEntry current = _headRelationshipEnds; 620 null != current; 621 current = current.GetNextRelationshipEnd(EntityKey)) 622 { 623 Debug.Assert(!Object.ReferenceEquals(item, current), "RelationshipEntry already in list"); 624 Debug.Assert(!item.RelationshipWrapper.Equals(current.RelationshipWrapper), "RelationshipWrapper already in list"); 625 } 626 #endif 627 // the item will become the head of the list 628 // i.e. you walk the list in reverse order of items being added 629 item.SetNextRelationshipEnd(this.EntityKey, _headRelationshipEnds); 630 _headRelationshipEnds = item; 631 _countRelationshipEnds++; 632 633 Debug.Assert(_countRelationshipEnds == (new RelationshipEndEnumerable(this)).ToArray().Length, "different count"); 634 } 635 636 /// <summary> 637 /// Determines if a given relationship entry is present in the list of entries 638 /// </summary> 639 /// <param name="item">The entry to look for</param> 640 /// <returns>True of the relationship end is found</returns> ContainsRelationshipEnd(RelationshipEntry item)641 internal bool ContainsRelationshipEnd(RelationshipEntry item) 642 { 643 for (RelationshipEntry current = _headRelationshipEnds; 644 null != current; 645 current = current.GetNextRelationshipEnd(EntityKey)) 646 { 647 if (object.ReferenceEquals(current, item)) 648 { 649 return true; 650 } 651 } 652 return false; 653 } 654 655 /// <summary> 656 /// Remove a RelationshipEntry (one of its ends must equal this.EntityKey) 657 /// </summary> 658 /// <param name="item"></param> RemoveRelationshipEnd(RelationshipEntry item)659 internal void RemoveRelationshipEnd(RelationshipEntry item) 660 { 661 Debug.Assert(null != item, "removing null"); 662 Debug.Assert(null != item.RelationshipWrapper, "null RelationshipWrapper"); 663 Debug.Assert(1 <= _countRelationshipEnds, "negative _relationshipEndCount"); 664 Debug.Assert(EntityKey.Equals(item.RelationshipWrapper.Key0) || EntityKey.Equals(item.RelationshipWrapper.Key1), "entity key doesn't match"); 665 666 // walk the singly-linked list, remembering the previous node so we can remove the current node 667 RelationshipEntry current = _headRelationshipEnds; 668 RelationshipEntry previous = null; 669 bool previousIsKey0 = false; 670 while (null != current) 671 { 672 // short-circuit if the key matches either candidate by reference 673 bool currentIsKey0 = object.ReferenceEquals(this.EntityKey, current.Key0) || 674 (!object.ReferenceEquals(this.EntityKey, current.Key1) && this.EntityKey.Equals(current.Key0)); 675 if (Object.ReferenceEquals(item, current)) 676 { 677 RelationshipEntry next; 678 if (currentIsKey0) 679 { // if this.EntityKey matches Key0, NextKey0 is the next element in the lsit 680 Debug.Assert(EntityKey.Equals(current.RelationshipWrapper.Key0), "entity key didn't match"); 681 next = current.NextKey0; 682 current.NextKey0 = null; 683 } 684 else 685 { // if this.EntityKey matches Key1, NextKey1 is the next element in the lsit 686 Debug.Assert(EntityKey.Equals(current.RelationshipWrapper.Key1), "entity key didn't match"); 687 next = current.NextKey1; 688 current.NextKey1 = null; 689 } 690 if (null == previous) 691 { 692 _headRelationshipEnds = next; 693 } 694 else if (previousIsKey0) 695 { 696 previous.NextKey0 = next; 697 } 698 else 699 { 700 previous.NextKey1 = next; 701 } 702 --_countRelationshipEnds; 703 704 Debug.Assert(_countRelationshipEnds == (new RelationshipEndEnumerable(this)).ToArray().Length, "different count"); 705 return; 706 } 707 Debug.Assert(!item.RelationshipWrapper.Equals(current.RelationshipWrapper), "same wrapper, different RelationshipEntry instances"); 708 709 previous = current; 710 current = currentIsKey0 ? current.NextKey0 : current.NextKey1; 711 previousIsKey0 = currentIsKey0; 712 } 713 Debug.Assert(false, "didn't remove a RelationshipEntry"); 714 } 715 716 /// <summary> 717 /// Update one of the ends for the related RelationshipEntry 718 /// </summary> 719 /// <param name="oldKey">the EntityKey the relationship should currently have</param> 720 /// <param name="promotedEntry">if promoting entity stub to full entity</param> UpdateRelationshipEnds(EntityKey oldKey, EntityEntry promotedEntry)721 internal void UpdateRelationshipEnds(EntityKey oldKey, EntityEntry promotedEntry) 722 { 723 Debug.Assert(null != (object)oldKey, "bad oldKey"); 724 Debug.Assert(!Object.ReferenceEquals(this, promotedEntry), "shouldn't be same reference"); 725 726 // traverse the list to update one of the ends in the relationship entry 727 int count = 0; 728 RelationshipEntry next = _headRelationshipEnds; 729 while (null != next) 730 { 731 // get the next relationship end before we change the key of current relationship end 732 RelationshipEntry current = next; 733 next = next.GetNextRelationshipEnd(oldKey); 734 735 // update the RelationshipEntry from the temporary key to real key 736 current.ChangeRelatedEnd(oldKey, EntityKey); 737 738 // If we have a promoted entry, copy the relationship entries to the promoted entry 739 // only if the promoted entry doesn't already know about that particular relationship entry 740 // This can be the case with self referencing entities 741 if (null != promotedEntry && !promotedEntry.ContainsRelationshipEnd(current)) 742 { // all relationship ends moved to new promotedEntry 743 promotedEntry.AddRelationshipEnd(current); 744 } 745 ++count; 746 } 747 Debug.Assert(count == _countRelationshipEnds, "didn't traverse all relationships"); 748 if (null != promotedEntry) 749 { // cleanup existing (dead) entry to reduce confusion 750 _headRelationshipEnds = null; 751 _countRelationshipEnds = 0; 752 } 753 } 754 755 #region Enumerable and Enumerator GetRelationshipEnds()756 internal RelationshipEndEnumerable GetRelationshipEnds() 757 { 758 return new RelationshipEndEnumerable(this); 759 } 760 761 /// <summary> 762 /// An enumerable so that EntityEntry doesn't implement it 763 /// </summary> 764 internal struct RelationshipEndEnumerable : IEnumerable<RelationshipEntry>, IEnumerable<IEntityStateEntry> 765 { 766 internal static readonly RelationshipEntry[] EmptyRelationshipEntryArray = new RelationshipEntry[0]; 767 private readonly EntityEntry _entityEntry; 768 RelationshipEndEnumerableSystem.Data.Objects.EntityEntry.RelationshipEndEnumerable769 internal RelationshipEndEnumerable(EntityEntry entityEntry) 770 { // its okay if entityEntry is null 771 _entityEntry = entityEntry; 772 } GetEnumeratorSystem.Data.Objects.EntityEntry.RelationshipEndEnumerable773 public RelationshipEndEnumerator GetEnumerator() 774 { 775 return new RelationshipEndEnumerator(_entityEntry); 776 } GetEnumeratorSystem.Data.Objects.EntityEntry.RelationshipEndEnumerable777 IEnumerator<IEntityStateEntry> IEnumerable<IEntityStateEntry>.GetEnumerator() 778 { 779 return GetEnumerator(); 780 } GetEnumeratorSystem.Data.Objects.EntityEntry.RelationshipEndEnumerable781 IEnumerator<RelationshipEntry> IEnumerable<RelationshipEntry>.GetEnumerator() 782 { 783 Debug.Assert(false, "dead code, don't box the RelationshipEndEnumerable"); 784 return GetEnumerator(); 785 } IEnumerable.GetEnumeratorSystem.Data.Objects.EntityEntry.RelationshipEndEnumerable786 IEnumerator IEnumerable.GetEnumerator() 787 { 788 Debug.Assert(false, "dead code, don't box the RelationshipEndEnumerable"); 789 return GetEnumerator(); 790 } 791 792 /// <summary> 793 /// Convert the singly-linked list into an Array 794 /// </summary> ToArraySystem.Data.Objects.EntityEntry.RelationshipEndEnumerable795 internal RelationshipEntry[] ToArray() 796 { 797 RelationshipEntry[] list = null; 798 if ((null != _entityEntry) && (0 < _entityEntry._countRelationshipEnds)) 799 { 800 RelationshipEntry relationshipEnd = _entityEntry._headRelationshipEnds; 801 list = new RelationshipEntry[_entityEntry._countRelationshipEnds]; 802 for (int i = 0; i < list.Length; ++i) 803 { 804 Debug.Assert(null != relationshipEnd, "count larger than list"); 805 Debug.Assert(_entityEntry.EntityKey.Equals(relationshipEnd.Key0) || _entityEntry.EntityKey.Equals(relationshipEnd.Key1), "entity key mismatch"); 806 list[i] = relationshipEnd; 807 808 relationshipEnd = relationshipEnd.GetNextRelationshipEnd(_entityEntry.EntityKey); 809 } 810 Debug.Assert(null == relationshipEnd, "count smaller than list"); 811 } 812 return list ?? EmptyRelationshipEntryArray; 813 } 814 } 815 816 /// <summary> 817 /// An enumerator to walk the RelationshipEntry linked-list 818 /// </summary> 819 internal struct RelationshipEndEnumerator : IEnumerator<RelationshipEntry>, IEnumerator<IEntityStateEntry> 820 { 821 private readonly EntityEntry _entityEntry; 822 private RelationshipEntry _current; 823 RelationshipEndEnumeratorSystem.Data.Objects.EntityEntry.RelationshipEndEnumerator824 internal RelationshipEndEnumerator(EntityEntry entityEntry) 825 { 826 _entityEntry = entityEntry; 827 _current = null; 828 } 829 public RelationshipEntry Current 830 { 831 get { return _current; } 832 } 833 IEntityStateEntry IEnumerator<IEntityStateEntry>.Current 834 { 835 get { return _current; } 836 } 837 object IEnumerator.Current 838 { 839 get 840 { 841 Debug.Assert(false, "dead code, don't box the RelationshipEndEnumerator"); 842 return _current; 843 } 844 } DisposeSystem.Data.Objects.EntityEntry.RelationshipEndEnumerator845 public void Dispose() 846 { 847 } MoveNextSystem.Data.Objects.EntityEntry.RelationshipEndEnumerator848 public bool MoveNext() 849 { 850 if (null != _entityEntry) 851 { 852 if (null == _current) 853 { 854 _current = _entityEntry._headRelationshipEnds; 855 } 856 else 857 { 858 _current = _current.GetNextRelationshipEnd(_entityEntry.EntityKey); 859 } 860 } 861 return (null != _current); 862 } ResetSystem.Data.Objects.EntityEntry.RelationshipEndEnumerator863 public void Reset() 864 { 865 Debug.Assert(false, "not implemented"); 866 } 867 } 868 #endregion 869 #endregion 870 871 #region ObjectStateEntry members 872 873 override internal bool IsKeyEntry 874 { 875 get 876 { 877 return null == _wrappedEntity.Entity; 878 } 879 } 880 881 /// <summary> 882 /// Reuse or create a new (Entity)DataRecordInfo. 883 /// </summary> GetDataRecordInfo(StateManagerTypeMetadata metadata, object userObject)884 override internal DataRecordInfo GetDataRecordInfo(StateManagerTypeMetadata metadata, object userObject) 885 { 886 if (Helper.IsEntityType(metadata.CdmMetadata.EdmType) && (null != (object)_entityKey)) 887 { 888 // is EntityType with null EntityKey when constructing new EntityKey during ObjectStateManager.Add 889 // always need a new EntityRecordInfo instance for the different key (reusing DataRecordInfo's FieldMetadata). 890 return new EntityRecordInfo(metadata.DataRecordInfo, _entityKey, (EntitySet)EntitySet); 891 } 892 else 893 { 894 // ObjectContext.AttachTo uses CurrentValueRecord to build EntityKey for EntityType 895 // so the Entity doesn't have an EntityKey yet, SQLBU 525130 896 //Debug.Assert(Helper.IsComplexType(metadata.CdmMetadata.EdmType), "!IsComplexType"); 897 return metadata.DataRecordInfo; 898 } 899 } 900 Reset()901 override internal void Reset() 902 { 903 Debug.Assert(_cache != null, "Cannot Reset an entity that is not currently attached to a context."); 904 RemoveFromForeignKeyIndex(); 905 _cache.ForgetEntryWithConceptualNull(this, resetAllKeys: true); 906 907 DetachObjectStateManagerFromEntity(); 908 909 _wrappedEntity = EntityWrapperFactory.NullWrapper; 910 _entityKey = null; 911 _modifiedFields = null; 912 _originalValues = null; 913 _originalComplexObjects = null; 914 915 SetChangeTrackingFlags(); 916 917 base.Reset(); 918 } 919 GetFieldType(int ordinal, StateManagerTypeMetadata metadata)920 override internal Type GetFieldType(int ordinal, StateManagerTypeMetadata metadata) 921 { 922 // 'metadata' is used for ComplexTypes 923 924 return metadata.GetFieldType(ordinal); 925 } 926 GetCLayerName(int ordinal, StateManagerTypeMetadata metadata)927 override internal string GetCLayerName(int ordinal, StateManagerTypeMetadata metadata) 928 { 929 return metadata.CLayerMemberName(ordinal); 930 } 931 GetOrdinalforCLayerName(string name, StateManagerTypeMetadata metadata)932 override internal int GetOrdinalforCLayerName(string name, StateManagerTypeMetadata metadata) 933 { 934 return metadata.GetOrdinalforCLayerMemberName(name); 935 } 936 RevertDelete()937 override internal void RevertDelete() 938 { 939 // just change the state from deleted, to last state. 940 State = (_modifiedFields == null) ? EntityState.Unchanged : EntityState.Modified; 941 _cache.ChangeState(this, EntityState.Deleted, State); 942 } 943 GetFieldCount(StateManagerTypeMetadata metadata)944 override internal int GetFieldCount(StateManagerTypeMetadata metadata) 945 { 946 return metadata.FieldCount; 947 } 948 CascadeAcceptChanges()949 private void CascadeAcceptChanges() 950 { 951 foreach (RelationshipEntry entry in _cache.CopyOfRelationshipsByKey(EntityKey)) 952 { 953 // CascadeAcceptChanges is only called on Entity ObjectStateEntry when it is 954 // in deleted state. Entity is in deleted state therefore for all related Relationship 955 // cache entries only valid state is Deleted. 956 Debug.Assert(entry.State == EntityState.Deleted, "Relationship ObjectStateEntry should be in deleted state"); 957 entry.AcceptChanges(); 958 } 959 } 960 SetModifiedAll()961 override internal void SetModifiedAll() 962 { 963 Debug.Assert(!this.IsKeyEntry, "SetModifiedAll called on a KeyEntry"); 964 Debug.Assert(State == EntityState.Modified, "SetModifiedAll called when not modified"); 965 966 ValidateState(); 967 if (null == _modifiedFields) 968 { 969 _modifiedFields = new BitArray(GetFieldCount(_cacheTypeMetadata)); 970 } 971 _modifiedFields.SetAll(true); 972 } 973 974 /// <summary> 975 /// Used to report that a scalar entity property is about to change 976 /// The current value of the specified property is cached when this method is called. 977 /// </summary> 978 /// <param name="entityMemberName">The name of the entity property that is changing</param> EntityMemberChanging(string entityMemberName)979 override internal void EntityMemberChanging(string entityMemberName) 980 { 981 if (this.IsKeyEntry) 982 { 983 throw EntityUtil.CannotAccessKeyEntryValues(); 984 } 985 this.EntityMemberChanging(entityMemberName, null, null); 986 } 987 988 /// <summary> 989 /// Used to report that a scalar entity property has been changed 990 /// The property value that was cached during EntityMemberChanging is now 991 /// added to OriginalValues 992 /// </summary> 993 /// <param name="entityMemberName">The name of the entity property that has changing</param> EntityMemberChanged(string entityMemberName)994 override internal void EntityMemberChanged(string entityMemberName) 995 { 996 if (this.IsKeyEntry) 997 { 998 throw EntityUtil.CannotAccessKeyEntryValues(); 999 } 1000 this.EntityMemberChanged(entityMemberName, null, null); 1001 } 1002 1003 /// <summary> 1004 /// Used to report that a complex property is about to change 1005 /// The current value of the specified property is cached when this method is called. 1006 /// </summary> 1007 /// <param name="entityMemberName">The name of the top-level entity property that is changing</param> 1008 /// <param name="complexObject">The complex object that contains the property that is changing</param> 1009 /// <param name="complexObjectMemberName">The name of the property that is changing on complexObject</param> EntityComplexMemberChanging(string entityMemberName, object complexObject, string complexObjectMemberName)1010 override internal void EntityComplexMemberChanging(string entityMemberName, object complexObject, string complexObjectMemberName) 1011 { 1012 if (this.IsKeyEntry) 1013 { 1014 throw EntityUtil.CannotAccessKeyEntryValues(); 1015 } 1016 EntityUtil.CheckArgumentNull(complexObjectMemberName, "complexObjectMemberName"); 1017 EntityUtil.CheckArgumentNull(complexObject, "complexObject"); 1018 this.EntityMemberChanging(entityMemberName, complexObject, complexObjectMemberName); 1019 } 1020 1021 /// <summary> 1022 /// Used to report that a complex property has been changed 1023 /// The property value that was cached during EntityMemberChanging is now added to OriginalValues 1024 /// </summary> 1025 /// <param name="entityMemberName">The name of the top-level entity property that has changed</param> 1026 /// <param name="complexObject">The complex object that contains the property that changed</param> 1027 /// <param name="complexObjectMemberName">The name of the property that changed on complexObject</param> EntityComplexMemberChanged(string entityMemberName, object complexObject, string complexObjectMemberName)1028 override internal void EntityComplexMemberChanged(string entityMemberName, object complexObject, string complexObjectMemberName) 1029 { 1030 if (this.IsKeyEntry) 1031 { 1032 throw EntityUtil.CannotAccessKeyEntryValues(); 1033 } 1034 EntityUtil.CheckArgumentNull(complexObjectMemberName, "complexObjectMemberName"); 1035 EntityUtil.CheckArgumentNull(complexObject, "complexObject"); 1036 this.EntityMemberChanged(entityMemberName, complexObject, complexObjectMemberName); 1037 } 1038 1039 #endregion 1040 1041 internal IEntityWrapper WrappedEntity 1042 { 1043 get 1044 { 1045 return _wrappedEntity; 1046 } 1047 } 1048 1049 /// <summary> 1050 /// Method called to complete the change tracking process on an entity property. The original property value 1051 /// is now saved in the original values record if there is not already an entry in the record for this property. 1052 /// The parameters to this method must have the same values as the parameter values passed to the last call to 1053 /// EntityValueChanging on this ObjectStateEntry. 1054 /// All inputs are in OSpace. 1055 /// </summary> 1056 /// <param name="entityMemberName">Name of the top-level entity property that has changed</param> 1057 /// <param name="complexObject">If entityMemberName refers to a complex property, this is the complex 1058 /// object that contains the change. Otherwise this is null.</param> 1059 /// <param name="complexObjectMemberName">If entityMemberName refers to a complex property, this is the name of 1060 /// the property that has changed on complexObject. Otherwise this is null.</param> EntityMemberChanged(string entityMemberName, object complexObject, string complexObjectMemberName)1061 private void EntityMemberChanged(string entityMemberName, object complexObject, string complexObjectMemberName) 1062 { 1063 string changingMemberName; 1064 StateManagerTypeMetadata typeMetadata; 1065 object changingObject; 1066 1067 // Get the metadata for the property that is changing, and verify that it is valid to change it for this entry 1068 // If something fails, we will clear out our cached values in the finally block, and require the user to submit another Changing notification 1069 try 1070 { 1071 int changingOrdinal = this.GetAndValidateChangeMemberInfo(entityMemberName, complexObject, complexObjectMemberName, 1072 out typeMetadata, out changingMemberName, out changingObject); 1073 1074 // if EntityKey is changing and is in a valid scenario for it to change, no further action is needed 1075 if (changingOrdinal == -2) 1076 { 1077 return; 1078 } 1079 1080 // Verify that the inputs to this call match the values we have cached 1081 if ((changingObject != _cache.ChangingObject) || 1082 (changingMemberName != _cache.ChangingMember) || 1083 (entityMemberName != _cache.ChangingEntityMember)) 1084 { 1085 throw EntityUtil.EntityValueChangedWithoutEntityValueChanging(); 1086 } 1087 1088 // check the state after the other values because if the other cached values have not been set and are null, it is more 1089 // intuitive to the user to get an error that specifically points to that as the problem, and in that case, the state will 1090 // also not be matched, so if we checked this first, it would cause a confusing error to be thrown. 1091 if (this.State != _cache.ChangingState) 1092 { 1093 throw EntityUtil.ChangedInDifferentStateFromChanging(this.State, _cache.ChangingState); 1094 } 1095 1096 object oldValue = _cache.ChangingOldValue; 1097 object newValue = null; 1098 StateManagerMemberMetadata memberMetadata = null; 1099 if (_cache.SaveOriginalValues) 1100 { 1101 memberMetadata = typeMetadata.Member(changingOrdinal); 1102 // Expand only non-null complex type values 1103 if (memberMetadata.IsComplex && oldValue != null) 1104 { 1105 // devnote: Not using GetCurrentEntityValue here because change tracking can only be done on OSpace members, 1106 // so we don't need to worry about shadow state, and we don't want a CSpace representation of complex objects 1107 newValue = memberMetadata.GetValue(changingObject); 1108 1109 ExpandComplexTypeAndAddValues(memberMetadata, oldValue, newValue, false); 1110 } 1111 else 1112 { 1113 AddOriginalValue(memberMetadata, changingObject, oldValue); 1114 } 1115 } 1116 1117 // if the property is a Foreign Key, let's clear out the appropriate EntityReference 1118 // UNLESS we are applying FK changes as part of DetectChanges where we don't want to 1119 // start changing references yet. If we are in the Align stage of DetectChanges, this is ok. 1120 TransactionManager transManager = ObjectStateManager.TransactionManager; 1121 List<Pair<string, string>> relationships; 1122 if (complexObject == null && // check if property is a top-level property 1123 (transManager.IsAlignChanges || !transManager.IsDetectChanges) && 1124 this.IsPropertyAForeignKey(entityMemberName, out relationships)) 1125 { 1126 foreach (var relationship in relationships) 1127 { 1128 string relationshipName = relationship.First; 1129 string targetRoleName = relationship.Second; 1130 1131 RelatedEnd relatedEnd = this.WrappedEntity.RelationshipManager.GetRelatedEndInternal(relationshipName, targetRoleName); 1132 Debug.Assert(relatedEnd != null, "relatedEnd should exist if property is a foreign key"); 1133 EntityReference reference = relatedEnd as EntityReference; 1134 Debug.Assert(reference != null, "relatedEnd should be an EntityReference"); 1135 1136 // Allow updating of other relationships that this FK property participates in except that 1137 // if we're doing fixup by references as part of AcceptChanges then don't allow a ref to 1138 // be changed. 1139 if (!transManager.IsFixupByReference) 1140 { 1141 if (memberMetadata == null) 1142 { 1143 memberMetadata = typeMetadata.Member(changingOrdinal); 1144 } 1145 if (newValue == null) 1146 { 1147 newValue = memberMetadata.GetValue(changingObject); 1148 } 1149 1150 bool hasConceptualNullFk = ForeignKeyFactory.IsConceptualNullKey(reference.CachedForeignKey); 1151 if (!ByValueEqualityComparer.Default.Equals(oldValue, newValue) || hasConceptualNullFk) 1152 { 1153 FixupEntityReferenceByForeignKey(reference); 1154 } 1155 } 1156 } 1157 } 1158 1159 // POCO: The state of the entry is not changed if the EntityMemberChanged method 1160 // was called from ObjectStateEntry.OriginalValues property. 1161 // The OriginalValues uses EntityMemberChanging/EntityMemberChanged to update snapshot of complex object in case 1162 // complex object was changed (not a scalar value). 1163 if (_cache != null && !_cache.TransactionManager.IsOriginalValuesGetter) 1164 { 1165 EntityState initialState = State; 1166 if (State != EntityState.Added) 1167 { 1168 State = EntityState.Modified; 1169 } 1170 if (State == EntityState.Modified) 1171 { 1172 SetModifiedProperty(entityMemberName); 1173 } 1174 if (initialState != this.State) 1175 { 1176 _cache.ChangeState(this, initialState, this.State); 1177 } 1178 } 1179 } 1180 finally 1181 { 1182 Debug.Assert(_cache != null, "Unexpected null state manager."); 1183 SetCachedChangingValues(null, null, null, EntityState.Detached, null); 1184 } 1185 } 1186 1187 // helper method used to set value of property SetCurrentEntityValue(string memberName, object newValue)1188 internal void SetCurrentEntityValue(string memberName, object newValue) 1189 { 1190 int ordinal = _cacheTypeMetadata.GetOrdinalforOLayerMemberName(memberName); 1191 SetCurrentEntityValue(_cacheTypeMetadata, ordinal, _wrappedEntity.Entity, newValue); 1192 } 1193 SetOriginalEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, object newValue)1194 internal void SetOriginalEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, object newValue) 1195 { 1196 ValidateState(); 1197 if (State == EntityState.Added) 1198 { 1199 throw EntityUtil.OriginalValuesDoesNotExist(); 1200 } 1201 1202 EntityState initialState = State; 1203 1204 object orgValue; // StateManagerValue 1205 object oldOriginalValue; // the actual value 1206 1207 // Update original values list 1208 StateManagerMemberMetadata memberMetadata = metadata.Member(ordinal); 1209 if (FindOriginalValue(memberMetadata, userObject, out orgValue)) 1210 { 1211 _originalValues.Remove((StateManagerValue)orgValue); 1212 } 1213 1214 if (memberMetadata.IsComplex) 1215 { 1216 oldOriginalValue = memberMetadata.GetValue(userObject); 1217 if (oldOriginalValue == null) 1218 { 1219 throw EntityUtil.NullableComplexTypesNotSupported(memberMetadata.CLayerName); 1220 } 1221 1222 IExtendedDataRecord newValueRecord = newValue as IExtendedDataRecord; 1223 if (newValueRecord != null) 1224 { 1225 // Requires materialization 1226 newValue = _cache.ComplexTypeMaterializer.CreateComplex(newValueRecord, newValueRecord.DataRecordInfo, null); 1227 } 1228 1229 // We only store scalar properties values in original values, so no need to search the list 1230 // if the property being set is complex. Just get the value as an OSpace object. 1231 ExpandComplexTypeAndAddValues(memberMetadata, oldOriginalValue, newValue, true); 1232 } 1233 else 1234 { 1235 AddOriginalValue(memberMetadata, userObject, newValue); 1236 } 1237 1238 if (initialState == EntityState.Unchanged) 1239 { 1240 State = EntityState.Modified; 1241 } 1242 } 1243 1244 /// <summary> 1245 /// Method called to start the change tracking process on an entity property. The current property value is cached at 1246 /// this stage in preparation for later storage in the original values record. Multiple successful calls to this method 1247 /// will overwrite the cached values. 1248 /// All inputs are in OSpace. 1249 /// </summary> 1250 /// <param name="entityMemberName">Name of the top-level entity property that is changing</param> 1251 /// <param name="complexObject">If entityMemberName refers to a complex property, this is the complex 1252 /// object that contains the change. Otherwise this is null.</param> 1253 /// <param name="complexObjectMemberName">If entityMemberName refers to a complex property, this is the name of 1254 /// the property that is changing on complexObject. Otherwise this is null.</param> EntityMemberChanging(string entityMemberName, object complexObject, string complexObjectMemberName)1255 private void EntityMemberChanging(string entityMemberName, object complexObject, string complexObjectMemberName) 1256 { 1257 string changingMemberName; 1258 StateManagerTypeMetadata typeMetadata; 1259 object changingObject; 1260 1261 // Get the metadata for the property that is changing, and verify that it is valid to change it for this entry 1262 int changingOrdinal = this.GetAndValidateChangeMemberInfo(entityMemberName, complexObject, complexObjectMemberName, 1263 out typeMetadata, out changingMemberName, out changingObject); 1264 1265 // if EntityKey is changing and is in a valid scenario for it to change, no further action is needed 1266 if (changingOrdinal == -2) 1267 { 1268 return; 1269 } 1270 1271 Debug.Assert(changingOrdinal != -1, "Expected GetAndValidateChangeMemberInfo to throw for a invalid property name"); 1272 1273 // Cache the current value for later storage in original values. If we are not in a state where we should update 1274 // the original values, we don't even need to bother saving the current value here. However, we will still cache 1275 // the other data regarding the change, so that we always require matching Changing and Changed calls, regardless of the state. 1276 StateManagerMemberMetadata memberMetadata = typeMetadata.Member(changingOrdinal); 1277 1278 // POCO 1279 // Entities which don't implement IEntityWithChangeTracker entity can already have original values even in the Unchanged state. 1280 _cache.SaveOriginalValues = (State == EntityState.Unchanged || State == EntityState.Modified) && 1281 !FindOriginalValue(memberMetadata, changingObject); 1282 1283 // devnote: Not using GetCurrentEntityValue here because change tracking can only be done on OSpace members, 1284 // so we don't need to worry about shadow state, and we don't want a CSpace representation of complex objects 1285 object oldValue = memberMetadata.GetValue(changingObject); 1286 1287 Debug.Assert(this.State != EntityState.Detached, "Change tracking should not happen on detached entities."); 1288 SetCachedChangingValues(entityMemberName, changingObject, changingMemberName, this.State, oldValue); 1289 } 1290 1291 // helper method used to get value of property GetOriginalEntityValue(string memberName)1292 internal object GetOriginalEntityValue(string memberName) 1293 { 1294 int ordinal = _cacheTypeMetadata.GetOrdinalforOLayerMemberName(memberName); 1295 return GetOriginalEntityValue(_cacheTypeMetadata, ordinal, _wrappedEntity.Entity, ObjectStateValueRecord.OriginalReadonly); 1296 } 1297 GetOriginalEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord)1298 internal object GetOriginalEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord) 1299 { 1300 Debug.Assert(updatableRecord != ObjectStateValueRecord.OriginalUpdatablePublic, "OriginalUpdatablePublic records must preserve complex type information, use the overload that takes parentEntityPropertyIndex"); 1301 return GetOriginalEntityValue(metadata, ordinal, userObject, updatableRecord, s_EntityRoot); 1302 } 1303 GetOriginalEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord, int parentEntityPropertyIndex)1304 internal object GetOriginalEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord, int parentEntityPropertyIndex) 1305 { 1306 // if original value is stored, then use it, otherwise use the current value from the entity 1307 ValidateState(); 1308 object retValue; 1309 StateManagerMemberMetadata member = metadata.Member(ordinal); 1310 if (FindOriginalValue(member, userObject, out retValue)) 1311 { 1312 // If the object is null, return DBNull.Value to be consistent with GetCurrentEntityValue 1313 return ((StateManagerValue)retValue).originalValue ?? DBNull.Value; 1314 } 1315 return GetCurrentEntityValue(metadata, ordinal, userObject, updatableRecord, parentEntityPropertyIndex); 1316 } 1317 GetCurrentEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord)1318 internal object GetCurrentEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord) 1319 { 1320 Debug.Assert(updatableRecord != ObjectStateValueRecord.OriginalUpdatablePublic, "OriginalUpdatablePublic records must preserve complex type information, use the overload that takes parentEntityPropertyIndex"); 1321 return GetCurrentEntityValue(metadata, ordinal, userObject, updatableRecord, s_EntityRoot); 1322 } 1323 GetCurrentEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord, int parentEntityPropertyIndex)1324 internal object GetCurrentEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord, int parentEntityPropertyIndex) 1325 { 1326 ValidateState(); 1327 1328 object retValue = null; 1329 StateManagerMemberMetadata member = metadata.Member(ordinal); 1330 Debug.Assert(null != member, "didn't throw ArgumentOutOfRangeException"); 1331 1332 if (!metadata.IsMemberPartofShadowState(ordinal)) 1333 { // if it is not shadow state 1334 retValue = member.GetValue(userObject); 1335 1336 // Wrap the value in a record if it is a non-null complex type 1337 if (member.IsComplex && retValue != null) 1338 { 1339 // need to get the new StateManagerTypeMetadata for nested /complext member 1340 switch (updatableRecord) 1341 { 1342 case ObjectStateValueRecord.OriginalReadonly: 1343 retValue = new ObjectStateEntryDbDataRecord(this, 1344 _cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType), retValue); 1345 break; 1346 case ObjectStateValueRecord.CurrentUpdatable: 1347 retValue = new ObjectStateEntryDbUpdatableDataRecord(this, 1348 _cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType), retValue); 1349 break; 1350 case ObjectStateValueRecord.OriginalUpdatableInternal: 1351 retValue = new ObjectStateEntryOriginalDbUpdatableDataRecord_Internal(this, 1352 _cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType), retValue); 1353 break; 1354 case ObjectStateValueRecord.OriginalUpdatablePublic: 1355 retValue = new ObjectStateEntryOriginalDbUpdatableDataRecord_Public(this, 1356 _cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType), retValue, parentEntityPropertyIndex); 1357 break; 1358 default: 1359 Debug.Assert(false, "shouldn't happen"); 1360 break; 1361 } 1362 // we need to pass the toplevel ordinal 1363 } 1364 } 1365 #if DEBUG // performance, don't do this work in retail until shadow state is supported 1366 else if (userObject == _wrappedEntity.Entity) 1367 { 1368 Debug.Assert(false, "shadowstate not supported"); 1369 #if SupportShadowState 1370 Debug.Assert(null != _currentValues, "shadow state without values"); 1371 _currentValues.TryGetValue(member.CLayerName, out retValue); // try to get it from shadow state if exists 1372 // we don't support CSpace only complex type 1373 #endif 1374 } 1375 #endif 1376 return retValue ?? DBNull.Value; 1377 } 1378 FindOriginalValue(StateManagerMemberMetadata metadata, object instance)1379 private bool FindOriginalValue(StateManagerMemberMetadata metadata, object instance) 1380 { 1381 object tmp; 1382 return FindOriginalValue(metadata, instance, out tmp); 1383 } 1384 FindOriginalValue(StateManagerMemberMetadata metadata, object instance, out object value)1385 internal bool FindOriginalValue(StateManagerMemberMetadata metadata, object instance, out object value) 1386 { 1387 bool found = false; 1388 object retValue = null; 1389 if (null != _originalValues) 1390 { 1391 foreach (StateManagerValue cachevalue in _originalValues) // this should include also shadow state 1392 { 1393 if (cachevalue.userObject == instance && cachevalue.memberMetadata == metadata) 1394 { 1395 found = true; 1396 retValue = cachevalue; 1397 break; 1398 } 1399 } 1400 } 1401 value = retValue; 1402 return found; 1403 } 1404 1405 // Get AssociationEndMember of current entry of given relationship 1406 // Relationship must be related to the current entry. GetAssociationEndMember(RelationshipEntry relationshipEntry)1407 internal AssociationEndMember GetAssociationEndMember(RelationshipEntry relationshipEntry) 1408 { 1409 Debug.Assert((object)this.EntityKey != null, "entry should have a not null EntityKey"); 1410 1411 ValidateState(); 1412 1413 AssociationEndMember endMember = relationshipEntry.RelationshipWrapper.GetAssociationEndMember(EntityKey); 1414 Debug.Assert(null != endMember, "should be one of the ends of the relationship"); 1415 return endMember; 1416 } 1417 1418 // Get entry which is on the other end of given relationship. 1419 // Relationship must be related to the current entry. GetOtherEndOfRelationship(RelationshipEntry relationshipEntry)1420 internal EntityEntry GetOtherEndOfRelationship(RelationshipEntry relationshipEntry) 1421 { 1422 Debug.Assert((object)this.EntityKey != null, "entry should have a not null EntityKey"); 1423 1424 return _cache.GetEntityEntry(relationshipEntry.RelationshipWrapper.GetOtherEntityKey(this.EntityKey)); 1425 } 1426 1427 1428 /// <summary> 1429 /// Helper method to recursively expand a complex object's values down to scalars for storage in the original values record. 1430 /// This method is used when a whole complex object is set on its parent object, instead of just setting 1431 /// individual scalar values on that object. 1432 /// </summary> 1433 /// <param name="memberMetadata">metadata for the complex property being expanded on the parent 1434 /// where the parent can be an entity or another complex object</param> 1435 /// <param name="oldComplexObject">Old value of the complex property. Scalar values from this object are stored in the original values record</param> 1436 /// <param name="newComplexObject">New value of the complex property. This object reference is used in the original value record and is 1437 /// associated with the scalar values for the same property on the oldComplexObject</param> 1438 /// <param name="useOldComplexObject">Whether or not to use the existing complex object in the original values or to use the original value that is already present </param> ExpandComplexTypeAndAddValues(StateManagerMemberMetadata memberMetadata, object oldComplexObject, object newComplexObject, bool useOldComplexObject)1439 private void ExpandComplexTypeAndAddValues(StateManagerMemberMetadata memberMetadata, object oldComplexObject, object newComplexObject, bool useOldComplexObject) 1440 { 1441 Debug.Assert(memberMetadata.IsComplex, "Cannot expand non-complex objects"); 1442 if (newComplexObject == null) 1443 { 1444 throw EntityUtil.NullableComplexTypesNotSupported(memberMetadata.CLayerName); 1445 } 1446 Debug.Assert(oldComplexObject == null || (oldComplexObject.GetType() == newComplexObject.GetType()), "Cannot replace a complex object with an object of a different type, unless the original one was null"); 1447 1448 StateManagerTypeMetadata typeMetadata = _cache.GetOrAddStateManagerTypeMetadata(memberMetadata.CdmMetadata.TypeUsage.EdmType); 1449 object retValue; 1450 for (int field = 0; field < typeMetadata.FieldCount; field++) 1451 { 1452 StateManagerMemberMetadata complexMemberMetadata = typeMetadata.Member(field); 1453 if (complexMemberMetadata.IsComplex) 1454 { 1455 object oldComplexMemberValue = null; 1456 if (oldComplexObject != null) 1457 { 1458 oldComplexMemberValue = complexMemberMetadata.GetValue(oldComplexObject); 1459 if (oldComplexMemberValue == null && FindOriginalValue(complexMemberMetadata, oldComplexObject, out retValue)) 1460 { 1461 _originalValues.Remove((StateManagerValue)retValue); 1462 } 1463 } 1464 ExpandComplexTypeAndAddValues(complexMemberMetadata, oldComplexMemberValue, complexMemberMetadata.GetValue(newComplexObject), useOldComplexObject); 1465 } 1466 else 1467 { 1468 object originalValue = null; 1469 object complexObject = newComplexObject; 1470 1471 if (useOldComplexObject) 1472 { 1473 // Set the original values using the existing current value object 1474 // complexObject --> the existing complex object 1475 // originalValue --> the new value to set for this member 1476 originalValue = complexMemberMetadata.GetValue(newComplexObject); 1477 complexObject = oldComplexObject; 1478 } 1479 else 1480 { 1481 if (oldComplexObject != null) 1482 { 1483 // If we already have an entry for this property in the original values list, we need to remove it. We can't just 1484 // update it because StateManagerValue is a struct and there is no way to get a reference to the entry in the list. 1485 originalValue = complexMemberMetadata.GetValue(oldComplexObject); 1486 if (FindOriginalValue(complexMemberMetadata, oldComplexObject, out retValue)) 1487 { 1488 StateManagerValue originalStateValue = ((StateManagerValue)retValue); 1489 _originalValues.Remove(originalStateValue); 1490 originalValue = originalStateValue.originalValue; 1491 } 1492 else 1493 { 1494 Debug.Assert(this.Entity is IEntityWithChangeTracker, "for POCO objects the snapshot should contain all original values"); 1495 } 1496 } 1497 else 1498 { 1499 originalValue = complexMemberMetadata.GetValue(newComplexObject); 1500 } 1501 } 1502 1503 1504 // Add the new entry. The userObject will reference the new complex object that is currently being set. 1505 // If the value was in the list previously, we will still use the old value with the new object reference. 1506 // That will ensure that we preserve the old value while still maintaining the link to the 1507 // existing complex object that is attached to the entity or parent complex object. If an entry is already 1508 // in the list this means that it was either explicitly set by the user or the entire complex type was previously 1509 // set and expanded down to the individual properties. In either case we do the same thing. 1510 AddOriginalValue(complexMemberMetadata, complexObject, originalValue); 1511 } 1512 } 1513 } 1514 1515 /// <summary> 1516 /// Helper method to validate that the property names being reported as changing/changed are valid for this entity and that 1517 /// the entity is in a valid state for the change request. Also determines if this is a change on a complex object, and 1518 /// returns the appropriate metadata and object to be used for the rest of the changing and changed operations. 1519 /// </summary> 1520 /// <param name="entityMemberName">Top-level entity property name</param> 1521 /// <param name="complexObject">Complex object that contains the change, null if the change is on a top-level entity property</param> 1522 /// <param name="complexObjectMemberName">Name of the property that is changing on the complexObject, null for top-level entity properties</param> 1523 /// <param name="typeMetadata">Metadata for the type that contains the change, either for the entity itself or for the complex object</param> 1524 /// <param name="changingMemberName">Property name that is actually changing -- either entityMemberName for entities or 1525 /// complexObjectMemberName for complex objects</param> 1526 /// <param name="changingObject">Object reference that contains the change, either the entity or complex object 1527 /// as appropriate for the requested change</param> 1528 /// <returns>Ordinal of the property that is changing, or -2 if the EntityKey is changing in a valid scenario. This is relative 1529 /// to the returned typeMetadata. Throws exceptions if the requested property name(s) are invalid for this entity.</returns> GetAndValidateChangeMemberInfo(string entityMemberName, object complexObject, string complexObjectMemberName, out StateManagerTypeMetadata typeMetadata, out string changingMemberName, out object changingObject)1530 internal int GetAndValidateChangeMemberInfo(string entityMemberName, object complexObject, string complexObjectMemberName, 1531 out StateManagerTypeMetadata typeMetadata, out string changingMemberName, out object changingObject) 1532 { 1533 typeMetadata = null; 1534 changingMemberName = null; 1535 changingObject = null; 1536 1537 EntityUtil.CheckArgumentNull(entityMemberName, "entityMemberName"); 1538 // complexObject and complexObjectMemberName are allowed to be null here for change tracking on top-level entity properties 1539 1540 ValidateState(); 1541 1542 int changingOrdinal = _cacheTypeMetadata.GetOrdinalforOLayerMemberName(entityMemberName); 1543 if (changingOrdinal == -1) 1544 { 1545 if (entityMemberName == StructuralObject.EntityKeyPropertyName) 1546 { 1547 // Setting EntityKey property is only allowed from here when we are in the middle of relationship fixup. 1548 if (!_cache.InRelationshipFixup) 1549 { 1550 throw EntityUtil.CantSetEntityKey(); 1551 } 1552 else 1553 { 1554 // If we are in fixup, there is nothing more to do here with EntityKey, so just 1555 // clear the saved changing values and return. This will ensure that we behave 1556 // the same with the change notifications on EntityKey as with other properties. 1557 // I.e. we still don't allow the following: 1558 // EntityMemberChanging("Property1") 1559 // EntityMemberChanging("EntityKey") 1560 // EntityMemberChanged("EntityKey") 1561 // EntityMemberChanged("Property1") 1562 Debug.Assert(this.State != EntityState.Detached, "Change tracking should not happen on detached entities."); 1563 SetCachedChangingValues(null, null, null, this.State, null); 1564 return -2; 1565 } 1566 } 1567 else 1568 { 1569 throw EntityUtil.ChangeOnUnmappedProperty(entityMemberName); 1570 } 1571 } 1572 else 1573 { 1574 StateManagerTypeMetadata tmpTypeMetadata; 1575 string tmpChangingMemberName; 1576 object tmpChangingObject; 1577 1578 // entityMemberName is a confirmed valid property on the Entity, but if this is a complex type we also need to validate its property 1579 if (complexObject != null) 1580 { 1581 // a complex object was provided, but the top-level Entity property is not complex 1582 if (!_cacheTypeMetadata.Member(changingOrdinal).IsComplex) 1583 { 1584 throw EntityUtil.ComplexChangeRequestedOnScalarProperty(entityMemberName); 1585 } 1586 1587 tmpTypeMetadata = _cache.GetOrAddStateManagerTypeMetadata(complexObject.GetType(), (EntitySet)this.EntitySet); 1588 changingOrdinal = tmpTypeMetadata.GetOrdinalforOLayerMemberName(complexObjectMemberName); 1589 if (changingOrdinal == -1) 1590 { 1591 throw EntityUtil.ChangeOnUnmappedComplexProperty(complexObjectMemberName); 1592 } 1593 1594 tmpChangingMemberName = complexObjectMemberName; 1595 tmpChangingObject = complexObject; 1596 } 1597 else 1598 { 1599 tmpTypeMetadata = _cacheTypeMetadata; 1600 tmpChangingMemberName = entityMemberName; 1601 tmpChangingObject = this.Entity; 1602 if (WrappedEntity.IdentityType != Entity.GetType() && // Is a proxy 1603 Entity is IEntityWithChangeTracker && // Is a full proxy 1604 IsPropertyAForeignKey(entityMemberName)) // Property is part of FK 1605 { 1606 // Set a flag so that we don't try to set FK properties while already in a setter. 1607 _cache.EntityInvokingFKSetter = WrappedEntity.Entity; 1608 } 1609 } 1610 1611 VerifyEntityValueIsEditable(tmpTypeMetadata, changingOrdinal, tmpChangingMemberName); 1612 1613 typeMetadata = tmpTypeMetadata; 1614 changingMemberName = tmpChangingMemberName; 1615 changingObject = tmpChangingObject; 1616 return changingOrdinal; 1617 } 1618 } 1619 1620 /// <summary> 1621 /// Helper method to set the information needed for the change tracking cache. Ensures that all of these values get set together. 1622 /// </summary> SetCachedChangingValues(string entityMemberName, object changingObject, string changingMember, EntityState changingState, object oldValue)1623 private void SetCachedChangingValues(string entityMemberName, object changingObject, string changingMember, EntityState changingState, object oldValue) 1624 { 1625 _cache.ChangingEntityMember = entityMemberName; 1626 _cache.ChangingObject = changingObject; 1627 _cache.ChangingMember = changingMember; 1628 _cache.ChangingState = changingState; 1629 _cache.ChangingOldValue = oldValue; 1630 if (changingState == EntityState.Detached) 1631 { 1632 _cache.SaveOriginalValues = false; 1633 } 1634 } 1635 1636 [DebuggerBrowsable(DebuggerBrowsableState.Never)] // don't have debugger view expand this 1637 internal OriginalValueRecord EditableOriginalValues 1638 { 1639 get 1640 { 1641 Debug.Assert(!this.IsKeyEntry, "should not edit original key entry"); 1642 Debug.Assert(EntityState.Modified == State || 1643 EntityState.Deleted == State || 1644 EntityState.Unchanged == State, 1645 "only expecting Modified or Deleted state"); 1646 1647 return new ObjectStateEntryOriginalDbUpdatableDataRecord_Internal(this, _cacheTypeMetadata, _wrappedEntity.Entity); 1648 } 1649 } 1650 DetachObjectStateManagerFromEntity()1651 internal void DetachObjectStateManagerFromEntity() 1652 { 1653 // This method can be called on relationship entries where there is no entity 1654 if (!this.IsKeyEntry) // _wrappedEntity.Entity is not null. 1655 { 1656 _wrappedEntity.SetChangeTracker(null); 1657 _wrappedEntity.DetachContext(); 1658 1659 if (!this._cache.TransactionManager.IsAttachTracking || 1660 this._cache.TransactionManager.OriginalMergeOption != MergeOption.NoTracking) 1661 { 1662 // If AttachTo() failed while attaching graph retrieved with NoTracking option, 1663 // we don't want to reset the EntityKey 1664 1665 //Entry's this._entityKey is set to null at the caller, maintaining consistency between entityWithKey.EntityKey and this.EntityKey 1666 _wrappedEntity.EntityKey = null; 1667 } 1668 } 1669 } 1670 1671 // This method is used for entities which don't implement IEntityWithChangeTracker to store orignal values of properties 1672 // which are later used to detect changes in properties TakeSnapshot(bool onlySnapshotComplexProperties)1673 internal void TakeSnapshot(bool onlySnapshotComplexProperties) 1674 { 1675 Debug.Assert(!this.IsKeyEntry); 1676 1677 if (this.State != EntityState.Added) 1678 { 1679 StateManagerTypeMetadata metadata = this._cacheTypeMetadata; 1680 1681 int fieldCount = this.GetFieldCount(metadata); 1682 object currentValue; 1683 1684 for (int i = 0; i < fieldCount; i++) 1685 { 1686 StateManagerMemberMetadata member = metadata.Member(i); 1687 if (member.IsComplex) 1688 { 1689 // memberValue is a complex object 1690 currentValue = member.GetValue(this._wrappedEntity.Entity); 1691 this.AddComplexObjectSnapshot(this.Entity, i, currentValue); 1692 this.TakeSnapshotOfComplexType(member, currentValue); 1693 } 1694 else if (!onlySnapshotComplexProperties) 1695 { 1696 currentValue = member.GetValue(this._wrappedEntity.Entity); 1697 this.AddOriginalValue(member, this._wrappedEntity.Entity, currentValue); 1698 } 1699 } 1700 } 1701 1702 this.TakeSnapshotOfForeignKeys(); 1703 } 1704 TakeSnapshotOfForeignKeys()1705 internal void TakeSnapshotOfForeignKeys() 1706 { 1707 Dictionary<RelatedEnd, HashSet<EntityKey>> keys; 1708 this.FindRelatedEntityKeysByForeignKeys(out keys, useOriginalValues: false); 1709 if (keys != null) 1710 { 1711 foreach (var pair in keys) 1712 { 1713 EntityReference reference = pair.Key as EntityReference; 1714 Debug.Assert(reference != null, "EntityReference expected"); 1715 Debug.Assert(pair.Value.Count == 1, "Unexpected number of keys"); 1716 1717 if (!ForeignKeyFactory.IsConceptualNullKey(reference.CachedForeignKey)) 1718 { 1719 reference.SetCachedForeignKey(pair.Value.First(), this); 1720 } 1721 } 1722 } 1723 } 1724 TakeSnapshotOfComplexType(StateManagerMemberMetadata member, object complexValue)1725 private void TakeSnapshotOfComplexType(StateManagerMemberMetadata member, object complexValue) 1726 { 1727 Debug.Assert(member.IsComplex, "Cannot expand non-complex objects"); 1728 1729 // Skip null values 1730 if (complexValue == null) 1731 return; 1732 1733 StateManagerTypeMetadata typeMetadata = _cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType); 1734 for (int i = 0; i < typeMetadata.FieldCount; i++) 1735 { 1736 StateManagerMemberMetadata complexMember = typeMetadata.Member(i); 1737 object currentValue = complexMember.GetValue(complexValue); 1738 if (complexMember.IsComplex) 1739 { 1740 // Recursive call for nested complex types 1741 // For POCO objects we have to store a reference to the original complex object 1742 this.AddComplexObjectSnapshot(complexValue, i, currentValue); 1743 TakeSnapshotOfComplexType(complexMember, currentValue); 1744 } 1745 else 1746 { 1747 if (!FindOriginalValue(complexMember, complexValue)) 1748 { 1749 AddOriginalValue(complexMember, complexValue, currentValue); 1750 } 1751 } 1752 } 1753 } 1754 AddComplexObjectSnapshot(object userObject, int ordinal, object complexObject)1755 private void AddComplexObjectSnapshot(object userObject, int ordinal, object complexObject) 1756 { 1757 Debug.Assert(userObject != null, "null userObject"); 1758 Debug.Assert(ordinal >= 0, "invalid ordinal"); 1759 1760 if (complexObject == null) 1761 { 1762 return; 1763 } 1764 1765 // Verify if the same complex object is not used multiple times. 1766 this.CheckForDuplicateComplexObjects(complexObject); 1767 1768 if (this._originalComplexObjects == null) 1769 { 1770 this._originalComplexObjects = new Dictionary<object, Dictionary<int, object>>(); 1771 } 1772 Dictionary<int, object> ordinal2complexObject; 1773 if (!this._originalComplexObjects.TryGetValue(userObject, out ordinal2complexObject)) 1774 { 1775 ordinal2complexObject = new Dictionary<int, object>(); 1776 this._originalComplexObjects.Add(userObject, ordinal2complexObject); 1777 } 1778 1779 Debug.Assert(!ordinal2complexObject.ContainsKey(ordinal), "shouldn't contain this ordinal yet"); 1780 ordinal2complexObject.Add(ordinal, complexObject); 1781 } 1782 CheckForDuplicateComplexObjects(object complexObject)1783 private void CheckForDuplicateComplexObjects(object complexObject) 1784 { 1785 if (this._originalComplexObjects == null || complexObject == null) 1786 return; 1787 1788 foreach (Dictionary<int, object> ordinal2complexObject in this._originalComplexObjects.Values) 1789 { 1790 foreach (object oldComplexObject in ordinal2complexObject.Values) 1791 { 1792 if (Object.ReferenceEquals(complexObject, oldComplexObject)) 1793 { 1794 throw new InvalidOperationException(System.Data.Entity.Strings.ObjectStateEntry_ComplexObjectUsedMultipleTimes(this.Entity.GetType().FullName, complexObject.GetType().FullName)); 1795 } 1796 } 1797 } 1798 } 1799 1800 /// <summary> 1801 /// Uses DetectChanges to determine whether or not the current value of the property with the given 1802 /// name is different from its original value. Note that this may be different from the property being 1803 /// marked as modified since a property which has not changed can still be marked as modified. 1804 /// </summary> 1805 /// <remarks> 1806 /// For complex properties, a new instance of the complex object which has all the same property 1807 /// values as the original instance is not considered to be different by this method. 1808 /// </remarks> 1809 /// <param name="propertyName">The name of the property.</param> 1810 /// <returns>True if the property has changed; false otherwise.</returns> IsPropertyChanged(string propertyName)1811 public override bool IsPropertyChanged(string propertyName) 1812 { 1813 return DetectChangesInProperty(ValidateAndGetOrdinalForProperty(propertyName, "IsPropertyChanged"), 1814 detectOnlyComplexProperties: false, detectOnly: true); 1815 } 1816 DetectChangesInProperty(int ordinal, bool detectOnlyComplexProperties, bool detectOnly)1817 private bool DetectChangesInProperty(int ordinal, bool detectOnlyComplexProperties, bool detectOnly) 1818 { 1819 bool changeDetected = false; 1820 StateManagerMemberMetadata member = _cacheTypeMetadata.Member(ordinal); 1821 var currentValue = member.GetValue(this._wrappedEntity.Entity); 1822 if (member.IsComplex) 1823 { 1824 if (this.State != EntityState.Deleted) 1825 { 1826 var oldComplexValue = this.GetComplexObjectSnapshot(this.Entity, ordinal); 1827 bool complexObjectInstanceChanged = this.DetectChangesInComplexType(member, member, currentValue, oldComplexValue, ref changeDetected, detectOnly); 1828 if (complexObjectInstanceChanged) 1829 { 1830 // instance of complex object was changed 1831 1832 // Before updating the snapshot verify if the same complex object is not used multiple times. 1833 this.CheckForDuplicateComplexObjects(currentValue); 1834 1835 if (!detectOnly) 1836 { 1837 // equivalent of EntityObject.ReportPropertyChanging() 1838 ((IEntityChangeTracker)this).EntityMemberChanging(member.CLayerName); 1839 1840 Debug.Assert(_cache.SaveOriginalValues, "complex object instance was changed so the SaveOriginalValues flag should be set to true"); 1841 1842 // Since the EntityMemberChanging method is called AFTER the complex object was changed, it means that 1843 // the EntityMemberChanging method was unable to find the real oldValue. 1844 // The real old value is stored for POCO objects in _originalComplexObjects dictionary. 1845 // The cached changing oldValue has to be updated with the real oldValue. 1846 _cache.ChangingOldValue = oldComplexValue; 1847 1848 // equivalent of EntityObject.ReportPropertyChanged() 1849 ((IEntityChangeTracker)this).EntityMemberChanged(member.CLayerName); 1850 } 1851 1852 // The _originalComplexObjects should always contain references to the values of complex objects which are "original" 1853 // at the moment of calling GetComplexObjectSnapshot(). They are used to get original scalar values from _originalValues. 1854 this.UpdateComplexObjectSnapshot(member, this.Entity, ordinal, currentValue); 1855 1856 if (!changeDetected) 1857 { 1858 // If we haven't already detected a change then we need to check the properties of the complex 1859 // object to see if there are any changes so that IsPropertyChanged will not skip reporting the 1860 // change just because the object reference has changed. 1861 DetectChangesInComplexType(member, member, currentValue, oldComplexValue, ref changeDetected, detectOnly); 1862 } 1863 } 1864 } 1865 } 1866 else if (!detectOnlyComplexProperties) 1867 { 1868 object originalStateManagerValue; 1869 var originalValueFound = this.FindOriginalValue(member, this._wrappedEntity.Entity, out originalStateManagerValue); 1870 1871 Debug.Assert(originalValueFound, "Original value not found even after snapshot."); 1872 1873 var originalValue = ((StateManagerValue)originalStateManagerValue).originalValue; 1874 if (!Object.Equals(currentValue, originalValue)) 1875 { 1876 changeDetected = true; 1877 1878 // Key property - throw if the actual byte values have changed, otherwise ignore the change 1879 if (member.IsPartOfKey) 1880 { 1881 if (!ByValueEqualityComparer.Default.Equals(currentValue, originalValue)) 1882 { 1883 throw EntityUtil.CannotModifyKeyProperty(member.CLayerName); 1884 } 1885 } 1886 else 1887 { 1888 if (this.State != EntityState.Deleted && !detectOnly) 1889 { 1890 // equivalent of EntityObject.ReportPropertyChanging() 1891 ((IEntityChangeTracker)this).EntityMemberChanging(member.CLayerName); 1892 1893 // equivalent of EntityObject.ReportPropertyChanged() 1894 ((IEntityChangeTracker)this).EntityMemberChanged(member.CLayerName); 1895 } 1896 } 1897 } 1898 } 1899 1900 return changeDetected; 1901 } 1902 1903 // This method uses original values stored in the ObjectStateEntry to detect changes in values of entity's properties DetectChangesInProperties(bool detectOnlyComplexProperties)1904 internal void DetectChangesInProperties(bool detectOnlyComplexProperties) 1905 { 1906 Debug.Assert(!this.IsKeyEntry, "Entry should be an EntityEntry"); 1907 Debug.Assert(this.State != EntityState.Added, "This method should not be called for entries in Added state"); 1908 1909 int fieldCount = GetFieldCount(_cacheTypeMetadata); 1910 for (int i = 0; i < fieldCount; i++) 1911 { 1912 DetectChangesInProperty(i, detectOnlyComplexProperties, detectOnly: false); 1913 } 1914 } 1915 DetectChangesInComplexType( StateManagerMemberMetadata topLevelMember, StateManagerMemberMetadata complexMember, object complexValue, object oldComplexValue, ref bool changeDetected, bool detectOnly)1916 private bool DetectChangesInComplexType( 1917 StateManagerMemberMetadata topLevelMember, 1918 StateManagerMemberMetadata complexMember, 1919 object complexValue, 1920 object oldComplexValue, 1921 ref bool changeDetected, 1922 bool detectOnly) 1923 { 1924 Debug.Assert(complexMember.IsComplex, "Cannot expand non-complex objects"); 1925 1926 if (complexValue == null) 1927 { 1928 // If the values are just null, do not detect this as a change 1929 if (oldComplexValue == null) 1930 { 1931 return false; 1932 } 1933 throw EntityUtil.NullableComplexTypesNotSupported(complexMember.CLayerName); 1934 } 1935 1936 if (!Object.ReferenceEquals(oldComplexValue, complexValue)) 1937 { 1938 // Complex object instance was changed. The calling method will update the snapshot of this object. 1939 return true; 1940 } 1941 1942 Debug.Assert(oldComplexValue != null, "original complex type value should not be null at this point"); 1943 1944 StateManagerTypeMetadata metadata = _cache.GetOrAddStateManagerTypeMetadata(complexMember.CdmMetadata.TypeUsage.EdmType); 1945 for (int i = 0; i < GetFieldCount(metadata); i++) 1946 { 1947 StateManagerMemberMetadata member = metadata.Member(i); 1948 object currentValue = null; 1949 currentValue = member.GetValue(complexValue); 1950 if (member.IsComplex) 1951 { 1952 if (this.State != EntityState.Deleted) 1953 { 1954 var oldNestedComplexValue = this.GetComplexObjectSnapshot(complexValue, i); 1955 bool complexObjectInstanceChanged = DetectChangesInComplexType(topLevelMember, member, currentValue, oldNestedComplexValue, ref changeDetected, detectOnly); 1956 if (complexObjectInstanceChanged) 1957 { 1958 // instance of complex object was changed 1959 1960 // Before updating the snapshot verify if the same complex object is not used multiple times. 1961 this.CheckForDuplicateComplexObjects(currentValue); 1962 1963 if (!detectOnly) 1964 { 1965 // equivalent of EntityObject.ReportComplexPropertyChanging() 1966 ((IEntityChangeTracker)this).EntityComplexMemberChanging(topLevelMember.CLayerName, complexValue, member.CLayerName); 1967 1968 // Since the EntityComplexMemberChanging method is called AFTER the complex object was changed, it means that 1969 // the EntityComplexMemberChanging method was unable to find real oldValue. 1970 // The real old value is stored for POCO objects in _originalComplexObjects dictionary. 1971 // The cached changing oldValue has to be updated with the real oldValue. 1972 _cache.ChangingOldValue = oldNestedComplexValue; 1973 1974 // equivalent of EntityObject.ReportComplexPropertyChanged() 1975 ((IEntityChangeTracker)this).EntityComplexMemberChanged(topLevelMember.CLayerName, complexValue, member.CLayerName); 1976 } 1977 // The _originalComplexObjects should always contain references to the values of complex objects which are "original" 1978 // at the moment of calling GetComplexObjectSnapshot(). They are used to get original scalar values from _originalValues. 1979 this.UpdateComplexObjectSnapshot(member, complexValue, i, currentValue); 1980 1981 if (!changeDetected) 1982 { 1983 DetectChangesInComplexType(topLevelMember, member, currentValue, oldNestedComplexValue, ref changeDetected, detectOnly); 1984 } 1985 } 1986 } 1987 } 1988 else 1989 { 1990 object originalStateManagerValue; 1991 bool originalValueFound = FindOriginalValue(member, complexValue, out originalStateManagerValue); 1992 1993 // originalValueFound will be false if the complex value was initially null since then its original 1994 // values will always be null, in which case all original scalar properties of the complex value are 1995 // considered null. 1996 if (!Object.Equals(currentValue, originalValueFound ? ((StateManagerValue)originalStateManagerValue).originalValue : null)) 1997 { 1998 changeDetected = true; 1999 2000 Debug.Assert(!member.IsPartOfKey, "Found member of complex type that is part of a key"); 2001 2002 if (!detectOnly) 2003 { 2004 // equivalent of EntityObject.ReportComplexPropertyChanging() 2005 ((IEntityChangeTracker)this).EntityComplexMemberChanging(topLevelMember.CLayerName, complexValue, member.CLayerName); 2006 2007 // equivalent of EntityObject.ReportComplexPropertyChanged() 2008 ((IEntityChangeTracker)this).EntityComplexMemberChanged(topLevelMember.CLayerName, complexValue, member.CLayerName); 2009 } 2010 } 2011 } 2012 } 2013 2014 // Scalar value in a complex object was changed 2015 return false; 2016 } 2017 GetComplexObjectSnapshot(object parentObject, int parentOrdinal)2018 private object GetComplexObjectSnapshot(object parentObject, int parentOrdinal) 2019 { 2020 object oldComplexObject = null; 2021 if (this._originalComplexObjects != null) 2022 { 2023 Dictionary<int, object> ordinal2complexObject; 2024 if (this._originalComplexObjects.TryGetValue(parentObject, out ordinal2complexObject)) 2025 { 2026 ordinal2complexObject.TryGetValue(parentOrdinal, out oldComplexObject); 2027 } 2028 } 2029 return oldComplexObject; 2030 } 2031 2032 // The _originalComplexObjects should always contain references to the values of complex objects which are "original" 2033 // at the moment of calling GetComplexObjectSnapshot(). They are used to get original scalar values from _originalValues 2034 // and to check if complex object instance was changed. 2035 // This method should be called after EntityMemberChanged in POCO case. UpdateComplexObjectSnapshot(StateManagerMemberMetadata member, object userObject, int ordinal, object currentValue)2036 internal void UpdateComplexObjectSnapshot(StateManagerMemberMetadata member, object userObject, int ordinal, object currentValue) 2037 { 2038 bool requiresAdd = true; 2039 if (this._originalComplexObjects != null) 2040 { 2041 Dictionary<int, object> ordinal2complexObject; 2042 if (this._originalComplexObjects.TryGetValue(userObject, out ordinal2complexObject)) 2043 { 2044 Debug.Assert(ordinal2complexObject != null, "value should already exists"); 2045 2046 object oldValue; 2047 ordinal2complexObject.TryGetValue(ordinal, out oldValue); 2048 // oldValue may be null if the complex object was attached with a null value 2049 ordinal2complexObject[ordinal] = currentValue; 2050 2051 // check nested complex objects (if they exist) 2052 if (oldValue != null && this._originalComplexObjects.TryGetValue(oldValue, out ordinal2complexObject)) 2053 { 2054 this._originalComplexObjects.Remove(oldValue); 2055 this._originalComplexObjects.Add(currentValue, ordinal2complexObject); 2056 2057 StateManagerTypeMetadata typeMetadata = _cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType); 2058 for (int i = 0; i < typeMetadata.FieldCount; i++) 2059 { 2060 StateManagerMemberMetadata complexMember = typeMetadata.Member(i); 2061 if (complexMember.IsComplex) 2062 { 2063 object nestedValue = complexMember.GetValue(currentValue); 2064 // Recursive call for nested complex objects 2065 UpdateComplexObjectSnapshot(complexMember, currentValue, i, nestedValue); 2066 } 2067 } 2068 } 2069 requiresAdd = false; 2070 } 2071 } 2072 if(requiresAdd) 2073 { 2074 AddComplexObjectSnapshot(userObject, ordinal, currentValue); 2075 } 2076 } 2077 2078 /// <summary> 2079 /// Processes each dependent end of an FK relationship in this entity and determines if a nav 2080 /// prop is set to a principal. If it is, and if the principal is Unchanged or Modified, 2081 /// then the primary key value is taken from the principal and used to fixup the FK value. 2082 /// This is called during AddObject so that references set from the added object will take 2083 /// precedence over FK values such that there is no need for the user to set FK values 2084 /// explicitly. If a conflict in the FK value is encountered due to an overlapping FK 2085 /// that is tied to two different PK values, then an exception is thrown. 2086 /// Note that references to objects that are not yet tracked by the context are ignored, since 2087 /// they will ultimately be brought into the context as Added objects, at which point we would 2088 /// have skipped them anyway because the are not Unchanged or Modified. 2089 /// </summary> FixupFKValuesFromNonAddedReferences()2090 internal void FixupFKValuesFromNonAddedReferences() 2091 { 2092 Debug.Assert(EntitySet is EntitySet, "Expect entity entries to have true entity sets."); 2093 if (!((EntitySet)EntitySet).HasForeignKeyRelationships) 2094 { 2095 return; 2096 } 2097 2098 // Keep track of all FK values that have already been set so that we can detect conflicts. 2099 var changedFKs = new Dictionary<int, object>(); 2100 foreach (Tuple<AssociationSet, ReferentialConstraint> dependent in ForeignKeyDependents) 2101 { 2102 var reference = RelationshipManager.GetRelatedEndInternal(dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name) as EntityReference; 2103 Debug.Assert(reference != null, "Expected reference to exist and be an entity reference (not collection)"); 2104 2105 if (reference.TargetAccessor.HasProperty) 2106 { 2107 var principal = WrappedEntity.GetNavigationPropertyValue(reference); 2108 if (principal != null) 2109 { 2110 ObjectStateEntry principalEntry; 2111 if (_cache.TryGetObjectStateEntry(principal, out principalEntry) && 2112 (principalEntry.State == EntityState.Modified || principalEntry.State == EntityState.Unchanged)) 2113 { 2114 Debug.Assert(principalEntry is EntityEntry, "Existing entry for an entity must be an EntityEntry, not a RelationshipEntry"); 2115 reference.UpdateForeignKeyValues(WrappedEntity, ((EntityEntry)principalEntry).WrappedEntity, changedFKs, forceChange: false); 2116 } 2117 } 2118 } 2119 } 2120 } 2121 2122 // Method used for entities which don't implement IEntityWithRelationships TakeSnapshotOfRelationships()2123 internal void TakeSnapshotOfRelationships() 2124 { 2125 Debug.Assert(this._wrappedEntity != null, "wrapped entity shouldn't be null"); 2126 Debug.Assert(!(this._wrappedEntity.Entity is IEntityWithRelationships), "this method should be called only for entities which don't implement IEntityWithRelationships"); 2127 2128 RelationshipManager rm = this._wrappedEntity.RelationshipManager; 2129 2130 StateManagerTypeMetadata metadata = this._cacheTypeMetadata; 2131 2132 ReadOnlyMetadataCollection<NavigationProperty> navigationProperties = 2133 (metadata.CdmMetadata.EdmType as EntityType).NavigationProperties; 2134 2135 foreach (NavigationProperty n in navigationProperties) 2136 { 2137 RelatedEnd relatedEnd = rm.GetRelatedEndInternal(n.RelationshipType.FullName, n.ToEndMember.Name); 2138 object val = this.WrappedEntity.GetNavigationPropertyValue(relatedEnd); 2139 2140 if (val != null) 2141 { 2142 if (n.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many) 2143 { 2144 // Collection 2145 IEnumerable collection = val as IEnumerable; 2146 if (collection == null) 2147 { 2148 throw new EntityException(System.Data.Entity.Strings.ObjectStateEntry_UnableToEnumerateCollection(n.Name, this.Entity.GetType().FullName)); 2149 } 2150 2151 foreach (object o in collection) 2152 { 2153 // Skip nulls in collections 2154 if (o != null) 2155 { 2156 this.TakeSnapshotOfSingleRelationship(relatedEnd, n, o); 2157 } 2158 } 2159 } 2160 else 2161 { 2162 // Reference 2163 this.TakeSnapshotOfSingleRelationship(relatedEnd, n, val); 2164 } 2165 } 2166 } 2167 } 2168 TakeSnapshotOfSingleRelationship(RelatedEnd relatedEnd, NavigationProperty n, object o)2169 private void TakeSnapshotOfSingleRelationship(RelatedEnd relatedEnd, NavigationProperty n, object o) 2170 { 2171 // Related entity can be already attached, so find the existing entry 2172 EntityEntry relatedEntry = this.ObjectStateManager.FindEntityEntry(o); 2173 IEntityWrapper relatedWrapper; 2174 2175 if (relatedEntry != null) 2176 { 2177 Debug.Assert(this.ObjectStateManager.TransactionManager.IsAddTracking || 2178 this.ObjectStateManager.TransactionManager.IsAttachTracking, "Should be inside Attach or Add"); 2179 2180 //relatedEntry.VerifyOrUpdateRelatedEnd(n, this._wrappedEntity); 2181 relatedWrapper = relatedEntry._wrappedEntity; 2182 2183 // In case of unidirectional relationships, it is possible that the other end of relationship was already added 2184 // to the context but its relationship manager doesn't contain proper related end with the current entity. 2185 // In OSM we treat all relationships as bidirectional so the related end has to be updated. 2186 RelatedEnd otherRelatedEnd = relatedWrapper.RelationshipManager.GetRelatedEndInternal(n.RelationshipType.FullName, n.FromEndMember.Name); 2187 if (!otherRelatedEnd.ContainsEntity(this._wrappedEntity)) 2188 { 2189 Debug.Assert(relatedWrapper.ObjectStateEntry != null, "Expected related entity to be tracked in snapshot code."); 2190 if (relatedWrapper.ObjectStateEntry.State == EntityState.Deleted) 2191 { 2192 throw EntityUtil.UnableToAddRelationshipWithDeletedEntity(); 2193 } 2194 if (ObjectStateManager.TransactionManager.IsAttachTracking && 2195 (State & (EntityState.Modified | EntityState.Unchanged)) != 0 && 2196 (relatedWrapper.ObjectStateEntry.State & (EntityState.Modified | EntityState.Unchanged)) != 0) 2197 { 2198 EntityEntry principalEntry = null; 2199 EntityEntry dependentEntry = null; 2200 if (relatedEnd.IsDependentEndOfReferentialConstraint(checkIdentifying: false)) 2201 { 2202 principalEntry = relatedWrapper.ObjectStateEntry; 2203 dependentEntry = this; 2204 } 2205 else if (otherRelatedEnd.IsDependentEndOfReferentialConstraint(checkIdentifying: false)) 2206 { 2207 principalEntry = this; 2208 dependentEntry = relatedWrapper.ObjectStateEntry; 2209 } 2210 if (principalEntry != null) 2211 { 2212 var constraint = ((AssociationType)relatedEnd.RelationMetadata).ReferentialConstraints[0]; 2213 if (!RelatedEnd.VerifyRIConstraintsWithRelatedEntry(constraint, dependentEntry.GetCurrentEntityValue, principalEntry.EntityKey)) 2214 { 2215 throw EntityUtil.InconsistentReferentialConstraintProperties(); 2216 } 2217 } 2218 } 2219 // Keep track of the fact that we aligned the related end here so that we can undo 2220 // it in rollback without wiping the already existing nav properties. 2221 EntityReference otherEndAsRef = otherRelatedEnd as EntityReference; 2222 if (otherEndAsRef != null && otherEndAsRef.NavigationPropertyIsNullOrMissing()) 2223 { 2224 ObjectStateManager.TransactionManager.AlignedEntityReferences.Add(otherEndAsRef); 2225 } 2226 otherRelatedEnd.AddToLocalCache(this._wrappedEntity, applyConstraints: true); 2227 otherRelatedEnd.OnAssociationChanged(CollectionChangeAction.Add, _wrappedEntity.Entity); 2228 } 2229 } 2230 else 2231 { 2232 if (!this.ObjectStateManager.TransactionManager.WrappedEntities.TryGetValue(o, out relatedWrapper)) 2233 { 2234 relatedWrapper = EntityWrapperFactory.WrapEntityUsingStateManager(o, this.ObjectStateManager); 2235 } 2236 } 2237 2238 if (!relatedEnd.ContainsEntity(relatedWrapper)) 2239 { 2240 relatedEnd.AddToLocalCache(relatedWrapper, true); 2241 relatedEnd.OnAssociationChanged(CollectionChangeAction.Add, relatedWrapper.Entity); 2242 } 2243 } 2244 DetectChangesInRelationshipsOfSingleEntity()2245 internal void DetectChangesInRelationshipsOfSingleEntity() 2246 { 2247 Debug.Assert(!this.IsKeyEntry, "Entry should be an EntityEntry"); 2248 Debug.Assert(!(this.Entity is IEntityWithRelationships), "Entity shouldn't implement IEntityWithRelationships"); 2249 2250 StateManagerTypeMetadata metadata = this._cacheTypeMetadata; 2251 2252 ReadOnlyMetadataCollection<NavigationProperty> navigationProperties = 2253 (metadata.CdmMetadata.EdmType as EntityType).NavigationProperties; 2254 2255 foreach (NavigationProperty n in navigationProperties) 2256 { 2257 RelatedEnd relatedEnd = this.WrappedEntity.RelationshipManager.GetRelatedEndInternal(n.RelationshipType.FullName, n.ToEndMember.Name); 2258 Debug.Assert(relatedEnd != null, "relatedEnd is null"); 2259 2260 object val = this.WrappedEntity.GetNavigationPropertyValue(relatedEnd); 2261 2262 HashSet<object> current = new HashSet<object>(); 2263 if (val != null) 2264 { 2265 if (n.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many) 2266 { 2267 // Collection 2268 IEnumerable collection = val as IEnumerable; 2269 if (collection == null) 2270 { 2271 throw new EntityException(System.Data.Entity.Strings.ObjectStateEntry_UnableToEnumerateCollection(n.Name, this.Entity.GetType().FullName)); 2272 } 2273 foreach (object o in collection) 2274 { 2275 // Skip nulls in collections 2276 if (o != null) 2277 { 2278 current.Add(o); 2279 } 2280 } 2281 } 2282 else 2283 { 2284 // Reference 2285 current.Add(val); 2286 } 2287 } 2288 2289 // find deleted entities 2290 foreach (object o in relatedEnd.GetInternalEnumerable()) 2291 { 2292 if (!current.Contains(o)) 2293 { 2294 this.AddRelationshipDetectedByGraph( 2295 this.ObjectStateManager.TransactionManager.DeletedRelationshipsByGraph, o, relatedEnd, verifyForAdd:false); 2296 } 2297 else 2298 { 2299 current.Remove(o); 2300 } 2301 } 2302 2303 // "current" contains now only added entities 2304 foreach (object o in current) 2305 { 2306 this.AddRelationshipDetectedByGraph( 2307 this.ObjectStateManager.TransactionManager.AddedRelationshipsByGraph, o, relatedEnd, verifyForAdd:true); 2308 } 2309 } 2310 } 2311 AddRelationshipDetectedByGraph( Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<IEntityWrapper>>> relationships, object relatedObject, RelatedEnd relatedEndFrom, bool verifyForAdd)2312 private void AddRelationshipDetectedByGraph( 2313 Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<IEntityWrapper>>> relationships, 2314 object relatedObject, 2315 RelatedEnd relatedEndFrom, 2316 bool verifyForAdd) 2317 { 2318 IEntityWrapper relatedWrapper = EntityWrapperFactory.WrapEntityUsingStateManager(relatedObject, this.ObjectStateManager); 2319 2320 this.AddDetectedRelationship(relationships, relatedWrapper, relatedEndFrom); 2321 2322 RelatedEnd relatedEndTo = relatedEndFrom.GetOtherEndOfRelationship(relatedWrapper); 2323 2324 if (verifyForAdd && 2325 relatedEndTo is EntityReference && 2326 this.ObjectStateManager.FindEntityEntry(relatedObject) == null) 2327 { 2328 // If the relatedObject is not tracked by the context, let's detect it before OSM.PerformAdd to avoid 2329 // making RelatedEnd.Add() more complicated (it would have to know when the values in relatedEndTo can be overriden, and when not 2330 relatedEndTo.VerifyNavigationPropertyForAdd(_wrappedEntity); 2331 } 2332 2333 this.AddDetectedRelationship(relationships, _wrappedEntity, relatedEndTo); 2334 } 2335 AddRelationshipDetectedByForeignKey( Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<EntityKey>>> relationships, Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<EntityKey>>> principalRelationships, EntityKey relatedKey, EntityEntry relatedEntry, RelatedEnd relatedEndFrom)2336 private void AddRelationshipDetectedByForeignKey( 2337 Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<EntityKey>>> relationships, 2338 Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<EntityKey>>> principalRelationships, 2339 EntityKey relatedKey, 2340 EntityEntry relatedEntry, 2341 RelatedEnd relatedEndFrom) 2342 { 2343 Debug.Assert(!relatedKey.IsTemporary, "the relatedKey was created by a method which returns only permaanent keys"); 2344 this.AddDetectedRelationship(relationships, relatedKey, relatedEndFrom); 2345 2346 if (relatedEntry != null) 2347 { 2348 IEntityWrapper relatedWrapper = relatedEntry.WrappedEntity; ; 2349 2350 RelatedEnd relatedEndTo = relatedEndFrom.GetOtherEndOfRelationship(relatedWrapper); 2351 2352 EntityKey permanentKeyOwner = this.ObjectStateManager.GetPermanentKey(relatedEntry.WrappedEntity, relatedEndTo, this.WrappedEntity); 2353 this.AddDetectedRelationship(principalRelationships, permanentKeyOwner, relatedEndTo); 2354 } 2355 } 2356 2357 /// <summary> 2358 /// Designed to be used by Change Detection methods to insert 2359 /// Added/Deleted relationships into <see cref="TransactionManager"/> 2360 /// Creates new entries in the dictionaries if required 2361 /// </summary> 2362 /// <typeparam name="T">IEntityWrapper or EntityKey</typeparam> 2363 /// <param name="relationships">The set of detected relationships to add this entry to</param> 2364 /// <param name="relatedObject">The entity the relationship points to</param> 2365 /// <param name="relatedEnd">The related end the relationship originates from</param> AddDetectedRelationship( Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<T>>> relationships, T relatedObject, RelatedEnd relatedEnd)2366 private void AddDetectedRelationship<T>( 2367 Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<T>>> relationships, 2368 T relatedObject, 2369 RelatedEnd relatedEnd) 2370 { 2371 // Update info about changes to this/from side of the relationship 2372 Dictionary<RelatedEnd, HashSet<T>> alreadyDetectedRelationshipsFrom; 2373 if (!relationships.TryGetValue(relatedEnd.WrappedOwner, out alreadyDetectedRelationshipsFrom)) 2374 { 2375 alreadyDetectedRelationshipsFrom = new Dictionary<RelatedEnd, HashSet<T>>(); 2376 relationships.Add(relatedEnd.WrappedOwner, alreadyDetectedRelationshipsFrom); 2377 } 2378 2379 HashSet<T> objectsInRelatedEnd; 2380 if (!alreadyDetectedRelationshipsFrom.TryGetValue(relatedEnd, out objectsInRelatedEnd)) 2381 { 2382 objectsInRelatedEnd = new HashSet<T>(); 2383 alreadyDetectedRelationshipsFrom.Add(relatedEnd, objectsInRelatedEnd); 2384 } 2385 else 2386 { 2387 if (relatedEnd is EntityReference) 2388 { 2389 Debug.Assert(objectsInRelatedEnd.Count() == 1, "unexpected number of entities for EntityReference"); 2390 T existingRelatedObject = objectsInRelatedEnd.First(); 2391 if (!Object.Equals(existingRelatedObject, relatedObject)) 2392 { 2393 throw EntityUtil.CannotAddMoreThanOneEntityToEntityReference( 2394 relatedEnd.RelationshipNavigation.To, 2395 relatedEnd.RelationshipNavigation.RelationshipName); 2396 } 2397 } 2398 } 2399 2400 objectsInRelatedEnd.Add(relatedObject); 2401 } 2402 2403 /// <summary> 2404 /// Detaches an entry and create in its place key entry if necessary 2405 /// Removes relationships with another key entries and removes these key entries if necessary 2406 /// </summary> Detach()2407 internal void Detach() 2408 { 2409 ValidateState(); 2410 2411 Debug.Assert(!this.IsKeyEntry); 2412 2413 bool createKeyEntry = false; 2414 2415 RelationshipManager relationshipManager = _wrappedEntity.RelationshipManager; 2416 Debug.Assert(relationshipManager != null, "Entity wrapper returned a null RelationshipManager"); 2417 // Key entry should be created only when current entity is not in Added state 2418 // and if the entity is a "OneToOne" or "ZeroToOne" end of some existing relationship. 2419 createKeyEntry = 2420 this.State != EntityState.Added && 2421 this.IsOneEndOfSomeRelationship(); 2422 2423 _cache.TransactionManager.BeginDetaching(); 2424 try 2425 { 2426 // Remove current entity from collections/references (on both ends of relationship) 2427 // Relationship entries are removed from ObjectStateManager if current entity is in Added state 2428 // or if current entity is a "Many" end of the relationship. 2429 // NOTE In this step only relationship entries which have normal entity on the other end 2430 // can be detached. 2431 // NOTE In this step no Deleted relationship entries are detached. 2432 relationshipManager.DetachEntityFromRelationships(this.State); 2433 } 2434 finally 2435 { 2436 _cache.TransactionManager.EndDetaching(); 2437 } 2438 2439 // Remove relationship entries which has a key entry on the other end. 2440 // If the key entry does not have any other relationship, it is removed from Object State Manager. 2441 // NOTE Relationship entries which have a normal entity on the other end are detached only if the relationship state is Deleted. 2442 this.DetachRelationshipsEntries(relationshipManager); 2443 2444 IEntityWrapper existingWrappedEntity = _wrappedEntity; 2445 EntityKey key = _entityKey; 2446 EntityState state = State; 2447 2448 if (createKeyEntry) 2449 { 2450 this.DegradeEntry(); 2451 } 2452 else 2453 { 2454 // If entity is in state different than Added state, entityKey should not be set to null 2455 // EntityKey is set to null in 2456 // ObjectStateManger.ChangeState() -> 2457 // ObjectStateEntry.Reset() -> 2458 // ObjectStateEntry.DetachObjectStateManagerFromEntity() 2459 2460 // Store data required to restore the entity key if needed. 2461 _wrappedEntity.ObjectStateEntry = null; 2462 2463 _cache.ChangeState(this, this.State, EntityState.Detached); 2464 } 2465 2466 // In case the detach event modifies the key. 2467 if (state != EntityState.Added) 2468 { 2469 existingWrappedEntity.EntityKey = key; 2470 } 2471 } 2472 2473 //"doFixup" equals to False is called from EntityCollection & Ref code only Delete(bool doFixup)2474 internal void Delete(bool doFixup) 2475 { 2476 ValidateState(); 2477 2478 if (this.IsKeyEntry) 2479 { 2480 throw EntityUtil.CannotCallDeleteOnKeyEntry(); 2481 } 2482 2483 if (doFixup && this.State != EntityState.Deleted) 2484 { 2485 this.RelationshipManager.NullAllFKsInDependentsForWhichThisIsThePrincipal(); 2486 this.NullAllForeignKeys(); // May set conceptual nulls which will later be removed 2487 this.FixupRelationships(); 2488 } 2489 2490 switch (State) 2491 { 2492 case EntityState.Added: 2493 Debug.Assert(EntityState.Added == State, "Expected ObjectStateEntry state is Added; make sure FixupRelationship did not corrupt cache entry state"); 2494 2495 _cache.ChangeState(this, EntityState.Added, EntityState.Detached); 2496 2497 Debug.Assert(null == _modifiedFields, "There should not be any modified fields"); 2498 2499 break; 2500 case EntityState.Modified: 2501 if (!doFixup) 2502 { 2503 // Even when we are not doing relationship fixup at the collection level, if the entry is not a relationship 2504 // we need to check to see if there are relationships that are referencing keys that should be removed 2505 // this mainly occurs in cascade delete scenarios 2506 DeleteRelationshipsThatReferenceKeys(null, null); 2507 } 2508 Debug.Assert(EntityState.Modified == State, "Expected ObjectStateEntry state is Modified; make sure FixupRelationship did not corrupt cache entry state"); 2509 _cache.ChangeState(this, EntityState.Modified, EntityState.Deleted); 2510 State = EntityState.Deleted; 2511 2512 break; 2513 case EntityState.Unchanged: 2514 if (!doFixup) 2515 { 2516 // Even when we are not doing relationship fixup at the collection level, if the entry is not a relationship 2517 // we need to check to see if there are relationships that are referencing keys that should be removed 2518 // this mainly occurs in cascade delete scenarios 2519 DeleteRelationshipsThatReferenceKeys(null, null); 2520 } 2521 Debug.Assert(State == EntityState.Unchanged, "Unexpected state"); 2522 Debug.Assert(EntityState.Unchanged == State, "Expected ObjectStateEntry state is Unchanged; make sure FixupRelationship did not corrupt cache entry state"); 2523 _cache.ChangeState(this, EntityState.Unchanged, EntityState.Deleted); 2524 Debug.Assert(null == _modifiedFields, "There should not be any modified fields"); 2525 State = EntityState.Deleted; 2526 2527 break; 2528 case EntityState.Deleted: 2529 // no-op 2530 break; 2531 } 2532 } 2533 2534 /// <summary> 2535 /// Nulls all FK values in this entity, or sets conceptual nulls if they are not nullable. 2536 /// </summary> NullAllForeignKeys()2537 private void NullAllForeignKeys() 2538 { 2539 foreach (var dependent in ForeignKeyDependents) 2540 { 2541 EntityReference relatedEnd = WrappedEntity.RelationshipManager.GetRelatedEndInternal( 2542 dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name) as EntityReference; 2543 Debug.Assert(relatedEnd != null, "Expected non-null EntityReference to principal."); 2544 relatedEnd.NullAllForeignKeys(); 2545 } 2546 } 2547 IsOneEndOfSomeRelationship()2548 private bool IsOneEndOfSomeRelationship() 2549 { 2550 foreach (RelationshipEntry relationshipEntry in _cache.FindRelationshipsByKey(EntityKey)) 2551 { 2552 RelationshipMultiplicity multiplicity = this.GetAssociationEndMember(relationshipEntry).RelationshipMultiplicity; 2553 if (multiplicity == RelationshipMultiplicity.One || 2554 multiplicity == RelationshipMultiplicity.ZeroOrOne) 2555 { 2556 EntityKey targetKey = relationshipEntry.RelationshipWrapper.GetOtherEntityKey(EntityKey); 2557 EntityEntry relatedEntry = _cache.GetEntityEntry(targetKey); 2558 // Relationships with KeyEntries don't count. 2559 if (!relatedEntry.IsKeyEntry) 2560 { 2561 return true; 2562 } 2563 } 2564 } 2565 return false; 2566 } 2567 2568 // Detaches related relationship entries if other ends of these relationships are key entries. 2569 // Detaches also related relationship entries if the entry is in Deleted state and the multiplicity is Many. 2570 // Key entry from the other side of the relationship is removed if is not related to other entries. DetachRelationshipsEntries(RelationshipManager relationshipManager)2571 private void DetachRelationshipsEntries(RelationshipManager relationshipManager) 2572 { 2573 Debug.Assert(relationshipManager != null, "Unexpected null RelationshipManager"); 2574 Debug.Assert(!this.IsKeyEntry, "Should only be detaching relationships with key entries if the source is not a key entry"); 2575 2576 foreach (RelationshipEntry relationshipEntry in _cache.CopyOfRelationshipsByKey(EntityKey)) 2577 { 2578 // Get state entry for other side of the relationship 2579 EntityKey targetKey = relationshipEntry.RelationshipWrapper.GetOtherEntityKey(EntityKey); 2580 Debug.Assert((object)targetKey != null, "EntityKey not on either side of relationship as expected"); 2581 2582 EntityEntry relatedEntry = _cache.GetEntityEntry(targetKey); 2583 if (relatedEntry.IsKeyEntry) 2584 { 2585 // This must be an EntityReference, so set the DetachedEntityKey if the relationship is currently Added or Unchanged 2586 // devnote: This assumes that we are in the middle of detaching the entity associated with this state entry, because 2587 // we don't always want to preserve the EntityKey for every detached relationship, if the source entity itself isn't being detached 2588 if (relationshipEntry.State != EntityState.Deleted) 2589 { 2590 AssociationEndMember targetMember = relationshipEntry.RelationshipWrapper.GetAssociationEndMember(targetKey); 2591 // devnote: Since we know the target end of this relationship is a key entry, it has to be a reference, so just cast 2592 EntityReference entityReference = (EntityReference)relationshipManager.GetRelatedEndInternal(targetMember.DeclaringType.FullName, targetMember.Name); 2593 entityReference.DetachedEntityKey = targetKey; 2594 } 2595 // else do nothing -- we can't null out the key for Deleted state, because there could be other relationships with this same source in a different state 2596 2597 // Remove key entry if necessary 2598 relationshipEntry.DeleteUnnecessaryKeyEntries(); 2599 // Remove relationship entry 2600 relationshipEntry.DetachRelationshipEntry(); 2601 } 2602 else 2603 { 2604 // Detach deleted relationships 2605 if (relationshipEntry.State == EntityState.Deleted) 2606 { 2607 RelationshipMultiplicity multiplicity = this.GetAssociationEndMember(relationshipEntry).RelationshipMultiplicity; 2608 if (multiplicity == RelationshipMultiplicity.Many) 2609 { 2610 relationshipEntry.DetachRelationshipEntry(); 2611 } 2612 } 2613 } 2614 } 2615 } 2616 FixupRelationships()2617 private void FixupRelationships() 2618 { 2619 RelationshipManager relationshipManager = _wrappedEntity.RelationshipManager; 2620 Debug.Assert(relationshipManager != null, "Entity wrapper returned a null RelationshipManager"); 2621 relationshipManager.RemoveEntityFromRelationships(); 2622 DeleteRelationshipsThatReferenceKeys(null, null); 2623 } 2624 2625 /// <summary> 2626 /// see if there are any relationship entries that point to key entries 2627 /// if there are, remove the relationship entry 2628 /// This is called when one of the ends of a relationship is being removed 2629 /// </summary> 2630 /// <param name="relationshipSet">An option relationshipSet; deletes only relationships that are part of this set</param> DeleteRelationshipsThatReferenceKeys(RelationshipSet relationshipSet, RelationshipEndMember endMember)2631 internal void DeleteRelationshipsThatReferenceKeys(RelationshipSet relationshipSet, RelationshipEndMember endMember) 2632 { 2633 if (State != EntityState.Detached) 2634 { 2635 // devnote: Need to use a copy of the relationships list because we may be deleting Added 2636 // relationships, which will be removed from the list while we are still iterating 2637 foreach (RelationshipEntry relationshipEntry in _cache.CopyOfRelationshipsByKey(EntityKey)) 2638 { 2639 // Only delete the relationship entry if it is not already deleted (in which case we cannot access its values) 2640 // and when the given (optionally) relationshipSet matches the one in teh relationship entry 2641 if ((relationshipEntry.State != EntityState.Deleted) && 2642 (relationshipSet == null || relationshipSet == relationshipEntry.EntitySet)) 2643 { 2644 EntityEntry otherEnd = this.GetOtherEndOfRelationship(relationshipEntry); 2645 if (endMember == null || endMember == otherEnd.GetAssociationEndMember(relationshipEntry)) 2646 { 2647 for (int i = 0; i < 2; i++) 2648 { 2649 EntityKey entityKey = relationshipEntry.GetCurrentRelationValue(i) as EntityKey; 2650 if ((object)entityKey != null) 2651 { 2652 EntityEntry relatedEntry = _cache.GetEntityEntry(entityKey); 2653 if (relatedEntry.IsKeyEntry) 2654 { 2655 // remove the relationshipEntry 2656 relationshipEntry.Delete(false); 2657 break; 2658 } 2659 } 2660 } 2661 } 2662 } 2663 } 2664 } 2665 } 2666 2667 // Retrieve referential constraint properties from Principal entities (possibly recursively) 2668 // and check referential constraint properties in the Dependent entities (1 level only) 2669 // This code does not check the constraints on FKs because that work is instead done by 2670 // the FK fixup code that is also called from AcceptChanges. 2671 // Returns true if any FK relationships were skipped so that they can be checked again after fixup RetrieveAndCheckReferentialConstraintValuesInAcceptChanges()2672 private bool RetrieveAndCheckReferentialConstraintValuesInAcceptChanges() 2673 { 2674 RelationshipManager relationshipManager = _wrappedEntity.RelationshipManager; 2675 Debug.Assert(relationshipManager != null, "Entity wrapper returned a null RelationshipManager"); 2676 // Find key property names which are part of referential integrity constraints 2677 List<string> propertiesToRetrieve; // names of properties which should be retrieved from Principal entities 2678 bool propertiesToCheckExist; // true iff there are properties which should be checked in dependent entities 2679 2680 // Get RI property names from metadata 2681 bool skippedFKs = relationshipManager.FindNamesOfReferentialConstraintProperties(out propertiesToRetrieve, out propertiesToCheckExist, skipFK: true); 2682 2683 // Do not try to retrieve RI properties if entity doesn't participate in any RI Constraints 2684 if (propertiesToRetrieve != null) 2685 { 2686 // Retrieve key values from related entities 2687 Dictionary<string, KeyValuePair<object, IntBox>> properties; 2688 2689 // Create HashSet to store references to already visited entities, used to detect circular references 2690 HashSet<object> visited = new HashSet<object>(); 2691 2692 relationshipManager.RetrieveReferentialConstraintProperties(out properties, visited, includeOwnValues: false); 2693 2694 // Update properties 2695 foreach (KeyValuePair<string, KeyValuePair<object, IntBox>> pair in properties) 2696 { 2697 this.SetCurrentEntityValue(pair.Key /*name*/, pair.Value.Key /*value*/); 2698 } 2699 } 2700 2701 if (propertiesToCheckExist) 2702 { 2703 // Compare properties of current entity with properties of the dependent entities 2704 this.CheckReferentialConstraintPropertiesInDependents(); 2705 } 2706 return skippedFKs; 2707 } 2708 2709 RetrieveReferentialConstraintPropertiesFromKeyEntries(Dictionary<string, KeyValuePair<object, IntBox>> properties)2710 internal void RetrieveReferentialConstraintPropertiesFromKeyEntries(Dictionary<string, KeyValuePair<object, IntBox>> properties) 2711 { 2712 string thisRole; 2713 AssociationSet association; 2714 2715 // Iterate through related relationship entries 2716 foreach (RelationshipEntry relationshipEntry in _cache.FindRelationshipsByKey(EntityKey)) 2717 { 2718 EntityEntry otherEnd = this.GetOtherEndOfRelationship(relationshipEntry); 2719 2720 // We only try to retrieve properties from key entries 2721 if (otherEnd.IsKeyEntry) 2722 { 2723 association = (AssociationSet)relationshipEntry.EntitySet; 2724 Debug.Assert(association != null, "relationship is not an association"); 2725 2726 // Iterate through referential constraints of the association of the relationship 2727 // NOTE PERFORMANCE This collection in current stack can have 0 or 1 elements 2728 foreach (ReferentialConstraint constraint in association.ElementType.ReferentialConstraints) 2729 { 2730 thisRole = this.GetAssociationEndMember(relationshipEntry).Name; 2731 2732 // Check if curent entry is a dependent end of the referential constraint 2733 if (constraint.ToRole.Name == thisRole) 2734 { 2735 Debug.Assert(!otherEnd.EntityKey.IsTemporary, "key of key entry can't be temporary"); 2736 IList<EntityKeyMember> otherEndKeyValues = otherEnd.EntityKey.EntityKeyValues; 2737 Debug.Assert(otherEndKeyValues != null, "key entry must have key values"); 2738 2739 // NOTE PERFORMANCE Number of key properties is supposed to be "small" 2740 foreach (EntityKeyMember pair in otherEndKeyValues) 2741 { 2742 for (int i = 0; i < constraint.FromProperties.Count; ++i) 2743 { 2744 if (constraint.FromProperties[i].Name == pair.Key) 2745 { 2746 EntityEntry.AddOrIncreaseCounter(properties, constraint.ToProperties[i].Name, pair.Value); 2747 } 2748 } 2749 } 2750 } 2751 } 2752 } 2753 } 2754 } 2755 AddOrIncreaseCounter(Dictionary<string, KeyValuePair<object, IntBox>> properties, string propertyName, object propertyValue)2756 internal static void AddOrIncreaseCounter(Dictionary<string, KeyValuePair<object, IntBox>> properties, string propertyName, object propertyValue) 2757 { 2758 Debug.Assert(properties != null); 2759 Debug.Assert(propertyName != null); 2760 Debug.Assert(propertyValue != null); 2761 2762 if (properties.ContainsKey(propertyName)) 2763 { 2764 // If this property already exists in the dictionary, check if value is the same then increase the counter 2765 2766 KeyValuePair<object, IntBox> valueCounterPair = properties[propertyName]; 2767 2768 if (!ByValueEqualityComparer.Default.Equals(valueCounterPair.Key, propertyValue)) 2769 throw EntityUtil.InconsistentReferentialConstraintProperties(); 2770 else 2771 valueCounterPair.Value.Value = valueCounterPair.Value.Value + 1; 2772 } 2773 else 2774 { 2775 // If property doesn't exist in the dictionary - add new entry with pair<value, counter> 2776 properties[propertyName] = new KeyValuePair<object, IntBox>(propertyValue, new IntBox(1)); 2777 } 2778 } 2779 2780 // Check if related dependent entities contain proper property values 2781 // Only entities in Unchanged and Modified state are checked (including KeyEntries) CheckReferentialConstraintPropertiesInDependents()2782 private void CheckReferentialConstraintPropertiesInDependents() 2783 { 2784 string thisRole; 2785 AssociationSet association; 2786 2787 // Iterate through related relationship entries 2788 foreach (RelationshipEntry relationshipEntry in _cache.FindRelationshipsByKey(EntityKey)) 2789 { 2790 EntityEntry otherEnd = this.GetOtherEndOfRelationship(relationshipEntry); 2791 2792 // We only check entries which are in Unchanged or Modified state 2793 // (including KeyEntries which are always in Unchanged State) 2794 if (otherEnd.State == EntityState.Unchanged || otherEnd.State == EntityState.Modified) 2795 { 2796 association = (AssociationSet)relationshipEntry.EntitySet; 2797 Debug.Assert(association != null, "relationship is not an association"); 2798 2799 // Iterate through referential constraints of the association of the relationship 2800 // NOTE PERFORMANCE This collection in current stack can have 0 or 1 elements 2801 foreach (ReferentialConstraint constraint in association.ElementType.ReferentialConstraints) 2802 { 2803 thisRole = this.GetAssociationEndMember(relationshipEntry).Name; 2804 2805 // Check if curent entry is a principal end of the referential constraint 2806 if (constraint.FromRole.Name == thisRole) 2807 { 2808 Debug.Assert(!otherEnd.EntityKey.IsTemporary, "key of Unchanged or Modified entry can't be temporary"); 2809 IList<EntityKeyMember> otherEndKeyValues = otherEnd.EntityKey.EntityKeyValues; 2810 // NOTE PERFORMANCE Number of key properties is supposed to be "small" 2811 foreach (EntityKeyMember pair in otherEndKeyValues) 2812 { 2813 for (int i = 0; i < constraint.FromProperties.Count; ++i) 2814 { 2815 if (constraint.ToProperties[i].Name == pair.Key) 2816 { 2817 if (!ByValueEqualityComparer.Default.Equals(GetCurrentEntityValue(constraint.FromProperties[i].Name), pair.Value)) 2818 { 2819 throw EntityUtil.InconsistentReferentialConstraintProperties(); 2820 } 2821 } 2822 } 2823 } 2824 } 2825 } 2826 } 2827 } 2828 } 2829 PromoteKeyEntry(IEntityWrapper wrappedEntity, IExtendedDataRecord shadowValues, StateManagerTypeMetadata typeMetadata)2830 internal void PromoteKeyEntry(IEntityWrapper wrappedEntity, IExtendedDataRecord shadowValues, StateManagerTypeMetadata typeMetadata) 2831 { 2832 Debug.Assert(wrappedEntity != null, "entity wrapper cannot be null."); 2833 Debug.Assert(wrappedEntity.Entity != null, "entity cannot be null."); 2834 Debug.Assert(this.IsKeyEntry, "ObjectStateEntry should be a key."); 2835 Debug.Assert(typeMetadata != null, "typeMetadata cannot be null."); 2836 2837 _wrappedEntity = wrappedEntity; 2838 _wrappedEntity.ObjectStateEntry = this; 2839 2840 // Allow updating of cached metadata because the actual entity might be a derived type 2841 _cacheTypeMetadata = typeMetadata; 2842 2843 SetChangeTrackingFlags(); 2844 2845 #if DEBUG // performance, don't do this work in retail until shadow state is supported 2846 if (shadowValues != null) 2847 { 2848 // shadowState always coms from materializer, just copy the shadowstate values 2849 Debug.Assert(shadowValues.DataRecordInfo.RecordType.EdmType.Equals(_cacheTypeMetadata.CdmMetadata.EdmType), "different cspace metadata instance"); 2850 for (int ordinal = 0; ordinal < _cacheTypeMetadata.FieldCount; ordinal++) 2851 { 2852 if (_cacheTypeMetadata.IsMemberPartofShadowState(ordinal)) 2853 { 2854 Debug.Assert(false, "shadowstate not supported"); 2855 } 2856 } 2857 } 2858 #endif 2859 } 2860 2861 /// <summary> 2862 /// Turns this entry into a key entry (SPAN stub). 2863 /// </summary> DegradeEntry()2864 internal void DegradeEntry() 2865 { 2866 Debug.Assert(!this.IsKeyEntry); 2867 Debug.Assert((object)_entityKey != null); 2868 2869 _entityKey = EntityKey; //Performs validation. 2870 2871 RemoveFromForeignKeyIndex(); 2872 2873 _wrappedEntity.SetChangeTracker(null); 2874 2875 _modifiedFields = null; 2876 _originalValues = null; 2877 _originalComplexObjects = null; 2878 2879 // we don't want temporary keys to exist outside of the context 2880 if (State == EntityState.Added) 2881 { 2882 _wrappedEntity.EntityKey = null; 2883 _entityKey = null; 2884 } 2885 2886 if (State != EntityState.Unchanged) 2887 { 2888 _cache.ChangeState(this, this.State, EntityState.Unchanged); 2889 State = EntityState.Unchanged; 2890 } 2891 2892 _cache.RemoveEntryFromKeylessStore(_wrappedEntity); 2893 _wrappedEntity.DetachContext(); 2894 _wrappedEntity.ObjectStateEntry = null; 2895 2896 object degradedEntity = _wrappedEntity.Entity; 2897 _wrappedEntity = EntityWrapperFactory.NullWrapper; 2898 2899 SetChangeTrackingFlags(); 2900 2901 _cache.OnObjectStateManagerChanged(CollectionChangeAction.Remove, degradedEntity); 2902 2903 Debug.Assert(this.IsKeyEntry); 2904 } 2905 AttachObjectStateManagerToEntity()2906 internal void AttachObjectStateManagerToEntity() 2907 { 2908 // This method should only be called in cases where we really have an entity to attach to 2909 Debug.Assert(_wrappedEntity.Entity != null, "Cannot attach a null entity to the state manager"); 2910 _wrappedEntity.SetChangeTracker(this); 2911 _wrappedEntity.TakeSnapshot(this); 2912 } 2913 2914 // Get values of key properties which doesn't already exist in passed in 'properties' GetOtherKeyProperties(Dictionary<string, KeyValuePair<object, IntBox>> properties)2915 internal void GetOtherKeyProperties(Dictionary<string, KeyValuePair<object, IntBox>> properties) 2916 { 2917 Debug.Assert(properties != null); 2918 Debug.Assert(_cacheTypeMetadata != null); 2919 Debug.Assert(_cacheTypeMetadata.DataRecordInfo != null); 2920 Debug.Assert(_cacheTypeMetadata.DataRecordInfo.RecordType != null); 2921 2922 EntityType entityType = _cacheTypeMetadata.DataRecordInfo.RecordType.EdmType as EntityType; 2923 Debug.Assert(entityType != null, "EntityType == null"); 2924 2925 foreach (EdmMember member in entityType.KeyMembers) 2926 { 2927 if (!properties.ContainsKey(member.Name)) 2928 { 2929 properties[member.Name] = new KeyValuePair<object, IntBox>(this.GetCurrentEntityValue(member.Name), new IntBox(1)); 2930 } 2931 } 2932 } 2933 AddOriginalValue(StateManagerMemberMetadata memberMetadata, object userObject, object value)2934 internal void AddOriginalValue(StateManagerMemberMetadata memberMetadata, object userObject, object value) 2935 { 2936 if (null == _originalValues) 2937 { 2938 _originalValues = new List<StateManagerValue>(); 2939 } 2940 _originalValues.Add(new StateManagerValue(memberMetadata, userObject, value)); 2941 } 2942 CompareKeyProperties(object changed)2943 internal void CompareKeyProperties(object changed) 2944 { 2945 Debug.Assert(changed != null, "changed object is null"); 2946 Debug.Assert(!this.IsKeyEntry); 2947 2948 StateManagerTypeMetadata metadata = this._cacheTypeMetadata; 2949 2950 int fieldCount = this.GetFieldCount(metadata); 2951 object currentValueNew; 2952 object currentValueOld; 2953 2954 for (int i = 0; i < fieldCount; i++) 2955 { 2956 StateManagerMemberMetadata member = metadata.Member(i); 2957 if (member.IsPartOfKey) 2958 { 2959 Debug.Assert(!member.IsComplex); 2960 2961 currentValueNew = member.GetValue(changed); 2962 currentValueOld = member.GetValue(_wrappedEntity.Entity); 2963 2964 if (!ByValueEqualityComparer.Default.Equals(currentValueNew, currentValueOld)) 2965 { 2966 throw EntityUtil.CannotModifyKeyProperty(member.CLayerName); 2967 } 2968 } 2969 } 2970 } 2971 2972 // helper method used to get value of property GetCurrentEntityValue(string memberName)2973 internal object GetCurrentEntityValue(string memberName) 2974 { 2975 int ordinal = _cacheTypeMetadata.GetOrdinalforOLayerMemberName(memberName); 2976 return GetCurrentEntityValue(_cacheTypeMetadata, ordinal, _wrappedEntity.Entity, ObjectStateValueRecord.CurrentUpdatable); 2977 } 2978 2979 /// <summary> 2980 /// Verifies that the property with the given ordinal is editable. 2981 /// </summary> 2982 /// <exception cref="InvalidOperationException">the property is not editable</exception> VerifyEntityValueIsEditable(StateManagerTypeMetadata typeMetadata, int ordinal, string memberName)2983 internal void VerifyEntityValueIsEditable(StateManagerTypeMetadata typeMetadata, int ordinal, string memberName) 2984 { 2985 if (this.State == EntityState.Deleted) 2986 { 2987 throw EntityUtil.CantModifyDetachedDeletedEntries(); 2988 } 2989 2990 Debug.Assert(typeMetadata != null, "Cannot verify entity or complex object is editable if typeMetadata is null."); 2991 StateManagerMemberMetadata member = typeMetadata.Member(ordinal); 2992 2993 Debug.Assert(member != null, "Member shouldn't be null."); 2994 2995 // Key fields are only editable if the entry is the Added state. 2996 if (member.IsPartOfKey && State != EntityState.Added) 2997 { 2998 throw EntityUtil.CannotModifyKeyProperty(memberName); 2999 } 3000 } 3001 3002 // This API are mainly for DbDataRecord implementations to get and set the values 3003 // also for loadoptions, setoldvalue will be used. 3004 // we should handle just for C-space, we will not recieve a call from O-space for set 3005 // We will not also return any value in term of O-Layer. all set and gets for us is in terms of C-layer. 3006 // the only O-layer interaction we have is through delegates from entity. SetCurrentEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, object newValue)3007 internal void SetCurrentEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, object newValue) 3008 { 3009 // required to validate state because entity could be detatched from this context and added to another context 3010 // and we want this to fail instead of setting the value which would redirect to the other context 3011 ValidateState(); 3012 3013 StateManagerMemberMetadata member = metadata.Member(ordinal); 3014 Debug.Assert(member != null, "StateManagerMemberMetadata was not found for the given ordinal."); 3015 3016 if (member.IsComplex) 3017 { 3018 if (newValue == null || newValue == DBNull.Value) 3019 { 3020 throw EntityUtil.NullableComplexTypesNotSupported(member.CLayerName); 3021 } 3022 3023 IExtendedDataRecord newValueRecord = newValue as IExtendedDataRecord; 3024 if (newValueRecord == null) 3025 { 3026 throw EntityUtil.InvalidTypeForComplexTypeProperty("value"); 3027 } 3028 3029 newValue = _cache.ComplexTypeMaterializer.CreateComplex(newValueRecord, newValueRecord.DataRecordInfo, null); 3030 } 3031 3032 _wrappedEntity.SetCurrentValue(this, member, ordinal, userObject, newValue); 3033 } 3034 TransitionRelationshipsForAdd()3035 private void TransitionRelationshipsForAdd() 3036 { 3037 foreach (RelationshipEntry relationshipEntry in _cache.CopyOfRelationshipsByKey(this.EntityKey)) 3038 { 3039 // Unchanged -> Added 3040 if (relationshipEntry.State == EntityState.Unchanged) 3041 { 3042 this.ObjectStateManager.ChangeState(relationshipEntry, EntityState.Unchanged, EntityState.Added); 3043 relationshipEntry.State = EntityState.Added; 3044 } 3045 // Deleted -> Detached 3046 else if (relationshipEntry.State == EntityState.Deleted) 3047 { 3048 // Remove key entry if necessary 3049 relationshipEntry.DeleteUnnecessaryKeyEntries(); 3050 // Remove relationship entry 3051 relationshipEntry.DetachRelationshipEntry(); 3052 } 3053 } 3054 } 3055 3056 [Conditional("DEBUG")] VerifyIsNotRelated()3057 private void VerifyIsNotRelated() 3058 { 3059 Debug.Assert(!this.IsKeyEntry, "shouldn't be called for a key entry"); 3060 3061 this.WrappedEntity.RelationshipManager.VerifyIsNotRelated(); 3062 } 3063 ChangeObjectState(EntityState requestedState)3064 internal void ChangeObjectState(EntityState requestedState) 3065 { 3066 if (this.IsKeyEntry) 3067 { 3068 if (requestedState == EntityState.Unchanged) 3069 { 3070 return; // No-op 3071 } 3072 throw EntityUtil.CannotModifyKeyEntryState(); 3073 } 3074 3075 switch (this.State) 3076 { 3077 case EntityState.Added: 3078 switch (requestedState) 3079 { 3080 case EntityState.Added: 3081 // Relationship fixup: Unchanged -> Added, Deleted -> Detached 3082 this.TransitionRelationshipsForAdd(); 3083 break; 3084 case EntityState.Unchanged: 3085 // Relationship fixup: none 3086 this.AcceptChanges(); 3087 break; 3088 case EntityState.Modified: 3089 // Relationship fixup: none 3090 this.AcceptChanges(); 3091 this.SetModified(); 3092 this.SetModifiedAll(); 3093 break; 3094 case EntityState.Deleted: 3095 // Need to forget conceptual nulls so that AcceptChanges does not throw. 3096 // Note that there should always be no conceptual nulls left when we get into the Deleted state. 3097 _cache.ForgetEntryWithConceptualNull(this, resetAllKeys: true); 3098 // Relationship fixup: Added -> Detached, Unchanged -> Deleted 3099 this.AcceptChanges(); 3100 // NOTE: OSM.TransactionManager.IsLocalPublicAPI == true so cascade delete and RIC are disabled 3101 this.Delete(true); 3102 break; 3103 case EntityState.Detached: 3104 // Relationship fixup: * -> Detached 3105 this.Detach(); 3106 break; 3107 default: 3108 throw EntityUtil.InvalidEntityStateArgument("state"); 3109 } 3110 break; 3111 case EntityState.Unchanged: 3112 switch (requestedState) 3113 { 3114 case EntityState.Added: 3115 this.ObjectStateManager.ReplaceKeyWithTemporaryKey(this); 3116 this._modifiedFields = null; 3117 this._originalValues = null; 3118 this._originalComplexObjects = null; 3119 this.State = EntityState.Added; 3120 // Relationship fixup: Unchanged -> Added, Deleted -> Detached 3121 this.TransitionRelationshipsForAdd(); 3122 break; 3123 case EntityState.Unchanged: 3124 // Relationship fixup: none 3125 break; 3126 case EntityState.Modified: 3127 // Relationship fixup: none 3128 this.SetModified(); 3129 this.SetModifiedAll(); 3130 break; 3131 case EntityState.Deleted: 3132 // Relationship fixup: Added -> Detached, Unchanged -> Deleted 3133 // NOTE: OSM.TransactionManager.IsLocalPublicAPI == true so cascade delete and RIC are disabled 3134 this.Delete(true); 3135 break; 3136 case EntityState.Detached: 3137 // Relationship fixup: * -> Detached 3138 this.Detach(); 3139 break; 3140 default: 3141 throw EntityUtil.InvalidEntityStateArgument("state"); 3142 } 3143 break; 3144 case EntityState.Modified: 3145 switch (requestedState) 3146 { 3147 case EntityState.Added: 3148 this.ObjectStateManager.ReplaceKeyWithTemporaryKey(this); 3149 this._modifiedFields = null; 3150 this._originalValues = null; 3151 this._originalComplexObjects = null; 3152 this.State = EntityState.Added; 3153 // Relationship fixup: Unchanged -> Added, Deleted -> Detached 3154 this.TransitionRelationshipsForAdd(); 3155 break; 3156 case EntityState.Unchanged: 3157 this.AcceptChanges(); 3158 // Relationship fixup: none 3159 break; 3160 case EntityState.Modified: 3161 // Relationship fixup: none 3162 this.SetModified(); 3163 this.SetModifiedAll(); 3164 break; 3165 case EntityState.Deleted: 3166 // Relationship fixup: Added -> Detached, Unchanged -> Deleted 3167 // NOTE: OSM.TransactionManager.IsLocalPublicAPI == true so cascade delete and RIC are disabled 3168 this.Delete(true); 3169 break; 3170 case EntityState.Detached: 3171 // Relationship fixup: * -> Detached 3172 this.Detach(); 3173 break; 3174 default: 3175 throw EntityUtil.InvalidEntityStateArgument("state"); 3176 } 3177 break; 3178 case EntityState.Deleted: 3179 switch (requestedState) 3180 { 3181 case EntityState.Added: 3182 // Throw if the entry has some not-Deleted relationships 3183 this.VerifyIsNotRelated(); 3184 this.TransitionRelationshipsForAdd(); 3185 this.ObjectStateManager.ReplaceKeyWithTemporaryKey(this); 3186 this._modifiedFields = null; 3187 this._originalValues = null; 3188 this._originalComplexObjects = null; 3189 this.State = EntityState.Added; 3190 _cache.FixupReferencesByForeignKeys(this); // Make sure refs based on FK values are set 3191 _cache.OnObjectStateManagerChanged(CollectionChangeAction.Add, Entity); 3192 break; 3193 case EntityState.Unchanged: 3194 // Throw if the entry has some not-Deleted relationship 3195 this.VerifyIsNotRelated(); 3196 this._modifiedFields = null; 3197 this._originalValues = null; 3198 this._originalComplexObjects = null; 3199 3200 this.ObjectStateManager.ChangeState(this, EntityState.Deleted, EntityState.Unchanged); 3201 this.State = EntityState.Unchanged; 3202 3203 _wrappedEntity.TakeSnapshot(this); // refresh snapshot 3204 3205 _cache.FixupReferencesByForeignKeys(this); // Make sure refs based on FK values are set 3206 _cache.OnObjectStateManagerChanged(CollectionChangeAction.Add, Entity); 3207 3208 // Relationship fixup: none 3209 break; 3210 case EntityState.Modified: 3211 // Throw if the entry has some not-Deleted relationship 3212 this.VerifyIsNotRelated(); 3213 // Relationship fixup: none 3214 this.ObjectStateManager.ChangeState(this, EntityState.Deleted, EntityState.Modified); 3215 this.State = EntityState.Modified; 3216 this.SetModifiedAll(); 3217 3218 _cache.FixupReferencesByForeignKeys(this); // Make sure refs based on FK values are set 3219 _cache.OnObjectStateManagerChanged(CollectionChangeAction.Add, Entity); 3220 3221 break; 3222 case EntityState.Deleted: 3223 // No-op 3224 break; 3225 case EntityState.Detached: 3226 // Relationship fixup: * -> Detached 3227 this.Detach(); 3228 break; 3229 default: 3230 throw EntityUtil.InvalidEntityStateArgument("state"); 3231 } 3232 break; 3233 case EntityState.Detached: 3234 Debug.Fail("detached entry"); 3235 break; 3236 } 3237 } 3238 UpdateOriginalValues(object entity)3239 internal void UpdateOriginalValues(object entity) 3240 { 3241 Debug.Assert(EntityState.Added != this.State, "Cannot change original values of an entity in the Added state"); 3242 3243 EntityState oldState = this.State; 3244 3245 this.UpdateRecordWithSetModified(entity, this.EditableOriginalValues); 3246 3247 if (oldState == EntityState.Unchanged && this.State == EntityState.Modified) 3248 { 3249 // The UpdateRecord changes state but doesn't update ObjectStateManager's dictionaries. 3250 this.ObjectStateManager.ChangeState(this, oldState, EntityState.Modified); 3251 } 3252 } 3253 UpdateRecordWithoutSetModified(object value, DbUpdatableDataRecord current)3254 internal void UpdateRecordWithoutSetModified(object value, DbUpdatableDataRecord current) 3255 { 3256 UpdateRecord(value, current, UpdateRecordBehavior.WithoutSetModified, s_EntityRoot); 3257 } 3258 UpdateRecordWithSetModified(object value, DbUpdatableDataRecord current)3259 internal void UpdateRecordWithSetModified(object value, DbUpdatableDataRecord current) 3260 { 3261 UpdateRecord(value, current, UpdateRecordBehavior.WithSetModified, s_EntityRoot); 3262 } 3263 3264 private enum UpdateRecordBehavior 3265 { 3266 WithoutSetModified, 3267 WithSetModified 3268 } 3269 3270 internal const int s_EntityRoot = -1; 3271 UpdateRecord(object value, DbUpdatableDataRecord current, UpdateRecordBehavior behavior, int propertyIndex)3272 private void UpdateRecord(object value, DbUpdatableDataRecord current, UpdateRecordBehavior behavior, int propertyIndex) 3273 { 3274 Debug.Assert(null != value, "null value"); 3275 Debug.Assert(null != current, "null CurrentValueRecord"); 3276 Debug.Assert(!(value is IEntityWrapper)); 3277 Debug.Assert(propertyIndex == s_EntityRoot || 3278 propertyIndex >= 0, "Unexpected index. Use -1 if the passed value is an entity, not a complex type object"); 3279 3280 // get Metadata for type 3281 StateManagerTypeMetadata typeMetadata = current._metadata; 3282 DataRecordInfo recordInfo = typeMetadata.DataRecordInfo; 3283 3284 foreach (FieldMetadata field in recordInfo.FieldMetadata) 3285 { 3286 int index = field.Ordinal; 3287 3288 var member = typeMetadata.Member(index); 3289 object fieldValue = member.GetValue(value) ?? DBNull.Value; 3290 3291 if (Helper.IsComplexType(field.FieldType.TypeUsage.EdmType)) 3292 { 3293 object existing = current.GetValue(index); 3294 // Ensure that the existing ComplexType value is not null. This is not supported. 3295 if (existing == DBNull.Value) 3296 { 3297 throw EntityUtil.NullableComplexTypesNotSupported(field.FieldType.Name); 3298 } 3299 else if (fieldValue != DBNull.Value) 3300 { 3301 // There is both an IExtendedDataRecord and an existing CurrentValueRecord 3302 3303 // This part is different than Shaper.UpdateRecord - we have to remember the name of property on the entity (for complex types) 3304 // For property of a complex type the rootCLayerName is CLayerName of the complex property on the entity. 3305 this.UpdateRecord(fieldValue, (DbUpdatableDataRecord)existing, 3306 behavior, 3307 propertyIndex == s_EntityRoot ? index : propertyIndex); 3308 } 3309 } 3310 else 3311 { 3312 Debug.Assert(Helper.IsScalarType(field.FieldType.TypeUsage.EdmType), "Expected primitive or enum type."); 3313 3314 // Set the new value if it doesn't match the existing value or if the field is modified, not a primary key, and 3315 // this entity has a conceptual null, since setting the field may then clear the conceptual null--see 640443. 3316 if (HasRecordValueChanged(current, index, fieldValue) && !member.IsPartOfKey) 3317 { 3318 current.SetValue(index, fieldValue); 3319 3320 if (behavior == UpdateRecordBehavior.WithSetModified) 3321 { 3322 // This part is different than Shaper.UpdateRecord - we have to mark the field as modified. 3323 // For property of a complex type the rootCLayerName is CLayerName of the complex property on the entity. 3324 SetModifiedPropertyInternal(propertyIndex == s_EntityRoot ? index : propertyIndex); 3325 } 3326 } 3327 } 3328 } 3329 } 3330 HasRecordValueChanged(DbDataRecord record, int propertyIndex, object newFieldValue)3331 internal bool HasRecordValueChanged(DbDataRecord record, int propertyIndex, object newFieldValue) 3332 { 3333 object existing = record.GetValue(propertyIndex); 3334 return (existing != newFieldValue) && 3335 (((object)DBNull.Value == newFieldValue) || 3336 ((object)DBNull.Value == existing) || 3337 (!ByValueEqualityComparer.Default.Equals(existing, newFieldValue))) || 3338 (_cache.EntryHasConceptualNull(this) && _modifiedFields != null && _modifiedFields[propertyIndex]); 3339 } 3340 ApplyCurrentValuesInternal(IEntityWrapper wrappedCurrentEntity)3341 internal void ApplyCurrentValuesInternal(IEntityWrapper wrappedCurrentEntity) 3342 { 3343 Debug.Assert(!IsKeyEntry, "Cannot apply values to a key KeyEntry."); 3344 Debug.Assert(wrappedCurrentEntity != null, "null entity wrapper"); 3345 3346 if (this.State != EntityState.Modified && 3347 this.State != EntityState.Unchanged) 3348 { 3349 throw EntityUtil.EntityMustBeUnchangedOrModified(this.State); 3350 } 3351 3352 if (this.WrappedEntity.IdentityType != wrappedCurrentEntity.IdentityType) 3353 { 3354 throw EntityUtil.EntitiesHaveDifferentType(this.Entity.GetType().FullName, wrappedCurrentEntity.Entity.GetType().FullName); 3355 } 3356 3357 this.CompareKeyProperties(wrappedCurrentEntity.Entity); 3358 3359 UpdateCurrentValueRecord(wrappedCurrentEntity.Entity); 3360 } 3361 UpdateCurrentValueRecord(object value)3362 internal void UpdateCurrentValueRecord(object value) 3363 { 3364 Debug.Assert(!(value is IEntityWrapper)); 3365 _wrappedEntity.UpdateCurrentValueRecord(value, this); 3366 } 3367 ApplyOriginalValuesInternal(IEntityWrapper wrappedOriginalEntity)3368 internal void ApplyOriginalValuesInternal(IEntityWrapper wrappedOriginalEntity) 3369 { 3370 Debug.Assert(!IsKeyEntry, "Cannot apply values to a key KeyEntry."); 3371 Debug.Assert(wrappedOriginalEntity != null, "null entity wrapper"); 3372 3373 if (this.State != EntityState.Modified && 3374 this.State != EntityState.Unchanged && 3375 this.State != EntityState.Deleted) 3376 { 3377 throw EntityUtil.EntityMustBeUnchangedOrModifiedOrDeleted(this.State); 3378 } 3379 3380 if (this.WrappedEntity.IdentityType != wrappedOriginalEntity.IdentityType) 3381 { 3382 throw EntityUtil.EntitiesHaveDifferentType(this.Entity.GetType().FullName, wrappedOriginalEntity.Entity.GetType().FullName); 3383 } 3384 3385 this.CompareKeyProperties(wrappedOriginalEntity.Entity); 3386 3387 // The ObjectStateEntry.UpdateModifiedFields uses a variation of Shaper.UpdateRecord method 3388 // which additionaly marks properties as modified as necessary. 3389 this.UpdateOriginalValues(wrappedOriginalEntity.Entity); 3390 } 3391 3392 /// <summary> 3393 /// For each FK contained in this entry, the entry is removed from the index maintained by 3394 /// the ObjectStateManager for that key. 3395 /// </summary> RemoveFromForeignKeyIndex()3396 internal void RemoveFromForeignKeyIndex() 3397 { 3398 if (!this.IsKeyEntry) 3399 { 3400 foreach (EntityReference relatedEnd in FindFKRelatedEnds()) 3401 { 3402 foreach(EntityKey foreignKey in relatedEnd.GetAllKeyValues()) 3403 { 3404 _cache.RemoveEntryFromForeignKeyIndex(foreignKey, this); 3405 } 3406 } 3407 _cache.AssertEntryDoesNotExistInForeignKeyIndex(this); 3408 } 3409 } 3410 3411 /// <summary> 3412 /// Looks at the foreign keys contained in this entry and performs fixup to the entities that 3413 /// they reference, or adds the key and this entry to the index of foreign keys that reference 3414 /// entities that we don't yet know about. 3415 /// </summary> FixupReferencesByForeignKeys(bool replaceAddedRefs)3416 internal void FixupReferencesByForeignKeys(bool replaceAddedRefs) 3417 { 3418 Debug.Assert(_cache != null, "Attempt to fixup detached entity entry"); 3419 _cache.TransactionManager.BeginGraphUpdate(); 3420 bool setIsLoaded = !(_cache.TransactionManager.IsAttachTracking || _cache.TransactionManager.IsAddTracking); 3421 try 3422 { 3423 foreach (var dependent in ForeignKeyDependents) 3424 { 3425 EntityReference relatedEnd = WrappedEntity.RelationshipManager.GetRelatedEndInternal( 3426 dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name) as EntityReference; 3427 Debug.Assert(relatedEnd != null, "Expected non-null EntityReference to principal."); 3428 // Prevent fixup using values that are effectivly null but aren't nullable. 3429 if (!ForeignKeyFactory.IsConceptualNullKey(relatedEnd.CachedForeignKey)) 3430 { 3431 FixupEntityReferenceToPrincipal(relatedEnd, null, setIsLoaded, replaceAddedRefs); 3432 } 3433 } 3434 } 3435 finally 3436 { 3437 _cache.TransactionManager.EndGraphUpdate(); 3438 } 3439 } 3440 FixupEntityReferenceByForeignKey(EntityReference reference)3441 internal void FixupEntityReferenceByForeignKey(EntityReference reference) 3442 { 3443 // The FK is changing, so the reference is no longer loaded from the store, even if we do fixup 3444 reference.SetIsLoaded(false); 3445 3446 // Remove the existing CachedForeignKey 3447 bool hasConceptualNullFk = ForeignKeyFactory.IsConceptualNullKey(reference.CachedForeignKey); 3448 if (hasConceptualNullFk) 3449 { 3450 ObjectStateManager.ForgetEntryWithConceptualNull(this, resetAllKeys: false); 3451 } 3452 3453 IEntityWrapper existingPrincipal = reference.ReferenceValue; 3454 EntityKey foreignKey = ForeignKeyFactory.CreateKeyFromForeignKeyValues(this, reference); 3455 3456 // Check if the new FK matches the key of the entity already at the principal end. 3457 // If it does, then don't change the ref. 3458 bool needToSetRef; 3459 if ((object)foreignKey == null || existingPrincipal.Entity == null) 3460 { 3461 needToSetRef = true; 3462 } 3463 else 3464 { 3465 EntityKey existingPrincipalKey = existingPrincipal.EntityKey; 3466 EntityEntry existingPrincipalEntry = existingPrincipal.ObjectStateEntry; 3467 // existingPrincipalKey may be null if this fixup code is being called in the middle of 3468 // adding an object. This can happen when using change tracking proxies with fixup. 3469 if ((existingPrincipalKey == null || existingPrincipalKey.IsTemporary) && existingPrincipalEntry != null) 3470 { 3471 // Build a temporary non-temp key for the added entity so we can see if it matches the new FK 3472 existingPrincipalKey = new EntityKey((EntitySet)existingPrincipalEntry.EntitySet, (IExtendedDataRecord)existingPrincipalEntry.CurrentValues); 3473 } 3474 3475 // If existingPrincipalKey is still a temp key here, then the equality check will fail 3476 needToSetRef = !foreignKey.Equals(existingPrincipalKey); 3477 } 3478 3479 if (_cache.TransactionManager.RelationshipBeingUpdated != reference) 3480 { 3481 if (needToSetRef) 3482 { 3483 ObjectStateManager stateManager = _cache; 3484 _cache.TransactionManager.BeginGraphUpdate(); 3485 // Keep track of this entity so that we don't try to delete/detach the entity while we're 3486 // working with it. This allows the FK to be set to some value without that entity being detached. 3487 // However, if the FK is being set to null, then for an identifying relationship we will detach. 3488 if ((object)foreignKey != null) 3489 { 3490 _cache.TransactionManager.EntityBeingReparented = Entity; 3491 } 3492 try 3493 { 3494 FixupEntityReferenceToPrincipal(reference, foreignKey, setIsLoaded: false, replaceExistingRef: true); 3495 } 3496 finally 3497 { 3498 Debug.Assert(_cache != null, "Unexpected null state manager."); 3499 _cache.TransactionManager.EntityBeingReparented = null; 3500 _cache.TransactionManager.EndGraphUpdate(); 3501 } 3502 } 3503 } 3504 else 3505 { 3506 // We only want to update the CachedForeignKey and not touch the EntityReference.Value/EntityKey 3507 FixupEntityReferenceToPrincipal(reference, foreignKey, setIsLoaded: false, replaceExistingRef: false); 3508 } 3509 } 3510 3511 /// <summary> 3512 /// Given a RelatedEnd that represents a FK from this dependent entity to the principal entity of the 3513 /// relationship, this method fixes up references between the two entities. 3514 /// </summary> 3515 /// <param name="relatedEnd">Represents a FK relationship to a principal</param> 3516 /// <param name="foreignKey">The foreign key, if it has already been computed</param> 3517 /// <param name="setIsLoaded">If true, then the IsLoaded flag for the relationship is set</param> 3518 /// <param name="replaceExistingRef">If true, then any existing references will be replaced</param> FixupEntityReferenceToPrincipal(EntityReference relatedEnd, EntityKey foreignKey, bool setIsLoaded, bool replaceExistingRef)3519 internal void FixupEntityReferenceToPrincipal(EntityReference relatedEnd, EntityKey foreignKey, bool setIsLoaded, bool replaceExistingRef) 3520 { 3521 Debug.Assert(relatedEnd != null, "Found null RelatedEnd or EntityCollection to principal"); 3522 if (foreignKey == null) 3523 { 3524 foreignKey = ForeignKeyFactory.CreateKeyFromForeignKeyValues(this, relatedEnd); 3525 } 3526 // Note that if we're not changing FKs directly, but rather as a result of fixup after a ref has changed, 3527 // and if the entity currently being pointed to is Added, then we shouldn't clobber it, because a ref to 3528 // an Added entity wins in this case. 3529 bool canModifyReference = _cache.TransactionManager.RelationshipBeingUpdated != relatedEnd && 3530 (!_cache.TransactionManager.IsForeignKeyUpdate || 3531 relatedEnd.ReferenceValue.ObjectStateEntry == null || 3532 relatedEnd.ReferenceValue.ObjectStateEntry.State != EntityState.Added); 3533 3534 // 3535 3536 relatedEnd.SetCachedForeignKey(foreignKey, this); 3537 ObjectStateManager.ForgetEntryWithConceptualNull(this, resetAllKeys: false); 3538 if (foreignKey != null) // Implies no value is null or CreateKeyFromForeignKeyValues would have returned null 3539 { 3540 // Lookup key in OSM. If found, then we can do fixup. If not, then need to add to index 3541 // Should not overwrite a reference at this point since this might cause the graph to 3542 // be shredded. This allows us to correctly detect key violations or RIC violations later. 3543 EntityEntry principalEntry; 3544 if (_cache.TryGetEntityEntry(foreignKey, out principalEntry) && 3545 !principalEntry.IsKeyEntry && 3546 principalEntry.State != EntityState.Deleted && 3547 (replaceExistingRef || WillNotRefSteal(relatedEnd, principalEntry.WrappedEntity)) && 3548 relatedEnd.CanSetEntityType(principalEntry.WrappedEntity)) 3549 { 3550 if (canModifyReference) 3551 { 3552 // We add both sides to the promoted EntityKeyRefs collection because it could be the dependent or 3553 // the principal or both that are being added. Having extra members in this index doesn't hurt. 3554 if (_cache.TransactionManager.PopulatedEntityReferences != null) 3555 { 3556 Debug.Assert(_cache.TransactionManager.IsAddTracking || _cache.TransactionManager.IsAttachTracking, 3557 "PromotedEntityKeyRefs is non-null while not tracking add or attach"); 3558 _cache.TransactionManager.PopulatedEntityReferences.Add(relatedEnd); 3559 } 3560 3561 // Set the EntityKey on the RelatedEnd--this will cause the reference to be set and fixup to happen. 3562 relatedEnd.SetEntityKey(foreignKey, forceFixup: true); 3563 3564 if (_cache.TransactionManager.PopulatedEntityReferences != null) 3565 { 3566 EntityReference otherEnd = relatedEnd.GetOtherEndOfRelationship(principalEntry.WrappedEntity) as EntityReference; 3567 if (otherEnd != null) 3568 { 3569 _cache.TransactionManager.PopulatedEntityReferences.Add(otherEnd); 3570 } 3571 } 3572 } 3573 if (setIsLoaded && principalEntry.State != EntityState.Added) 3574 { 3575 relatedEnd.SetIsLoaded(true); 3576 } 3577 } 3578 else 3579 { 3580 // Add an entry to the index for later fixup 3581 _cache.AddEntryContainingForeignKeyToIndex(foreignKey, this); 3582 if (canModifyReference && replaceExistingRef && relatedEnd.ReferenceValue.Entity != null) 3583 { 3584 relatedEnd.ReferenceValue = EntityWrapperFactory.NullWrapper; 3585 } 3586 } 3587 } 3588 else if(canModifyReference) 3589 { 3590 if (replaceExistingRef && (relatedEnd.ReferenceValue.Entity != null || relatedEnd.EntityKey != null)) 3591 { 3592 relatedEnd.ReferenceValue = EntityWrapperFactory.NullWrapper; 3593 } 3594 if (setIsLoaded) 3595 { 3596 // This is the case where a query comes from the database with a null FK value. 3597 // We know that there is no related entity in the database and therefore the entity on the 3598 // other end of the relationship is as loaded as it is possible to be. Therefore, we 3599 // set the IsLoaded flag so that if a user asks we will tell them that (based on last known 3600 // state of the database) there is no need to do a load. 3601 relatedEnd.SetIsLoaded(true); 3602 } 3603 } 3604 } 3605 3606 /// <summary> 3607 /// Determins whether or not setting a reference will cause implicit ref stealing as part of FK fixup. 3608 /// If it would, then an exception is thrown. If it would not and we can safely overwrite the existing 3609 /// value, then true is returned. If it would not but we should not overwrite the existing value, 3610 /// then false is returned. WillNotRefSteal(EntityReference refToPrincipal, IEntityWrapper wrappedPrincipal)3611 private bool WillNotRefSteal(EntityReference refToPrincipal, IEntityWrapper wrappedPrincipal) 3612 { 3613 RelatedEnd dependentEnd = refToPrincipal.GetOtherEndOfRelationship(wrappedPrincipal); 3614 EntityReference refToDependent = dependentEnd as EntityReference; 3615 if ((refToPrincipal.ReferenceValue.Entity == null && refToPrincipal.NavigationPropertyIsNullOrMissing()) && 3616 (refToDependent == null || (refToDependent.ReferenceValue.Entity == null && refToDependent.NavigationPropertyIsNullOrMissing()))) 3617 { 3618 // Return true if the ref to principal is null and it's not 1:1 or it is 1:1 and the ref to dependent is also null. 3619 return true; 3620 } 3621 else if (refToDependent != null && 3622 (Object.ReferenceEquals(refToDependent.ReferenceValue.Entity, refToPrincipal.WrappedOwner.Entity) || 3623 refToDependent.CheckIfNavigationPropertyContainsEntity(refToPrincipal.WrappedOwner))) 3624 { 3625 return true; 3626 } 3627 else if (refToDependent == null || 3628 Object.ReferenceEquals(refToPrincipal.ReferenceValue.Entity, wrappedPrincipal.Entity) || 3629 refToPrincipal.CheckIfNavigationPropertyContainsEntity(wrappedPrincipal)) 3630 { 3631 // Return false if the ref to principal is non-null and it's not 1:1 3632 return false; 3633 } 3634 else 3635 { 3636 // Else it is 1:1 and one side or the other is non-null => reference steal! 3637 throw EntityUtil.CannotAddMoreThanOneEntityToEntityReference( 3638 refToDependent.RelationshipNavigation.To, 3639 refToDependent.RelationshipNavigation.RelationshipName); 3640 } 3641 } 3642 3643 /// <summary> 3644 /// Given that this entry represents an entity on the dependent side of a FK, this method attempts to return the key of the 3645 /// entity on the principal side of the FK. If the two entities both exist in the context, then the primary key of 3646 /// the principal entity is found and returned. If the principal entity does not exist in the context, then a key 3647 /// for it is built up from the foreign key values contained in the dependent entity. 3648 /// </summary> 3649 /// <param name="principalRole">The role indicating the FK to navigate</param> 3650 /// <param name="principalKey">Set to the principal key or null on return</param> 3651 /// <returns>True if the principal key was found or built; false if it could not be found or built</returns> TryGetReferenceKey(AssociationEndMember principalRole, out EntityKey principalKey)3652 internal bool TryGetReferenceKey(AssociationEndMember principalRole, out EntityKey principalKey) 3653 { 3654 EntityReference relatedEnd = (RelatedEnd)RelationshipManager.GetRelatedEnd(principalRole.DeclaringType.FullName, principalRole.Name) as EntityReference; 3655 Debug.Assert(relatedEnd != null, "Expected there to be a non null EntityReference to the principal"); 3656 if (relatedEnd.CachedValue.Entity == null || relatedEnd.CachedValue.ObjectStateEntry == null) 3657 { 3658 principalKey = null; 3659 return false; 3660 } 3661 // 3662 principalKey = relatedEnd.EntityKey ?? relatedEnd.CachedValue.ObjectStateEntry.EntityKey; 3663 return principalKey != null; 3664 } 3665 3666 /// <summary> 3667 /// Performs fixuyup of foreign keys based on referencesd between objects. This should only be called 3668 /// for Added objects since this is the only time that references take precedence over FKs in fixup. 3669 /// </summary> FixupForeignKeysByReference()3670 internal void FixupForeignKeysByReference() 3671 { 3672 Debug.Assert(_cache != null, "Attempt to fixup detached entity entry"); 3673 _cache.TransactionManager.BeginFixupKeysByReference(); 3674 try 3675 { 3676 FixupForeignKeysByReference(null); 3677 } 3678 finally 3679 { 3680 _cache.TransactionManager.EndFixupKeysByReference(); 3681 } 3682 } 3683 3684 /// <summary> 3685 /// Fixup the FKs by the current reference values 3686 /// Do this in the order of fixing up values from the principal ends first, and then propogate those values to the dependents 3687 /// </summary> 3688 /// <param name="visited"></param> FixupForeignKeysByReference(List<EntityEntry> visited)3689 private void FixupForeignKeysByReference(List<EntityEntry> visited) 3690 { 3691 EntitySet entitySet = EntitySet as EntitySet; 3692 3693 // Perf optimization to avoid all this work if the entity doesn't participate in any FK relationships 3694 if (!entitySet.HasForeignKeyRelationships) 3695 { 3696 return; 3697 } 3698 3699 foreach (var dependent in ForeignKeyDependents) 3700 { 3701 // Added dependent. Make sure we traverse all the way to the top-most principal before beginging fixup. 3702 EntityReference reference = RelationshipManager.GetRelatedEndInternal(dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name) as EntityReference; 3703 Debug.Assert(reference != null, "Expected reference to exist and be an entity reference (not collection)"); 3704 IEntityWrapper existingPrincipal = reference.ReferenceValue; 3705 if (existingPrincipal.Entity != null) 3706 { 3707 EntityEntry principalEntry = existingPrincipal.ObjectStateEntry; 3708 bool? isOneToMany = null; 3709 if (principalEntry != null && principalEntry.State == EntityState.Added && 3710 (principalEntry != this || (isOneToMany = reference.GetOtherEndOfRelationship(existingPrincipal) is EntityReference).Value)) 3711 { 3712 visited = visited ?? new List<EntityEntry>(); 3713 if (visited.Contains(this)) 3714 { 3715 if (!isOneToMany.HasValue) 3716 { 3717 isOneToMany = reference.GetOtherEndOfRelationship(existingPrincipal) is EntityReference; 3718 } 3719 if (isOneToMany.Value) 3720 { 3721 // Cycles in constraints are dissallowed except for 1:* self references 3722 throw EntityUtil.CircularRelationshipsWithReferentialConstraints(); 3723 } 3724 } 3725 else 3726 { 3727 visited.Add(this); 3728 principalEntry.FixupForeignKeysByReference(visited); 3729 visited.Remove(this); 3730 } 3731 } 3732 // "forceChange" is false because we don't want to actually set the property values 3733 // here if they are aready set to the same thing--we don't want the events and setting 3734 // the modified flag is irrelavent during AcceptChanges. 3735 reference.UpdateForeignKeyValues(this.WrappedEntity, existingPrincipal, changedFKs: null, forceChange: false); 3736 } 3737 else 3738 { 3739 EntityKey principalKey = reference.EntityKey; 3740 if (principalKey != null && !principalKey.IsTemporary) 3741 { 3742 reference.UpdateForeignKeyValues(this.WrappedEntity, principalKey); 3743 } 3744 } 3745 } 3746 3747 foreach (var principal in ForeignKeyPrincipals) 3748 { 3749 // Added prinipal end. Fixup FKs on all dependents. 3750 // This is necessary because of the case where a PK in an added entity is changed after it and its dependnents 3751 // are added to the context--see bug 628752. 3752 bool fkOverlapsPk = false; // Set to true if we find out that the FK overlaps the dependent PK 3753 bool dependentPropsChecked = false; // Set to true once we have checked whether or not the FK overlaps the PK 3754 EntityKey principalKey = WrappedEntity.EntityKey; 3755 RelatedEnd principalEnd = RelationshipManager.GetRelatedEndInternal(principal.Item1.ElementType.FullName, principal.Item2.ToRole.Name); 3756 foreach (IEntityWrapper dependent in principalEnd.GetWrappedEntities()) 3757 { 3758 EntityEntry dependentEntry = dependent.ObjectStateEntry; 3759 Debug.Assert(dependentEntry != null, "Should have fully tracked graph at this point."); 3760 if (dependentEntry.State != EntityState.Added && !dependentPropsChecked) 3761 { 3762 dependentPropsChecked = true; 3763 foreach (EdmProperty dependentProp in principal.Item2.ToProperties) 3764 { 3765 int dependentOrdinal = dependentEntry._cacheTypeMetadata.GetOrdinalforOLayerMemberName(dependentProp.Name); 3766 StateManagerMemberMetadata member = dependentEntry._cacheTypeMetadata.Member(dependentOrdinal); 3767 if (member.IsPartOfKey) 3768 { 3769 // If the FK overlpas the PK then we can't set it for non-Added entities. 3770 // In this situation we just continue with the next one and if the conflict 3771 // may then be flagged later as a RIC check. 3772 fkOverlapsPk = true; 3773 break; 3774 } 3775 } 3776 } 3777 // This code relies on the fact that a dependent referenced to an Added principal must be either Added or 3778 // Modified since we cannpt trust thestate of the principal PK and therefore the dependent FK must also 3779 // be considered not completely trusted--it may need to be updated. 3780 if (dependentEntry.State == EntityState.Added || (dependentEntry.State == EntityState.Modified && !fkOverlapsPk)) 3781 { 3782 EntityReference principalRef = principalEnd.GetOtherEndOfRelationship(dependent) as EntityReference; 3783 Debug.Assert(principalRef != null, "Expected reference to exist and be an entity reference (not collection)"); 3784 // "forceChange" is false because we don't want to actually set the property values 3785 // here if they are aready set to the same thing--we don't want the events and setting 3786 // the modified flag is irrelavent during AcceptChanges. 3787 principalRef.UpdateForeignKeyValues(dependent, WrappedEntity, changedFKs: null, forceChange: false); 3788 } 3789 } 3790 } 3791 } 3792 IsPropertyAForeignKey(string propertyName)3793 private bool IsPropertyAForeignKey(string propertyName) 3794 { 3795 foreach (var dependent in ForeignKeyDependents) 3796 { 3797 foreach (EdmProperty property in dependent.Item2.ToProperties) 3798 { 3799 if (property.Name == propertyName) 3800 { 3801 return true; 3802 } 3803 } 3804 } 3805 return false; 3806 } 3807 IsPropertyAForeignKey(string propertyName, out List<Pair<string, string>> relationships)3808 private bool IsPropertyAForeignKey(string propertyName, out List<Pair<string, string>> relationships) 3809 { 3810 relationships = null; 3811 3812 foreach (var dependent in ForeignKeyDependents) 3813 { 3814 foreach (EdmProperty property in dependent.Item2.ToProperties) 3815 { 3816 if (property.Name == propertyName) 3817 { 3818 if (relationships == null) 3819 { 3820 relationships = new List<Pair<string, string>>(); 3821 } 3822 relationships.Add(new Pair<string, string>(dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name)); 3823 break; 3824 } 3825 } 3826 } 3827 3828 return relationships != null; 3829 } 3830 FindRelatedEntityKeysByForeignKeys( out Dictionary<RelatedEnd, HashSet<EntityKey>> relatedEntities, bool useOriginalValues)3831 internal void FindRelatedEntityKeysByForeignKeys( 3832 out Dictionary<RelatedEnd, HashSet<EntityKey>> relatedEntities, 3833 bool useOriginalValues) 3834 { 3835 relatedEntities = null; 3836 3837 foreach (var dependent in ForeignKeyDependents) 3838 { 3839 AssociationSet associationSet = dependent.Item1; 3840 ReferentialConstraint constraint = dependent.Item2; 3841 // Get association end members for the dependent and the principal ends 3842 string dependentId = constraint.ToRole.Identity; 3843 var setEnds = associationSet.AssociationSetEnds; 3844 Debug.Assert(associationSet.AssociationSetEnds.Count == 2, "Expected an association set with only two ends."); 3845 AssociationEndMember dependentEnd; 3846 AssociationEndMember principalEnd; 3847 if (setEnds[0].CorrespondingAssociationEndMember.Identity == dependentId) 3848 { 3849 dependentEnd = setEnds[0].CorrespondingAssociationEndMember; 3850 principalEnd = setEnds[1].CorrespondingAssociationEndMember; 3851 } 3852 else 3853 { 3854 dependentEnd = setEnds[1].CorrespondingAssociationEndMember; 3855 principalEnd = setEnds[0].CorrespondingAssociationEndMember; 3856 } 3857 3858 EntitySet principalEntitySet = MetadataHelper.GetEntitySetAtEnd(associationSet, principalEnd); 3859 EntityKey foreignKey = ForeignKeyFactory.CreateKeyFromForeignKeyValues(this, constraint, principalEntitySet, useOriginalValues); 3860 if (foreignKey != null) // Implies no value is null or CreateKeyFromForeignKeyValues would have returned null 3861 { 3862 EntityReference reference = RelationshipManager.GetRelatedEndInternal( 3863 associationSet.ElementType.FullName, constraint.FromRole.Name) as EntityReference; 3864 3865 // only for deleted relationships the hashset can have > 1 elements 3866 HashSet<EntityKey> entityKeys; 3867 relatedEntities = relatedEntities != null ? relatedEntities : new Dictionary<RelatedEnd, HashSet<EntityKey>>(); 3868 if (!relatedEntities.TryGetValue(reference, out entityKeys)) 3869 { 3870 entityKeys = new HashSet<EntityKey>(); 3871 relatedEntities.Add(reference, entityKeys); 3872 } 3873 entityKeys.Add(foreignKey); 3874 } 3875 } 3876 } 3877 3878 /// <summary> 3879 /// Returns a list of all RelatedEnds for this entity 3880 /// that are the dependent end of an FK Association 3881 /// </summary> FindFKRelatedEnds()3882 internal IEnumerable<EntityReference> FindFKRelatedEnds() 3883 { 3884 HashSet<EntityReference> relatedEnds = new HashSet<EntityReference>(); 3885 3886 foreach (var dependent in ForeignKeyDependents) 3887 { 3888 EntityReference reference = RelationshipManager.GetRelatedEndInternal( 3889 dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name) as EntityReference; 3890 relatedEnds.Add(reference); 3891 } 3892 return relatedEnds; 3893 } 3894 3895 /// <summary> 3896 /// Identifies any changes in FK's and creates entries in; 3897 /// - TransactionManager.AddedRelationshipsByForeignKey 3898 /// - TransactionManager.DeletedRelationshipsByForeignKey 3899 /// 3900 /// If the FK change will result in fix-up then two entries 3901 /// are added to TransactionManager.AddedRelationshipsByForeignKey 3902 /// (one for each direction of the new realtionship) 3903 /// </summary> DetectChangesInForeignKeys()3904 internal void DetectChangesInForeignKeys() 3905 { 3906 //DetectChangesInProperties should already have marked this entity as dirty 3907 Debug.Assert(this.State == EntityState.Added || this.State == EntityState.Modified, "unexpected state"); 3908 3909 //We are going to be adding data to the TransactionManager 3910 TransactionManager tm = this.ObjectStateManager.TransactionManager; 3911 3912 foreach (EntityReference entityReference in this.FindFKRelatedEnds()) 3913 { 3914 EntityKey currentKey = ForeignKeyFactory.CreateKeyFromForeignKeyValues(this, entityReference); 3915 EntityKey originalKey = entityReference.CachedForeignKey; 3916 bool originalKeyIsConceptualNull = ForeignKeyFactory.IsConceptualNullKey(originalKey); 3917 3918 //If both keys are null there is nothing to check 3919 if (originalKey != null || currentKey != null) 3920 { 3921 if (originalKey == null) 3922 { 3923 //If original is null then we are just adding a relationship 3924 EntityEntry entry; 3925 this.ObjectStateManager.TryGetEntityEntry(currentKey, out entry); 3926 this.AddRelationshipDetectedByForeignKey(tm.AddedRelationshipsByForeignKey, tm.AddedRelationshipsByPrincipalKey, currentKey, entry, entityReference); 3927 } 3928 else if (currentKey == null) 3929 { 3930 //If current is null we are just deleting a relationship 3931 Debug.Assert(!originalKeyIsConceptualNull, "If FK is nullable there shouldn't be a conceptual null set"); 3932 this.AddDetectedRelationship(tm.DeletedRelationshipsByForeignKey, originalKey, entityReference); 3933 } 3934 //If there is a Conceptual Null set we need to check if the current values 3935 //are different from the values when the Conceptual Null was created 3936 else if (!currentKey.Equals(originalKey) 3937 && (!originalKeyIsConceptualNull || ForeignKeyFactory.IsConceptualNullKeyChanged(originalKey, currentKey))) 3938 { 3939 //If keys don't match then we are always adding 3940 EntityEntry entry; 3941 this.ObjectStateManager.TryGetEntityEntry(currentKey, out entry); 3942 this.AddRelationshipDetectedByForeignKey(tm.AddedRelationshipsByForeignKey, tm.AddedRelationshipsByPrincipalKey, currentKey, entry, entityReference); 3943 3944 //And if the original key wasn't a conceptual null we are also deleting 3945 if (!originalKeyIsConceptualNull) 3946 { 3947 this.AddDetectedRelationship(tm.DeletedRelationshipsByForeignKey, originalKey, entityReference); 3948 } 3949 } 3950 } 3951 } 3952 } 3953 3954 /// <summary> 3955 /// True if the underlying entity is not capable of tracking changes to complex types such that 3956 /// DetectChanges is required to do this. 3957 /// </summary> 3958 internal bool RequiresComplexChangeTracking 3959 { 3960 get { return _requiresComplexChangeTracking; } 3961 } 3962 3963 /// <summary> 3964 /// True if the underlying entity is not capable of tracking changes to scalars such that 3965 /// DetectChanges is required to do this. 3966 /// </summary> 3967 internal bool RequiresScalarChangeTracking 3968 { 3969 get { return _requiresScalarChangeTracking; } 3970 } 3971 3972 /// <summary> 3973 /// True if the underlying entity is not capable of performing full change tracking such that 3974 /// it must be considered by at least some parts of DetectChanges. 3975 /// </summary> 3976 internal bool RequiresAnyChangeTracking 3977 { 3978 get { return _requiresAnyChangeTracking; } 3979 } 3980 } 3981 } 3982