1 //------------------------------------------------------------------------------ 2 // <copyright file="Shaper.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 // <owner current="true" primary="true">Microsoft</owner> 6 // <owner current="true" primary="false">Microsoft</owner> 7 //------------------------------------------------------------------------------ 8 9 namespace System.Data.Common.Internal.Materialization 10 { 11 using System.Collections.Generic; 12 using System.Data.Common.Utils; 13 using System.Data.Metadata.Edm; 14 using System.Data.Objects; 15 using System.Data.Objects.DataClasses; 16 using System.Data.Objects.Internal; 17 using System.Data.Spatial; 18 using System.Diagnostics; 19 using System.Reflection; 20 21 /// <summary> 22 /// Shapes store reader values into EntityClient/ObjectQuery results. Also maintains 23 /// state used by materializer delegates. 24 /// </summary> 25 internal abstract class Shaper 26 { 27 #region constructor 28 Shaper(DbDataReader reader, ObjectContext context, MetadataWorkspace workspace, MergeOption mergeOption, int stateCount)29 internal Shaper(DbDataReader reader, ObjectContext context, MetadataWorkspace workspace, MergeOption mergeOption, int stateCount) 30 { 31 Debug.Assert(context == null || workspace == context.MetadataWorkspace, "workspace must match context's workspace"); 32 33 this.Reader = reader; 34 this.MergeOption = mergeOption; 35 this.State = new object[stateCount]; 36 this.Context = context; 37 this.Workspace = workspace; 38 this.AssociationSpaceMap = new Dictionary<AssociationType, AssociationType>(); 39 this.spatialReader = new Singleton<DbSpatialDataReader>(CreateSpatialDataReader); 40 } 41 42 #endregion 43 44 #region OnMaterialized storage 45 46 /// <summary> 47 /// Keeps track of the entities that have been materialized so that we can fire an OnMaterialized 48 /// for them before returning control to the caller. 49 /// </summary> 50 private IList<IEntityWrapper> _materializedEntities; 51 52 #endregion 53 54 #region runtime callable/accessible code 55 56 // Code in this section is called from the delegates produced by the Translator. It 57 // may not show up if you search using Find All References...use Find in Files instead. 58 // 59 // Many items on this class are public, simply to make the job of producing the 60 // expressions that use them simpler. If you have a hankering to make them private, 61 // you will need to modify the code in the Translator that does the GetMethod/GetField 62 // to use BindingFlags.NonPublic | BindingFlags.Instance as well. 63 // 64 // Debug.Asserts that fire from the code in this region will probably create a 65 // SecurityException in the Coordinator's Read method since those are restricted when 66 // running the Shaper. 67 68 /// <summary> 69 /// The store data reader we're pulling data from 70 /// </summary> 71 public readonly DbDataReader Reader; 72 73 /// <summary> 74 /// The state slots we use in the coordinator expression. 75 /// </summary> 76 public readonly object[] State; 77 78 /// <summary> 79 /// The context the shaper is performing for. 80 /// </summary> 81 public readonly ObjectContext Context; 82 83 /// <summary> 84 /// The workspace we are performing for; yes we could get it from the context, but 85 /// it's much easier to just have it handy. 86 /// </summary> 87 public readonly MetadataWorkspace Workspace; 88 89 /// <summary> 90 /// The merge option this shaper is performing under/for. 91 /// </summary> 92 public readonly MergeOption MergeOption; 93 94 /// <summary> 95 /// A mapping of CSpace AssociationTypes to OSpace AssociationTypes 96 /// Used for faster lookup/retrieval of AssociationTypes during materialization 97 /// </summary> 98 private readonly Dictionary<AssociationType, AssociationType> AssociationSpaceMap; 99 100 /// <summary> 101 /// Caches Tuples of EntitySet, AssociationType, and source member name for which RelatedEnds exist. 102 /// </summary> 103 private HashSet<Tuple<string, string, string>> _relatedEndCache; 104 105 /// <summary> 106 /// Utility method used to evaluate a multi-discriminator column map. Takes 107 /// discriminator values and determines the appropriate entity type, then looks up 108 /// the appropriate handler and invokes it. 109 /// </summary> Discriminate(object[] discriminatorValues, Func<object[], EntityType> discriminate, KeyValuePair<EntityType, Func<Shaper, TElement>>[] elementDelegates)110 public TElement Discriminate<TElement>(object[] discriminatorValues, Func<object[], EntityType> discriminate, KeyValuePair<EntityType, Func<Shaper, TElement>>[] elementDelegates) 111 { 112 EntityType entityType = discriminate(discriminatorValues); 113 Func<Shaper, TElement> elementDelegate = null; 114 foreach (KeyValuePair<EntityType, Func<Shaper, TElement>> typeDelegatePair in elementDelegates) 115 { 116 if (typeDelegatePair.Key == entityType) 117 { 118 elementDelegate = typeDelegatePair.Value; 119 } 120 } 121 return elementDelegate(this); 122 } 123 HandleEntityNoTracking(IEntityWrapper wrappedEntity)124 public IEntityWrapper HandleEntityNoTracking<TEntity>(IEntityWrapper wrappedEntity) 125 { 126 Debug.Assert(null != wrappedEntity, "wrapped entity is null"); 127 RegisterMaterializedEntityForEvent(wrappedEntity); 128 return wrappedEntity; 129 } 130 131 /// <summary> 132 /// REQUIRES:: entity is not null and MergeOption is OverwriteChanges or PreserveChanges 133 /// Handles state management for an entity returned by a query. Where an existing entry 134 /// exists, updates that entry and returns the existing entity. Otherwise, the entity 135 /// passed in is returned. 136 /// </summary> HandleEntity(IEntityWrapper wrappedEntity, EntityKey entityKey, EntitySet entitySet)137 public IEntityWrapper HandleEntity<TEntity>(IEntityWrapper wrappedEntity, EntityKey entityKey, EntitySet entitySet) 138 { 139 Debug.Assert(MergeOption.NoTracking != this.MergeOption, "no need to HandleEntity if there's no tracking"); 140 Debug.Assert(MergeOption.AppendOnly != this.MergeOption, "use HandleEntityAppendOnly instead..."); 141 Debug.Assert(null != wrappedEntity, "wrapped entity is null"); 142 Debug.Assert(null != wrappedEntity.Entity, "if HandleEntity is called, there must be an entity"); 143 144 IEntityWrapper result = wrappedEntity; 145 146 // no entity set, so no tracking is required for this entity 147 if (null != (object)entityKey) 148 { 149 Debug.Assert(null != entitySet, "if there is an entity key, there must also be an entity set"); 150 151 // check for an existing entity with the same key 152 EntityEntry existingEntry = this.Context.ObjectStateManager.FindEntityEntry(entityKey); 153 if (null != existingEntry && !existingEntry.IsKeyEntry) 154 { 155 Debug.Assert(existingEntry.EntityKey.Equals(entityKey), "Found ObjectStateEntry with wrong EntityKey"); 156 UpdateEntry<TEntity>(wrappedEntity, existingEntry); 157 result = existingEntry.WrappedEntity; 158 } 159 else 160 { 161 RegisterMaterializedEntityForEvent(result); 162 if (null == existingEntry) 163 { 164 Context.ObjectStateManager.AddEntry(wrappedEntity, entityKey, entitySet, "HandleEntity", false); 165 } 166 else 167 { 168 Context.ObjectStateManager.PromoteKeyEntry(existingEntry, wrappedEntity, (IExtendedDataRecord)null, false, /*setIsLoaded*/ true, /*keyEntryInitialized*/ false, "HandleEntity"); 169 } 170 } 171 } 172 return result; 173 } 174 175 /// <summary> 176 /// REQUIRES:: entity exists; MergeOption is AppendOnly 177 /// Handles state management for an entity with the given key. When the entity already exists 178 /// in the state manager, it is returned directly. Otherwise, the entityDelegate is invoked and 179 /// the resulting entity is returned. 180 /// </summary> HandleEntityAppendOnly(Func<Shaper, IEntityWrapper> constructEntityDelegate, EntityKey entityKey, EntitySet entitySet)181 public IEntityWrapper HandleEntityAppendOnly<TEntity>(Func<Shaper, IEntityWrapper> constructEntityDelegate, EntityKey entityKey, EntitySet entitySet) 182 { 183 Debug.Assert(this.MergeOption == MergeOption.AppendOnly, "only use HandleEntityAppendOnly when MergeOption is AppendOnly"); 184 Debug.Assert(null != constructEntityDelegate, "must provide delegate to construct the entity"); 185 186 IEntityWrapper result; 187 188 if (null == (object)entityKey) 189 { 190 // no entity set, so no tracking is required for this entity, just 191 // call the delegate to "materialize" it. 192 result = constructEntityDelegate(this); 193 RegisterMaterializedEntityForEvent(result); 194 } 195 else 196 { 197 Debug.Assert(null != entitySet, "if there is an entity key, there must also be an entity set"); 198 199 // check for an existing entity with the same key 200 EntityEntry existingEntry = this.Context.ObjectStateManager.FindEntityEntry(entityKey); 201 if (null != existingEntry && !existingEntry.IsKeyEntry) 202 { 203 Debug.Assert(existingEntry.EntityKey.Equals(entityKey), "Found ObjectStateEntry with wrong EntityKey"); 204 if (typeof(TEntity) != existingEntry.WrappedEntity.IdentityType) 205 { 206 throw EntityUtil.RecyclingEntity(existingEntry.EntityKey, typeof(TEntity), existingEntry.WrappedEntity.IdentityType); 207 } 208 209 if (EntityState.Added == existingEntry.State) 210 { 211 throw EntityUtil.AddedEntityAlreadyExists(existingEntry.EntityKey); 212 } 213 result = existingEntry.WrappedEntity; 214 } 215 else 216 { 217 // We don't already have the entity, so construct it 218 result = constructEntityDelegate(this); 219 RegisterMaterializedEntityForEvent(result); 220 if (null == existingEntry) 221 { 222 Context.ObjectStateManager.AddEntry(result, entityKey, entitySet, "HandleEntity", false); 223 } 224 else 225 { 226 Context.ObjectStateManager.PromoteKeyEntry(existingEntry, result, (IExtendedDataRecord)null, false, /*setIsLoaded*/ true, /*keyEntryInitialized*/ false, "HandleEntity"); 227 } 228 } 229 } 230 return result; 231 } 232 233 /// <summary> 234 /// Call to ensure a collection of full-spanned elements are added 235 /// into the state manager properly. We registers an action to be called 236 /// when the collection is closed that pulls the collection of full spanned 237 /// objects into the state manager. 238 /// </summary> HandleFullSpanCollection(IEntityWrapper wrappedEntity, Coordinator<T_TargetEntity> coordinator, AssociationEndMember targetMember)239 public IEntityWrapper HandleFullSpanCollection<T_SourceEntity, T_TargetEntity>(IEntityWrapper wrappedEntity, Coordinator<T_TargetEntity> coordinator, AssociationEndMember targetMember) 240 { 241 Debug.Assert(null != wrappedEntity, "wrapped entity is null"); 242 if (null != wrappedEntity.Entity) 243 { 244 coordinator.RegisterCloseHandler((state, spannedEntities) => FullSpanAction(wrappedEntity, spannedEntities, targetMember)); 245 } 246 return wrappedEntity; 247 } 248 249 /// <summary> 250 /// Call to ensure a single full-spanned element is added into 251 /// the state manager properly. 252 /// </summary> HandleFullSpanElement(IEntityWrapper wrappedSource, IEntityWrapper wrappedSpannedEntity, AssociationEndMember targetMember)253 public IEntityWrapper HandleFullSpanElement<T_SourceEntity, T_TargetEntity>(IEntityWrapper wrappedSource, IEntityWrapper wrappedSpannedEntity, AssociationEndMember targetMember) 254 { 255 Debug.Assert(null != wrappedSource, "wrapped entity is null"); 256 if (wrappedSource.Entity == null) 257 { 258 return wrappedSource; 259 } 260 List<IEntityWrapper> spannedEntities = null; 261 if (wrappedSpannedEntity.Entity != null) 262 { 263 // There was a single entity in the column 264 // Create a list so we can perform the same logic as a collection of entities 265 spannedEntities = new List<IEntityWrapper>(1); 266 spannedEntities.Add(wrappedSpannedEntity); 267 } 268 else 269 { 270 EntityKey sourceKey = wrappedSource.EntityKey; 271 CheckClearedEntryOnSpan(null, wrappedSource, sourceKey, targetMember); 272 } 273 FullSpanAction(wrappedSource, spannedEntities, targetMember); 274 return wrappedSource; 275 } 276 277 /// <summary> 278 /// Call to ensure a target entities key is added into the state manager 279 /// properly 280 /// </summary> HandleRelationshipSpan(IEntityWrapper wrappedEntity, EntityKey targetKey, AssociationEndMember targetMember)281 public IEntityWrapper HandleRelationshipSpan<T_SourceEntity>(IEntityWrapper wrappedEntity, EntityKey targetKey, AssociationEndMember targetMember) 282 { 283 if (null == wrappedEntity.Entity) 284 { 285 return wrappedEntity; 286 } 287 Debug.Assert(targetMember != null); 288 Debug.Assert(targetMember.RelationshipMultiplicity == RelationshipMultiplicity.One || targetMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne); 289 290 EntityKey sourceKey = wrappedEntity.EntityKey; 291 AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember); 292 CheckClearedEntryOnSpan(targetKey, wrappedEntity, sourceKey, targetMember); 293 294 if (null != (object)targetKey) 295 { 296 EntitySet targetEntitySet; 297 298 EntityContainer entityContainer = this.Context.MetadataWorkspace.GetEntityContainer( 299 targetKey.EntityContainerName, DataSpace.CSpace); 300 301 // find the correct AssociationSet 302 AssociationSet associationSet = MetadataHelper.GetAssociationsForEntitySetAndAssociationType(entityContainer, 303 targetKey.EntitySetName, (AssociationType)(targetMember.DeclaringType), targetMember.Name, out targetEntitySet); 304 Debug.Assert(associationSet != null, "associationSet should not be null"); 305 306 ObjectStateManager manager = Context.ObjectStateManager; 307 EntityState newEntryState; 308 // If there is an existing relationship entry, update it based on its current state and the MergeOption, otherwise add a new one 309 if (!ObjectStateManager.TryUpdateExistingRelationships(this.Context, this.MergeOption, associationSet, sourceMember, sourceKey, wrappedEntity, targetMember, targetKey, /*setIsLoaded*/ true, out newEntryState)) 310 { 311 // Try to find a state entry for the target key 312 EntityEntry targetEntry = null; 313 if (!manager.TryGetEntityEntry(targetKey, out targetEntry)) 314 { 315 // no entry exists for the target key 316 // create a key entry for the target 317 targetEntry = manager.AddKeyEntry(targetKey, targetEntitySet); 318 } 319 320 // SQLBU 557105. For 1-1 relationships we have to take care of the relationships of targetEntity 321 bool needNewRelationship = true; 322 switch (sourceMember.RelationshipMultiplicity) 323 { 324 case RelationshipMultiplicity.ZeroOrOne: 325 case RelationshipMultiplicity.One: 326 // devnote: targetEntry can be a key entry (targetEntry.Entity == null), 327 // but it that case this parameter won't be used in TryUpdateExistingRelationships 328 needNewRelationship = !ObjectStateManager.TryUpdateExistingRelationships(this.Context, 329 this.MergeOption, 330 associationSet, 331 targetMember, 332 targetKey, 333 targetEntry.WrappedEntity, 334 sourceMember, 335 sourceKey, 336 /*setIsLoaded*/ true, 337 out newEntryState); 338 339 // It is possible that as part of removing existing relationships, the key entry was deleted 340 // If that is the case, recreate the key entry 341 if (targetEntry.State == EntityState.Detached) 342 { 343 targetEntry = manager.AddKeyEntry(targetKey, targetEntitySet); 344 } 345 break; 346 case RelationshipMultiplicity.Many: 347 // we always need a new relationship with Many-To-Many, if there was no exact match between these two entities, so do nothing 348 break; 349 default: 350 Debug.Assert(false, "Unexpected sourceMember.RelationshipMultiplicity"); 351 break; 352 } 353 354 if (needNewRelationship) 355 { 356 357 // If the target entry is a key entry, then we need to add a relation 358 // between the source and target entries 359 // If we are in a state where we just need to add a new Deleted relation, we 360 // only need to do that and not touch the related ends 361 // If the target entry is a full entity entry, then we need to add 362 // the target entity to the source collection or reference 363 if (targetEntry.IsKeyEntry || newEntryState == EntityState.Deleted) 364 { 365 // Add a relationship between the source entity and the target key entry 366 RelationshipWrapper wrapper = new RelationshipWrapper(associationSet, sourceMember.Name, sourceKey, targetMember.Name, targetKey); 367 manager.AddNewRelation(wrapper, newEntryState); 368 } 369 else 370 { 371 Debug.Assert(!targetEntry.IsRelationship, "how IsRelationship?"); 372 if (targetEntry.State != EntityState.Deleted) 373 { 374 // The entry contains an entity, do collection or reference fixup 375 // This will also try to create a new relationship entry or will revert the delete on an existing deleted relationship 376 ObjectStateManager.AddEntityToCollectionOrReference( 377 this.MergeOption, wrappedEntity, sourceMember, 378 targetEntry.WrappedEntity, 379 targetMember, 380 /*setIsLoaded*/ true, 381 /*relationshipAlreadyExists*/ false, 382 /* inKeyEntryPromotion */ false); 383 } 384 else 385 { 386 // if the target entry is deleted, then the materializer needs to create a deleted relationship 387 // between the entity and the target entry so that if the entity is deleted, the update 388 // pipeline can find the relationship (even though it is deleted) 389 RelationshipWrapper wrapper = new RelationshipWrapper(associationSet, sourceMember.Name, sourceKey, targetMember.Name, targetKey); 390 manager.AddNewRelation(wrapper, EntityState.Deleted); 391 } 392 } 393 } 394 } 395 } 396 else 397 { 398 RelatedEnd relatedEnd; 399 if(TryGetRelatedEnd(wrappedEntity, (AssociationType)targetMember.DeclaringType, sourceMember.Name, targetMember.Name, out relatedEnd)) 400 { 401 SetIsLoadedForSpan(relatedEnd, false); 402 } 403 } 404 405 // else there is nothing else for us to do, the relationship has been handled already 406 return wrappedEntity; 407 } 408 TryGetRelatedEnd(IEntityWrapper wrappedEntity, AssociationType associationType, string sourceEndName, string targetEndName, out RelatedEnd relatedEnd)409 private bool TryGetRelatedEnd(IEntityWrapper wrappedEntity, AssociationType associationType, string sourceEndName, string targetEndName, out RelatedEnd relatedEnd) 410 { 411 Debug.Assert(associationType.DataSpace == DataSpace.CSpace); 412 413 // Get the OSpace AssociationType 414 AssociationType oSpaceAssociation; 415 if (!AssociationSpaceMap.TryGetValue((AssociationType)associationType, out oSpaceAssociation)) 416 { 417 oSpaceAssociation = this.Workspace.GetItemCollection(DataSpace.OSpace).GetItem<AssociationType>(associationType.FullName); 418 AssociationSpaceMap[(AssociationType)associationType] = oSpaceAssociation; 419 } 420 421 AssociationEndMember sourceEnd = null; 422 AssociationEndMember targetEnd = null; 423 foreach (var end in oSpaceAssociation.AssociationEndMembers) 424 { 425 if (end.Name == sourceEndName) 426 { 427 sourceEnd = end; 428 } 429 else if (end.Name == targetEndName) 430 { 431 targetEnd = end; 432 } 433 } 434 435 if (sourceEnd != null && targetEnd != null) 436 { 437 bool createRelatedEnd = false; 438 if (wrappedEntity.EntityKey == null) 439 { 440 // Free-floating entity--key is null, so don't have EntitySet for validation, so always create RelatedEnd 441 createRelatedEnd = true; 442 } 443 else 444 { 445 // It is possible, because of MEST, that we're trying to load a relationship that is valid for this EntityType 446 // in metadata, but is not valid in this case because the specific entity is part of an EntitySet that is not 447 // mapped in any AssociationSet for this association type. 448 // The metadata structure makes checking for this somewhat time consuming because of the loop required. 449 // Because the whole reason for this method is perf, we try to reduce the 450 // impact of this check by caching positive hits in a HashSet so we don't have to do this for 451 // every entity in a query. (We could also cache misses, but since these only happen in MEST, which 452 // is not common, we decided not to slow down the normal non-MEST case anymore by doing this.) 453 var entitySet = wrappedEntity.EntityKey.GetEntitySet(this.Workspace); 454 var relatedEndKey = Tuple.Create<string, string, string>(entitySet.Identity, associationType.Identity, sourceEndName); 455 456 if (_relatedEndCache == null) 457 { 458 _relatedEndCache = new HashSet<Tuple<string, string, string>>(); 459 } 460 461 if (_relatedEndCache.Contains(relatedEndKey)) 462 { 463 createRelatedEnd = true; 464 } 465 else 466 { 467 foreach (var entitySetBase in entitySet.EntityContainer.BaseEntitySets) 468 { 469 if ((EdmType)entitySetBase.ElementType == associationType) 470 { 471 if (((AssociationSet)entitySetBase).AssociationSetEnds[sourceEndName].EntitySet == entitySet) 472 { 473 createRelatedEnd = true; 474 _relatedEndCache.Add(relatedEndKey); 475 break; 476 } 477 } 478 } 479 } 480 } 481 if (createRelatedEnd) 482 { 483 relatedEnd = LightweightCodeGenerator.GetRelatedEnd(wrappedEntity.RelationshipManager, sourceEnd, targetEnd, null); 484 return true; 485 } 486 } 487 488 relatedEnd = null; 489 return false; 490 } 491 492 /// <summary> 493 /// Sets the IsLoaded flag to "true" 494 /// There are also rules for when this can be set based on MergeOption and the current value(s) in the related end. 495 /// </summary> SetIsLoadedForSpan(RelatedEnd relatedEnd, bool forceToTrue)496 private void SetIsLoadedForSpan(RelatedEnd relatedEnd, bool forceToTrue) 497 { 498 Debug.Assert(relatedEnd != null, "RelatedEnd should not be null"); 499 500 // We can now say this related end is "Loaded" 501 // The cases where we should set this to true are: 502 // AppendOnly: the related end is empty and does not point to a stub 503 // PreserveChanges: the related end is empty and does not point to a stub (otherwise, an Added item exists and IsLoaded should not change) 504 // OverwriteChanges: always 505 // NoTracking: always 506 if (!forceToTrue) 507 { 508 // Detect the empty value state of the relatedEnd 509 forceToTrue = relatedEnd.IsEmpty(); 510 EntityReference reference = relatedEnd as EntityReference; 511 if (reference != null) 512 { 513 forceToTrue &= reference.EntityKey == null; 514 } 515 } 516 if (forceToTrue || this.MergeOption == MergeOption.OverwriteChanges) 517 { 518 relatedEnd.SetIsLoaded(true); 519 } 520 } 521 522 /// <summary> 523 /// REQUIRES:: entity is not null and MergeOption is OverwriteChanges or PreserveChanges 524 /// Calls through to HandleEntity after retrieving the EntityKey from the given entity. 525 /// Still need this so that the correct key will be used for iPOCOs that implement IEntityWithKey 526 /// in a non-default manner. 527 /// </summary> HandleIEntityWithKey(IEntityWrapper wrappedEntity, EntitySet entitySet)528 public IEntityWrapper HandleIEntityWithKey<TEntity>(IEntityWrapper wrappedEntity, EntitySet entitySet) 529 { 530 Debug.Assert(null != wrappedEntity, "wrapped entity is null"); 531 return HandleEntity<TEntity>(wrappedEntity, wrappedEntity.EntityKey, entitySet); 532 } 533 534 /// <summary> 535 /// Calls through to the specified RecordState to set the value for the specified column ordinal. 536 /// </summary> SetColumnValue(int recordStateSlotNumber, int ordinal, object value)537 public bool SetColumnValue(int recordStateSlotNumber, int ordinal, object value) 538 { 539 RecordState recordState = (RecordState)this.State[recordStateSlotNumber]; 540 recordState.SetColumnValue(ordinal, value); 541 return true; // TRICKY: return true so we can use BitwiseOr expressions to string these guys together. 542 } 543 544 /// <summary> 545 /// Calls through to the specified RecordState to set the value for the EntityRecordInfo. 546 /// </summary> SetEntityRecordInfo(int recordStateSlotNumber, EntityKey entityKey, EntitySet entitySet)547 public bool SetEntityRecordInfo(int recordStateSlotNumber, EntityKey entityKey, EntitySet entitySet) 548 { 549 RecordState recordState = (RecordState)this.State[recordStateSlotNumber]; 550 recordState.SetEntityRecordInfo(entityKey, entitySet); 551 return true; // TRICKY: return true so we can use BitwiseOr expressions to string these guys together. 552 } 553 554 /// <summary> 555 /// REQUIRES:: should be called only by delegate allocating this state. 556 /// Utility method assigning a value to a state slot. Returns an arbitrary value 557 /// allowing the method call to be composed in a ShapeEmitter Expression delegate. 558 /// </summary> SetState(int ordinal, T value)559 public bool SetState<T>(int ordinal, T value) 560 { 561 this.State[ordinal] = value; 562 return true; // TRICKY: return true so we can use BitwiseOr expressions to string these guys together. 563 } 564 565 /// <summary> 566 /// REQUIRES:: should be called only by delegate allocating this state. 567 /// Utility method assigning a value to a state slot and return the value, allowing 568 /// the value to be accessed/set in a ShapeEmitter Expression delegate and later 569 /// retrieved. 570 /// </summary> SetStatePassthrough(int ordinal, T value)571 public T SetStatePassthrough<T>(int ordinal, T value) 572 { 573 this.State[ordinal] = value; 574 return value; 575 } 576 577 /// <summary> 578 /// Used to retrieve a property value with exception handling. Normally compiled 579 /// delegates directly call typed methods on the DbDataReader (e.g. GetInt32) 580 /// but when an exception occurs we retry using this method to potentially get 581 /// a more useful error message to the user. 582 /// </summary> GetPropertyValueWithErrorHandling(int ordinal, string propertyName, string typeName)583 public TProperty GetPropertyValueWithErrorHandling<TProperty>(int ordinal, string propertyName, string typeName) 584 { 585 TProperty result = new PropertyErrorHandlingValueReader<TProperty>(propertyName, typeName).GetValue(this.Reader, ordinal); 586 return result; 587 } 588 589 /// <summary> 590 /// Used to retrieve a column value with exception handling. Normally compiled 591 /// delegates directly call typed methods on the DbDataReader (e.g. GetInt32) 592 /// but when an exception occurs we retry using this method to potentially get 593 /// a more useful error message to the user. 594 /// </summary> GetColumnValueWithErrorHandling(int ordinal)595 public TColumn GetColumnValueWithErrorHandling<TColumn>(int ordinal) 596 { 597 TColumn result = new ColumnErrorHandlingValueReader<TColumn>().GetValue(this.Reader, ordinal); 598 return result; 599 } 600 CreateSpatialDataReader()601 private DbSpatialDataReader CreateSpatialDataReader() 602 { 603 return SpatialHelpers.CreateSpatialDataReader(this.Workspace, this.Reader); 604 } 605 private readonly Singleton<DbSpatialDataReader> spatialReader; 606 GetGeographyColumnValue(int ordinal)607 public DbGeography GetGeographyColumnValue(int ordinal) 608 { 609 return this.spatialReader.Value.GetGeography(ordinal); 610 } 611 GetGeometryColumnValue(int ordinal)612 public DbGeometry GetGeometryColumnValue(int ordinal) 613 { 614 return this.spatialReader.Value.GetGeometry(ordinal); 615 } 616 GetSpatialColumnValueWithErrorHandling(int ordinal, PrimitiveTypeKind spatialTypeKind)617 public TColumn GetSpatialColumnValueWithErrorHandling<TColumn>(int ordinal, PrimitiveTypeKind spatialTypeKind) 618 { 619 Debug.Assert(spatialTypeKind == PrimitiveTypeKind.Geography || spatialTypeKind == PrimitiveTypeKind.Geometry, "Spatial primitive type kind is not geography or geometry?"); 620 621 TColumn result; 622 if (spatialTypeKind == PrimitiveTypeKind.Geography) 623 { 624 result = new ColumnErrorHandlingValueReader<TColumn>( 625 (reader, column) => (TColumn)(object)this.spatialReader.Value.GetGeography(column), 626 (reader, column) => this.spatialReader.Value.GetGeography(column) 627 ).GetValue(this.Reader, ordinal); 628 } 629 else 630 { 631 result = new ColumnErrorHandlingValueReader<TColumn>( 632 (reader, column) => (TColumn)(object)this.spatialReader.Value.GetGeometry(column), 633 (reader, column) => this.spatialReader.Value.GetGeometry(column) 634 ).GetValue(this.Reader, ordinal); 635 } 636 return result; 637 } 638 GetSpatialPropertyValueWithErrorHandling(int ordinal, string propertyName, string typeName, PrimitiveTypeKind spatialTypeKind)639 public TProperty GetSpatialPropertyValueWithErrorHandling<TProperty>(int ordinal, string propertyName, string typeName, PrimitiveTypeKind spatialTypeKind) 640 { 641 TProperty result; 642 if (Helper.IsGeographicTypeKind(spatialTypeKind)) 643 { 644 result = new PropertyErrorHandlingValueReader<TProperty>(propertyName, typeName, 645 (reader, column) => (TProperty)(object)this.spatialReader.Value.GetGeography(column), 646 (reader, column) => this.spatialReader.Value.GetGeography(column) 647 ).GetValue(this.Reader, ordinal); 648 } 649 else 650 { 651 Debug.Assert(Helper.IsGeometricTypeKind(spatialTypeKind)); 652 result = new PropertyErrorHandlingValueReader<TProperty>(propertyName, typeName, 653 (reader, column) => (TProperty)(object)this.spatialReader.Value.GetGeometry(column), 654 (reader, column) => this.spatialReader.Value.GetGeometry(column) 655 ).GetValue(this.Reader, ordinal); 656 } 657 658 return result; 659 } 660 661 #endregion 662 663 #region helper methods (used by runtime callable code) 664 CheckClearedEntryOnSpan(object targetValue, IEntityWrapper wrappedSource, EntityKey sourceKey, AssociationEndMember targetMember)665 private void CheckClearedEntryOnSpan(object targetValue, IEntityWrapper wrappedSource, EntityKey sourceKey, AssociationEndMember targetMember) 666 { 667 // If a relationship does not exist on the server but does exist on the client, 668 // we may need to remove it, depending on the current state and the MergeOption 669 if ((null != (object)sourceKey) && (null == targetValue) && 670 (this.MergeOption == MergeOption.PreserveChanges || 671 this.MergeOption == MergeOption.OverwriteChanges)) 672 { 673 // When the spanned value is null, it may be because the spanned association applies to a 674 // subtype of the entity's type, and the entity is not actually an instance of that type. 675 AssociationEndMember sourceEnd = MetadataHelper.GetOtherAssociationEnd(targetMember); 676 EdmType expectedSourceType = ((RefType)sourceEnd.TypeUsage.EdmType).ElementType; 677 TypeUsage entityTypeUsage; 678 if (!this.Context.Perspective.TryGetType(wrappedSource.IdentityType, out entityTypeUsage) || 679 entityTypeUsage.EdmType.EdmEquals(expectedSourceType) || 680 TypeSemantics.IsSubTypeOf(entityTypeUsage.EdmType, expectedSourceType)) 681 { 682 // Otherwise, the source entity is the correct type (exactly or a subtype) for the source 683 // end of the spanned association, so validate that the relationhip that was spanned is 684 // part of the Container owning the EntitySet of the root entity. 685 // This can be done by comparing the EntitySet of the row's entity to the relationships 686 // in the Container and their AssociationSetEnd's type 687 CheckClearedEntryOnSpan(sourceKey, wrappedSource, targetMember); 688 } 689 } 690 } 691 CheckClearedEntryOnSpan(EntityKey sourceKey, IEntityWrapper wrappedSource, AssociationEndMember targetMember)692 private void CheckClearedEntryOnSpan(EntityKey sourceKey, IEntityWrapper wrappedSource, AssociationEndMember targetMember) 693 { 694 Debug.Assert(null != (object)sourceKey); 695 Debug.Assert(wrappedSource != null); 696 Debug.Assert(wrappedSource.Entity != null); 697 Debug.Assert(targetMember != null); 698 Debug.Assert(this.Context != null); 699 700 AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember); 701 702 EntityContainer entityContainer = this.Context.MetadataWorkspace.GetEntityContainer(sourceKey.EntityContainerName, 703 DataSpace.CSpace); 704 EntitySet sourceEntitySet; 705 AssociationSet associationSet = MetadataHelper.GetAssociationsForEntitySetAndAssociationType(entityContainer, sourceKey.EntitySetName, 706 (AssociationType)sourceMember.DeclaringType, sourceMember.Name, out sourceEntitySet); 707 708 if (associationSet != null) 709 { 710 Debug.Assert(associationSet.AssociationSetEnds[sourceMember.Name].EntitySet == sourceEntitySet); 711 ObjectStateManager.RemoveRelationships(Context, MergeOption, associationSet, sourceKey, sourceMember); 712 } 713 } 714 715 /// <summary> 716 /// Wire's one or more full-spanned entities into the state manager; used by 717 /// both full-spanned collections and full-spanned entities. 718 /// </summary> FullSpanAction(IEntityWrapper wrappedSource, IList<T_TargetEntity> spannedEntities, AssociationEndMember targetMember)719 private void FullSpanAction<T_TargetEntity>(IEntityWrapper wrappedSource, IList<T_TargetEntity> spannedEntities, AssociationEndMember targetMember) 720 { 721 Debug.Assert(null != wrappedSource, "wrapped entity is null"); 722 723 if (wrappedSource.Entity != null) 724 { 725 EntityKey sourceKey = wrappedSource.EntityKey; 726 AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember); 727 728 RelatedEnd relatedEnd; 729 if (TryGetRelatedEnd(wrappedSource, (AssociationType)targetMember.DeclaringType, sourceMember.Name, targetMember.Name, out relatedEnd)) 730 { 731 // Add members of the list to the source entity (item in column 0) 732 int count = ObjectStateManager.UpdateRelationships(this.Context, this.MergeOption, (AssociationSet)relatedEnd.RelationshipSet, sourceMember, sourceKey, wrappedSource, targetMember, (List<T_TargetEntity>)spannedEntities, true); 733 734 SetIsLoadedForSpan(relatedEnd, count > 0); 735 } 736 } 737 } 738 739 #region update existing ObjectStateEntry 740 UpdateEntry(IEntityWrapper wrappedEntity, EntityEntry existingEntry)741 private void UpdateEntry<TEntity>(IEntityWrapper wrappedEntity, EntityEntry existingEntry) 742 { 743 Debug.Assert(null != wrappedEntity, "wrapped entity is null"); 744 Debug.Assert(null != wrappedEntity.Entity, "null entity"); 745 Debug.Assert(null != existingEntry, "null ObjectStateEntry"); 746 Debug.Assert(null != existingEntry.Entity, "ObjectStateEntry without Entity"); 747 748 Type clrType = typeof(TEntity); 749 if (clrType != existingEntry.WrappedEntity.IdentityType) 750 { 751 throw EntityUtil.RecyclingEntity(existingEntry.EntityKey, clrType, existingEntry.WrappedEntity.IdentityType); 752 } 753 754 if (EntityState.Added == existingEntry.State) 755 { 756 throw EntityUtil.AddedEntityAlreadyExists(existingEntry.EntityKey); 757 } 758 759 if (MergeOption.AppendOnly != MergeOption) 760 { // existing entity, update CSpace values in place 761 Debug.Assert(EntityState.Added != existingEntry.State, "entry in State=Added"); 762 Debug.Assert(EntityState.Detached != existingEntry.State, "entry in State=Detached"); 763 764 if (MergeOption.OverwriteChanges == MergeOption) 765 { 766 if (EntityState.Deleted == existingEntry.State) 767 { 768 existingEntry.RevertDelete(); 769 } 770 existingEntry.UpdateCurrentValueRecord(wrappedEntity.Entity); 771 Context.ObjectStateManager.ForgetEntryWithConceptualNull(existingEntry, resetAllKeys: true); 772 existingEntry.AcceptChanges(); 773 Context.ObjectStateManager.FixupReferencesByForeignKeys(existingEntry, replaceAddedRefs: true); 774 } 775 else 776 { 777 Debug.Assert(MergeOption.PreserveChanges == MergeOption, "not MergeOption.PreserveChanges"); 778 if (EntityState.Unchanged == existingEntry.State) 779 { 780 // same behavior as MergeOption.OverwriteChanges 781 existingEntry.UpdateCurrentValueRecord(wrappedEntity.Entity); 782 Context.ObjectStateManager.ForgetEntryWithConceptualNull(existingEntry, resetAllKeys: true); 783 existingEntry.AcceptChanges(); 784 Context.ObjectStateManager.FixupReferencesByForeignKeys(existingEntry, replaceAddedRefs: true); 785 } 786 else 787 { 788 if (Context.ContextOptions.UseLegacyPreserveChangesBehavior) 789 { 790 // Do not mark properties as modified if they differ from the entity. 791 existingEntry.UpdateRecordWithoutSetModified(wrappedEntity.Entity, existingEntry.EditableOriginalValues); 792 } 793 else 794 { 795 // Mark properties as modified if they differ from the entity 796 existingEntry.UpdateRecordWithSetModified(wrappedEntity.Entity, existingEntry.EditableOriginalValues); 797 } 798 } 799 } 800 } 801 } 802 803 #endregion 804 805 #endregion 806 807 #region nested types 808 private abstract class ErrorHandlingValueReader<T> 809 { 810 private readonly Func<DbDataReader, int, T> getTypedValue; 811 private readonly Func<DbDataReader, int, object> getUntypedValue; 812 ErrorHandlingValueReader(Func<DbDataReader, int, T> typedValueAccessor, Func<DbDataReader, int, object> untypedValueAccessor)813 protected ErrorHandlingValueReader(Func<DbDataReader, int, T> typedValueAccessor, Func<DbDataReader, int, object> untypedValueAccessor) 814 { 815 this.getTypedValue = typedValueAccessor; 816 this.getUntypedValue = untypedValueAccessor; 817 } 818 ErrorHandlingValueReader()819 protected ErrorHandlingValueReader() 820 : this(GetTypedValueDefault, GetUntypedValueDefault) 821 { 822 } 823 GetTypedValueDefault(DbDataReader reader, int ordinal)824 private static T GetTypedValueDefault(DbDataReader reader, int ordinal) 825 { 826 var underlyingType = Nullable.GetUnderlyingType(typeof(T)); 827 // The value read from the reader is of a primitive type. Such a value cannot be cast to a nullable enum type directly 828 // but first needs to be cast to the non-nullable enum type. Therefore we will call this method for non-nullable 829 // underlying enum type and cast to the target type. 830 if (underlyingType != null && underlyingType.IsEnum) 831 { 832 var type = typeof(ErrorHandlingValueReader<>).MakeGenericType(underlyingType); 833 return (T)type.GetMethod(MethodBase.GetCurrentMethod().Name, BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, new object[] { reader, ordinal }); 834 } 835 836 // use the specific reader.GetXXX method 837 bool isNullable; 838 MethodInfo readerMethod = Translator.GetReaderMethod(typeof(T), out isNullable); 839 T result = (T)readerMethod.Invoke(reader, new object[] { ordinal }); 840 return result; 841 } 842 GetUntypedValueDefault(DbDataReader reader, int ordinal)843 private static object GetUntypedValueDefault(DbDataReader reader, int ordinal) 844 { 845 return reader.GetValue(ordinal); 846 } 847 848 /// <summary> 849 /// Gets value from reader using the same pattern as the materializer delegate. Avoids 850 /// the need to compile multiple delegates for error handling. If there is a failure 851 /// reading a value 852 /// </summary> GetValue(DbDataReader reader, int ordinal)853 internal T GetValue(DbDataReader reader, int ordinal) 854 { 855 T result; 856 if (reader.IsDBNull(ordinal)) 857 { 858 try 859 { 860 result = (T)(object)null; 861 } 862 catch (NullReferenceException) 863 { 864 // NullReferenceException is thrown when casting null to a value type. 865 // We don't use isNullable here because of an issue with GetReaderMethod 866 // 867 throw CreateNullValueException(); 868 } 869 } 870 else 871 { 872 try 873 { 874 result = this.getTypedValue(reader, ordinal); 875 } 876 catch (Exception e) 877 { 878 if (EntityUtil.IsCatchableExceptionType(e)) 879 { 880 // determine if the problem is with the result type 881 // (note that if we throw on this call, it's ok 882 // for it to percolate up -- we only intercept type 883 // and null mismatches) 884 object untypedResult = this.getUntypedValue(reader, ordinal); 885 Type resultType = null == untypedResult ? null : untypedResult.GetType(); 886 if (!typeof(T).IsAssignableFrom(resultType)) 887 { 888 throw CreateWrongTypeException(resultType); 889 } 890 } 891 throw; 892 } 893 } 894 return result; 895 } 896 897 /// <summary> 898 /// Creates the exception thrown when the reader returns a null value 899 /// for a non nullable property/column. 900 /// </summary> CreateNullValueException()901 protected abstract Exception CreateNullValueException(); 902 903 /// <summary> 904 /// Creates the exception thrown when the reader returns a value with 905 /// an incompatible type. 906 /// </summary> CreateWrongTypeException(Type resultType)907 protected abstract Exception CreateWrongTypeException(Type resultType); 908 } 909 910 private class ColumnErrorHandlingValueReader<TColumn> : ErrorHandlingValueReader<TColumn> 911 { ColumnErrorHandlingValueReader()912 internal ColumnErrorHandlingValueReader() 913 { 914 } 915 ColumnErrorHandlingValueReader(Func<DbDataReader, int, TColumn> typedAccessor, Func<DbDataReader, int, object> untypedAccessor)916 internal ColumnErrorHandlingValueReader(Func<DbDataReader, int, TColumn> typedAccessor, Func<DbDataReader, int, object> untypedAccessor) 917 : base(typedAccessor, untypedAccessor) 918 { 919 } 920 CreateNullValueException()921 protected override Exception CreateNullValueException() 922 { 923 return EntityUtil.ValueNullReferenceCast(typeof(TColumn)); 924 } 925 CreateWrongTypeException(Type resultType)926 protected override Exception CreateWrongTypeException(Type resultType) 927 { 928 return EntityUtil.ValueInvalidCast(resultType, typeof(TColumn)); 929 } 930 } 931 932 private class PropertyErrorHandlingValueReader<TProperty> : ErrorHandlingValueReader<TProperty> 933 { 934 private readonly string _propertyName; 935 private readonly string _typeName; 936 PropertyErrorHandlingValueReader(string propertyName, string typeName)937 internal PropertyErrorHandlingValueReader(string propertyName, string typeName) 938 : base() 939 { 940 _propertyName = propertyName; 941 _typeName = typeName; 942 } 943 PropertyErrorHandlingValueReader(string propertyName, string typeName, Func<DbDataReader, int, TProperty> typedAccessor, Func<DbDataReader, int, object> untypedAccessor)944 internal PropertyErrorHandlingValueReader(string propertyName, string typeName, Func<DbDataReader, int, TProperty> typedAccessor, Func<DbDataReader, int, object> untypedAccessor) 945 : base(typedAccessor, untypedAccessor) 946 { 947 _propertyName = propertyName; 948 _typeName = typeName; 949 } 950 CreateNullValueException()951 protected override Exception CreateNullValueException() 952 { 953 return EntityUtil.Constraint( 954 System.Data.Entity.Strings.Materializer_SetInvalidValue( 955 (Nullable.GetUnderlyingType(typeof(TProperty)) ?? typeof(TProperty)).Name, 956 _typeName, _propertyName, "null")); 957 } 958 CreateWrongTypeException(Type resultType)959 protected override Exception CreateWrongTypeException(Type resultType) 960 { 961 return EntityUtil.InvalidOperation( 962 System.Data.Entity.Strings.Materializer_SetInvalidValue( 963 (Nullable.GetUnderlyingType(typeof(TProperty)) ?? typeof(TProperty)).Name, 964 _typeName, _propertyName, resultType.Name)); 965 } 966 } 967 #endregion 968 969 #region OnMaterialized helpers 970 RaiseMaterializedEvents()971 public void RaiseMaterializedEvents() 972 { 973 if (_materializedEntities != null) 974 { 975 foreach (var wrappedEntity in _materializedEntities) 976 { 977 Context.OnObjectMaterialized(wrappedEntity.Entity); 978 } 979 _materializedEntities.Clear(); 980 } 981 } 982 InitializeForOnMaterialize()983 public void InitializeForOnMaterialize() 984 { 985 if (Context.OnMaterializedHasHandlers) 986 { 987 if (_materializedEntities == null) 988 { 989 _materializedEntities = new List<IEntityWrapper>(); 990 } 991 } 992 else if (_materializedEntities != null) 993 { 994 _materializedEntities = null; 995 } 996 } 997 RegisterMaterializedEntityForEvent(IEntityWrapper wrappedEntity)998 protected void RegisterMaterializedEntityForEvent(IEntityWrapper wrappedEntity) 999 { 1000 if (_materializedEntities != null) 1001 { 1002 _materializedEntities.Add(wrappedEntity); 1003 } 1004 } 1005 1006 #endregion 1007 } 1008 1009 /// <summary> 1010 /// Typed Shaper. Includes logic to enumerate results and wraps the _rootCoordinator, 1011 /// which includes materializer delegates for the root query collection. 1012 /// </summary> 1013 internal sealed class Shaper<T> : Shaper 1014 { 1015 #region private state 1016 1017 /// <summary> 1018 /// Shapers and Coordinators work together in harmony to materialize the data 1019 /// from the store; the shaper contains the state, the coordinator contains the 1020 /// code. 1021 /// </summary> 1022 internal readonly Coordinator<T> RootCoordinator; 1023 1024 /// <summary> 1025 /// Which type of query is this, object layer (true) or value layer (false) 1026 /// </summary> 1027 private readonly bool IsObjectQuery; 1028 1029 /// <summary> 1030 /// Keeps track of whether we've completed processing or not. 1031 /// </summary> 1032 private bool _isActive; 1033 1034 /// <summary> 1035 /// The enumerator we're using to read data; really only populated for value 1036 /// layer queries. 1037 /// </summary> 1038 private IEnumerator<T> _rootEnumerator; 1039 1040 /// <summary> 1041 /// Whether the current value of _rootEnumerator has been returned by a bridge 1042 /// data reader. 1043 /// </summary> 1044 private bool _dataWaiting; 1045 1046 /// <summary> 1047 /// Is the reader owned by the EF or was it supplied by the user? 1048 /// </summary> 1049 private bool _readerOwned; 1050 1051 #endregion 1052 1053 #region constructor 1054 Shaper(DbDataReader reader, ObjectContext context, MetadataWorkspace workspace, MergeOption mergeOption, int stateCount, CoordinatorFactory<T> rootCoordinatorFactory, Action checkPermissions, bool readerOwned)1055 internal Shaper(DbDataReader reader, ObjectContext context, MetadataWorkspace workspace, MergeOption mergeOption, int stateCount, CoordinatorFactory<T> rootCoordinatorFactory, Action checkPermissions, bool readerOwned) 1056 : base(reader, context, workspace, mergeOption, stateCount) 1057 { 1058 RootCoordinator = new Coordinator<T>(rootCoordinatorFactory, /*parent*/ null, /*next*/ null); 1059 if (null != checkPermissions) 1060 { 1061 checkPermissions(); 1062 } 1063 IsObjectQuery = !(typeof(T) == typeof(RecordState)); 1064 _isActive = true; 1065 RootCoordinator.Initialize(this); 1066 _readerOwned = readerOwned; 1067 } 1068 1069 #endregion 1070 1071 #region "public" surface area 1072 1073 /// <summary> 1074 /// Events raised when the shaper has finished enumerating results. Useful for callback 1075 /// to set parameter values. 1076 /// </summary> 1077 internal event EventHandler OnDone; 1078 1079 /// <summary> 1080 /// Used to handle the read-ahead requirements of value-layer queries. This 1081 /// field indicates the status of the current value of the _rootEnumerator; when 1082 /// a bridge data reader "accepts responsibility" for the current value, it sets 1083 /// this to false. 1084 /// </summary> 1085 internal bool DataWaiting 1086 { 1087 get { return _dataWaiting; } 1088 set { _dataWaiting = value; } 1089 } 1090 1091 /// <summary> 1092 /// The enumerator that the value-layer bridge will use to read data; all nested 1093 /// data readers need to use the same enumerator, so we put it on the Shaper, since 1094 /// that is something that all the nested data readers (and data records) have access 1095 /// to -- it prevents us from having to pass two objects around. 1096 /// </summary> 1097 internal IEnumerator<T> RootEnumerator 1098 { 1099 get 1100 { 1101 if (_rootEnumerator == null) 1102 { 1103 InitializeRecordStates(RootCoordinator.CoordinatorFactory); 1104 _rootEnumerator = GetEnumerator(); 1105 } 1106 return _rootEnumerator; 1107 } 1108 } 1109 1110 /// <summary> 1111 /// Initialize the RecordStateFactory objects in their StateSlots. 1112 /// </summary> InitializeRecordStates(CoordinatorFactory coordinatorFactory)1113 private void InitializeRecordStates(CoordinatorFactory coordinatorFactory) 1114 { 1115 foreach (RecordStateFactory recordStateFactory in coordinatorFactory.RecordStateFactories) 1116 { 1117 State[recordStateFactory.StateSlotNumber] = recordStateFactory.Create(coordinatorFactory); 1118 } 1119 1120 foreach (CoordinatorFactory nestedCoordinatorFactory in coordinatorFactory.NestedCoordinators) 1121 { 1122 InitializeRecordStates(nestedCoordinatorFactory); 1123 } 1124 } 1125 GetEnumerator()1126 public IEnumerator<T> GetEnumerator() 1127 { 1128 // we can use a simple enumerator if there are no nested results, no keys and no "has data" 1129 // discriminator 1130 if (RootCoordinator.CoordinatorFactory.IsSimple) 1131 { 1132 return new SimpleEnumerator(this); 1133 } 1134 else 1135 { 1136 RowNestedResultEnumerator rowEnumerator = new Shaper<T>.RowNestedResultEnumerator(this); 1137 1138 if (this.IsObjectQuery) 1139 { 1140 return new ObjectQueryNestedEnumerator(rowEnumerator); 1141 } 1142 else 1143 { 1144 return (IEnumerator<T>)(object)(new RecordStateEnumerator(rowEnumerator)); 1145 } 1146 } 1147 } 1148 1149 #endregion 1150 1151 #region enumerator helpers 1152 1153 /// <summary> 1154 /// Called when enumeration of results has completed. 1155 /// </summary> Finally()1156 private void Finally() 1157 { 1158 if (_isActive) 1159 { 1160 _isActive = false; 1161 1162 if (_readerOwned) 1163 { 1164 // I'd prefer not to special case this, but value-layer behavior is that you 1165 // must explicitly close the data reader; if we automatically dispose of the 1166 // reader here, we won't have that behavior. 1167 if (IsObjectQuery) 1168 { 1169 this.Reader.Dispose(); 1170 } 1171 1172 // This case includes when the ObjectResult is disposed before it 1173 // created an ObjectQueryEnumeration; at this time, the connection can be released 1174 if (this.Context != null) 1175 { 1176 this.Context.ReleaseConnection(); 1177 } 1178 } 1179 1180 if (null != this.OnDone) 1181 { 1182 this.OnDone(this, new EventArgs()); 1183 } 1184 } 1185 } 1186 1187 /// <summary> 1188 /// Reads the next row from the store. If there is a failure, throws an exception message 1189 /// in some scenarios (note that we respond to failure rather than anticipate failure, 1190 /// avoiding repeated checks in the inner materialization loop) 1191 /// </summary> StoreRead()1192 private bool StoreRead() 1193 { 1194 bool readSucceeded; 1195 try 1196 { 1197 readSucceeded = this.Reader.Read(); 1198 } 1199 catch (Exception e) 1200 { 1201 // check if the reader is closed; if so, throw friendlier exception 1202 if (this.Reader.IsClosed) 1203 { 1204 const string operation = "Read"; 1205 throw EntityUtil.DataReaderClosed(operation); 1206 } 1207 1208 // wrap exception if necessary 1209 if (EntityUtil.IsCatchableEntityExceptionType(e)) 1210 { 1211 throw EntityUtil.CommandExecution(System.Data.Entity.Strings.EntityClient_StoreReaderFailed, e); 1212 } 1213 throw; 1214 } 1215 return readSucceeded; 1216 } 1217 1218 /// <summary> 1219 /// Notify ObjectContext that we are about to start materializing an element 1220 /// </summary> StartMaterializingElement()1221 private void StartMaterializingElement() 1222 { 1223 if (Context != null) 1224 { 1225 Context.InMaterialization = true; 1226 InitializeForOnMaterialize(); 1227 } 1228 } 1229 1230 /// <summary> 1231 /// Notify ObjectContext that we are finished materializing the element 1232 /// </summary> StopMaterializingElement()1233 private void StopMaterializingElement() 1234 { 1235 if (Context != null) 1236 { 1237 Context.InMaterialization = false; 1238 RaiseMaterializedEvents(); 1239 } 1240 } 1241 1242 #endregion 1243 1244 #region simple enumerator 1245 1246 /// <summary> 1247 /// Optimized enumerator for queries not including nested results. 1248 /// </summary> 1249 private class SimpleEnumerator : IEnumerator<T> 1250 { 1251 private readonly Shaper<T> _shaper; 1252 SimpleEnumerator(Shaper<T> shaper)1253 internal SimpleEnumerator(Shaper<T> shaper) 1254 { 1255 _shaper = shaper; 1256 } 1257 1258 public T Current 1259 { 1260 get { return _shaper.RootCoordinator.Current; } 1261 } 1262 1263 object System.Collections.IEnumerator.Current 1264 { 1265 get { return _shaper.RootCoordinator.Current; } 1266 } 1267 Dispose()1268 public void Dispose() 1269 { 1270 // Technically, calling GC.SuppressFinalize is not required because the class does not 1271 // have a finalizer, but it does no harm, protects against the case where a finalizer is added 1272 // in the future, and prevents an FxCop warning. 1273 GC.SuppressFinalize(this); 1274 // For backwards compatibility, we set the current value to the 1275 // default value, so you can still call Current. 1276 _shaper.RootCoordinator.SetCurrentToDefault(); 1277 _shaper.Finally(); 1278 } 1279 MoveNext()1280 public bool MoveNext() 1281 { 1282 if (!_shaper._isActive) 1283 { 1284 return false; 1285 } 1286 if (_shaper.StoreRead()) 1287 { 1288 try 1289 { 1290 _shaper.StartMaterializingElement(); 1291 _shaper.RootCoordinator.ReadNextElement(_shaper); 1292 } 1293 finally 1294 { 1295 _shaper.StopMaterializingElement(); 1296 } 1297 return true; 1298 } 1299 this.Dispose(); 1300 return false; 1301 } 1302 Reset()1303 public void Reset() 1304 { 1305 throw EntityUtil.NotSupported(); 1306 } 1307 } 1308 1309 #endregion 1310 1311 #region nested enumerator 1312 1313 /// <summary> 1314 /// Enumerates (for each row in the input) an array of all coordinators producing new elements. The array 1315 /// contains a position for each 'depth' in the result. A null value in any position indicates that no new 1316 /// results were produced for the given row at the given depth. It is possible for a row to contain no 1317 /// results for any row. 1318 /// </summary> 1319 private class RowNestedResultEnumerator : IEnumerator<Coordinator[]> 1320 { 1321 private readonly Shaper<T> _shaper; 1322 private readonly Coordinator[] _current; 1323 RowNestedResultEnumerator(Shaper<T> shaper)1324 internal RowNestedResultEnumerator(Shaper<T> shaper) 1325 { 1326 _shaper = shaper; 1327 _current = new Coordinator[_shaper.RootCoordinator.MaxDistanceToLeaf() + 1]; 1328 } 1329 1330 public Coordinator[] Current 1331 { 1332 get { return _current; } 1333 } 1334 Dispose()1335 public void Dispose() 1336 { 1337 // Technically, calling GC.SuppressFinalize is not required because the class does not 1338 // have a finalizer, but it does no harm, protects against the case where a finalizer is added 1339 // in the future, and prevents an FxCop warning. 1340 GC.SuppressFinalize(this); 1341 _shaper.Finally(); 1342 } 1343 1344 object System.Collections.IEnumerator.Current 1345 { 1346 get { return _current; } 1347 } 1348 MoveNext()1349 public bool MoveNext() 1350 { 1351 Coordinator currentCoordinator = _shaper.RootCoordinator; 1352 1353 try 1354 { 1355 _shaper.StartMaterializingElement(); 1356 1357 if (!_shaper.StoreRead()) 1358 { 1359 // Reset all collections 1360 this.RootCoordinator.ResetCollection(_shaper); 1361 return false; 1362 } 1363 1364 int depth = 0; 1365 bool haveInitializedChildren = false; 1366 for (; depth < _current.Length; depth++) 1367 { 1368 // find a coordinator at this depth that currently has data (if any) 1369 while (currentCoordinator != null && !currentCoordinator.CoordinatorFactory.HasData(_shaper)) 1370 { 1371 currentCoordinator = currentCoordinator.Next; 1372 } 1373 if (null == currentCoordinator) 1374 { 1375 break; 1376 } 1377 1378 // check if this row contains a new element for this coordinator 1379 if (currentCoordinator.HasNextElement(_shaper)) 1380 { 1381 // if we have children and haven't initialized them yet, do so now 1382 if (!haveInitializedChildren && null != currentCoordinator.Child) 1383 { 1384 currentCoordinator.Child.ResetCollection(_shaper); 1385 } 1386 haveInitializedChildren = true; 1387 1388 // read the next element 1389 currentCoordinator.ReadNextElement(_shaper); 1390 1391 // place the coordinator in the result array to indicate there is a new 1392 // element at this depth 1393 _current[depth] = currentCoordinator; 1394 } 1395 else 1396 { 1397 // clear out the coordinator in result array to indicate there is no new 1398 // element at this depth 1399 _current[depth] = null; 1400 } 1401 1402 // move to child (in the next iteration we deal with depth + 1 1403 currentCoordinator = currentCoordinator.Child; 1404 } 1405 1406 // clear out all positions below the depth we reached before we ran out of data 1407 for (; depth < _current.Length; depth++) 1408 { 1409 _current[depth] = null; 1410 } 1411 } 1412 finally 1413 { 1414 _shaper.StopMaterializingElement(); 1415 } 1416 1417 return true; 1418 } 1419 Reset()1420 public void Reset() 1421 { 1422 throw EntityUtil.NotSupported(); 1423 } 1424 1425 internal Coordinator<T> RootCoordinator 1426 { 1427 get { return _shaper.RootCoordinator; } 1428 } 1429 } 1430 1431 /// <summary> 1432 /// Wraps RowNestedResultEnumerator and yields results appropriate to an ObjectQuery instance. In particular, 1433 /// root level elements (T) are returned only after aggregating all child elements. 1434 /// </summary> 1435 private class ObjectQueryNestedEnumerator : IEnumerator<T> 1436 { 1437 private readonly RowNestedResultEnumerator _rowEnumerator; 1438 private T _previousElement; 1439 private State _state; 1440 ObjectQueryNestedEnumerator(RowNestedResultEnumerator rowEnumerator)1441 internal ObjectQueryNestedEnumerator(RowNestedResultEnumerator rowEnumerator) 1442 { 1443 _rowEnumerator = rowEnumerator; 1444 _previousElement = default(T); 1445 _state = State.Start; 1446 } 1447 1448 public T Current { get { return _previousElement; } } 1449 Dispose()1450 public void Dispose() 1451 { 1452 // Technically, calling GC.SuppressFinalize is not required because the class does not 1453 // have a finalizer, but it does no harm, protects against the case where a finalizer is added 1454 // in the future, and prevents an FxCop warning. 1455 GC.SuppressFinalize(this); 1456 _rowEnumerator.Dispose(); 1457 } 1458 1459 object System.Collections.IEnumerator.Current { get { return this.Current; } } 1460 MoveNext()1461 public bool MoveNext() 1462 { 1463 // See the documentation for enum State to understand the behaviors and requirements 1464 // for each state. 1465 switch (_state) 1466 { 1467 case State.Start: 1468 { 1469 if (TryReadToNextElement()) 1470 { 1471 // if there's an element in the reader... 1472 ReadElement(); 1473 } 1474 else 1475 { 1476 // no data at all... 1477 _state = State.NoRows; 1478 } 1479 }; 1480 break; 1481 case State.Reading: 1482 { 1483 ReadElement(); 1484 }; 1485 break; 1486 case State.NoRowsLastElementPending: 1487 { 1488 // nothing to do but move to the next state... 1489 _state = State.NoRows; 1490 }; 1491 break; 1492 } 1493 1494 bool result; 1495 if (_state == State.NoRows) 1496 { 1497 _previousElement = default(T); 1498 result = false; 1499 } 1500 else 1501 { 1502 result = true; 1503 } 1504 1505 return result; 1506 } 1507 1508 /// <summary> 1509 /// Requires: the row is currently positioned at the start of an element. 1510 /// 1511 /// Reads all rows in the element and sets up state for the next element (if any). 1512 /// </summary> ReadElement()1513 private void ReadElement() 1514 { 1515 // remember the element we're currently reading 1516 _previousElement = _rowEnumerator.RootCoordinator.Current; 1517 1518 // now we need to read to the next element (or the end of the 1519 // reader) so that we can return the first element 1520 if (TryReadToNextElement()) 1521 { 1522 // we're positioned at the start of the next element (which 1523 // corresponds to the 'reading' state) 1524 _state = State.Reading; 1525 } 1526 else 1527 { 1528 // we're positioned at the end of the reader 1529 _state = State.NoRowsLastElementPending; 1530 } 1531 } 1532 1533 /// <summary> 1534 /// Reads rows until the start of a new element is found. If no element 1535 /// is found before all rows are consumed, returns false. 1536 /// </summary> TryReadToNextElement()1537 private bool TryReadToNextElement() 1538 { 1539 while (_rowEnumerator.MoveNext()) 1540 { 1541 // if we hit a new element, return true 1542 if (_rowEnumerator.Current[0] != null) 1543 { 1544 return true; 1545 } 1546 } 1547 return false; 1548 } 1549 Reset()1550 public void Reset() 1551 { 1552 _rowEnumerator.Reset(); 1553 } 1554 1555 /// <summary> 1556 /// Describes the state of this enumerator with respect to the _rowEnumerator 1557 /// it wraps. 1558 /// </summary> 1559 private enum State 1560 { 1561 /// <summary> 1562 /// No rows have been read yet 1563 /// </summary> 1564 Start, 1565 1566 /// <summary> 1567 /// Positioned at the start of a new root element. The previous element must 1568 /// be stored in _previousElement. We read ahead in this manner so that 1569 /// the previous element is fully populated (all of its children loaded) 1570 /// before returning. 1571 /// </summary> 1572 Reading, 1573 1574 /// <summary> 1575 /// Positioned past the end of the rows. The last element in the enumeration 1576 /// has not yet been returned to the user however, and is stored in _previousElement. 1577 /// </summary> 1578 NoRowsLastElementPending, 1579 1580 /// <summary> 1581 /// Positioned past the end of the rows. The last element has been returned to 1582 /// the user. 1583 /// </summary> 1584 NoRows, 1585 } 1586 } 1587 1588 /// <summary> 1589 /// Wraps RowNestedResultEnumerator and yields results appropriate to an EntityReader instance. In particular, 1590 /// yields RecordState whenever a new element becomes available at any depth in the result hierarchy. 1591 /// </summary> 1592 private class RecordStateEnumerator : IEnumerator<RecordState> 1593 { 1594 private readonly RowNestedResultEnumerator _rowEnumerator; 1595 private RecordState _current; 1596 1597 /// <summary> 1598 /// Gets depth of coordinator we're currently consuming. If _depth == -1, it means we haven't started 1599 /// to consume the next row yet. 1600 /// </summary> 1601 private int _depth; 1602 private bool _readerConsumed; 1603 RecordStateEnumerator(RowNestedResultEnumerator rowEnumerator)1604 internal RecordStateEnumerator(RowNestedResultEnumerator rowEnumerator) 1605 { 1606 _rowEnumerator = rowEnumerator; 1607 _current = null; 1608 _depth = -1; 1609 _readerConsumed = false; 1610 } 1611 1612 public RecordState Current 1613 { 1614 get { return _current; } 1615 } 1616 Dispose()1617 public void Dispose() 1618 { 1619 // Technically, calling GC.SuppressFinalize is not required because the class does not 1620 // have a finalizer, but it does no harm, protects against the case where a finalizer is added 1621 // in the future, and prevents an FxCop warning. 1622 GC.SuppressFinalize(this); 1623 _rowEnumerator.Dispose(); 1624 } 1625 1626 object System.Collections.IEnumerator.Current 1627 { 1628 get { return _current; } 1629 } 1630 MoveNext()1631 public bool MoveNext() 1632 { 1633 if (!_readerConsumed) 1634 { 1635 while (true) 1636 { 1637 // keep on cycling until we find a result 1638 if (-1 == _depth || _rowEnumerator.Current.Length == _depth) 1639 { 1640 // time to move to the next row... 1641 if (!_rowEnumerator.MoveNext()) 1642 { 1643 // no more rows... 1644 _current = null; 1645 _readerConsumed = true; 1646 break; 1647 } 1648 1649 _depth = 0; 1650 } 1651 1652 // check for results at the current depth 1653 Coordinator currentCoordinator = _rowEnumerator.Current[_depth]; 1654 if (null != currentCoordinator) 1655 { 1656 _current = ((Coordinator<RecordState>)currentCoordinator).Current; 1657 _depth++; 1658 break; 1659 } 1660 1661 _depth++; 1662 } 1663 } 1664 1665 return !_readerConsumed; 1666 } 1667 Reset()1668 public void Reset() 1669 { 1670 _rowEnumerator.Reset(); 1671 } 1672 } 1673 1674 #endregion 1675 1676 } 1677 } 1678