Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / ndp / fx / src / DataEntity / System / Data / Common / internal / materialization / shaper.cs / 3 / shaper.cs
//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// [....]
// [....]
//-----------------------------------------------------------------------------
using System.Collections.Generic;
using System.Data.Common.Utils;
using System.Linq;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Diagnostics;
using System.Reflection;
namespace System.Data.Common.Internal.Materialization
{
///
/// Shapes store reader values into EntityClient/ObjectQuery results. Also maintains
/// state used by materializer delegates.
///
internal abstract class Shaper
{
#region constructor
internal Shaper(DbDataReader reader, ObjectContext context, MetadataWorkspace workspace, MergeOption mergeOption, int stateCount)
{
Debug.Assert(context == null || workspace == context.MetadataWorkspace, "workspace must match context's workspace");
this.Reader = reader;
this.MergeOption = mergeOption;
this.State = new object[stateCount];
this.Context = context;
this.Workspace = workspace;
}
#endregion
#region runtime callable/accessible code
// Code in this section is called from the delegates produced by the Translator. It
// may not show up if you search using Find All References...use Find in Files instead.
//
// Many items on this class are public, simply to make the job of producing the
// expressions that use them simpler. If you have a hankering to make them private,
// you will need to modify the code in the Translator that does the GetMethod/GetField
// to use BindingFlags.NonPublic | BindingFlags.Instance as well.
//
// Debug.Asserts that fire from the code in this region will probably create a
// SecurityException in the Coordinator's Read method since those are restricted when
// running the Shaper.
///
/// The store data reader we're pulling data from
///
public readonly DbDataReader Reader;
///
/// The state slots we use in the coordinator expression.
///
public readonly object[] State;
///
/// The context the shaper is performing for.
///
public readonly ObjectContext Context;
///
/// The workspace we are performing for; yes we could get it from the context, but
/// it's much easier to just have it handy.
///
public readonly MetadataWorkspace Workspace;
///
/// The merge option this shaper is performing under/for.
///
public readonly MergeOption MergeOption;
///
/// Utility method used to evaluate a multi-discriminator column map. Takes
/// discriminator values and determines the appropriate entity type, then looks up
/// the appropriate handler and invokes it.
///
public TElement Discriminate(object[] discriminatorValues, Func discriminate, KeyValuePair>[] elementDelegates)
{
EntityType entityType = discriminate(discriminatorValues);
Func elementDelegate = null;
foreach (KeyValuePair> typeDelegatePair in elementDelegates)
{
if (typeDelegatePair.Key == entityType)
{
elementDelegate = typeDelegatePair.Value;
}
}
return elementDelegate(this);
}
///
/// REQUIRES:: entity is not null and MergeOption is OverwriteChanges or PreserveChanges
/// Handles state management for an entity returned by a query. Where an existing entry
/// exists, updates that entry and returns the existing entity. Otherwise, the entity
/// passed in is returned.
///
public TEntity HandleEntity(TEntity entity, EntityKey entityKey, EntitySet entitySet)
{
Debug.Assert(MergeOption.NoTracking != this.MergeOption, "no need to HandleEntity if there's no tracking");
Debug.Assert(MergeOption.AppendOnly != this.MergeOption, "use HandleEntityAppendOnly instead...");
Debug.Assert(null != entity, "if HandleEntity is called, there must be an entity");
TEntity result = entity;
// no entity set, so no tracking is required for this entity
if (null != (object)entityKey)
{
Debug.Assert(null != entitySet, "if there is an entity key, there must also be an entity set");
// check for an existing entity with the same key
ObjectStateEntry existingEntry = this.Context.ObjectStateManager.FindObjectStateEntry(entityKey);
if (null != existingEntry && !existingEntry.IsKeyEntry)
{
Debug.Assert(existingEntry.EntityKey.Equals(entityKey), "Found ObjectStateEntry with wrong EntityKey");
UpdateEntry(entity, existingEntry);
result = (TEntity)existingEntry.Entity;
}
else
{
// if the entity isn't tracked yet, attach it
if (null == existingEntry)
{
Context.ObjectStateManager.AddEntry(entity, entityKey, entitySet, "HandleEntity", false);
}
else
{
Context.ObjectStateManager.PromoteKeyEntry(existingEntry, entity, (IExtendedDataRecord)null, false, /*setIsLoaded*/ true, /*keyEntryInitialized*/ false, "HandleEntity");
}
}
}
return result;
}
///
/// REQUIRES:: entity exists; MergeOption is AppendOnly
/// Handles state management for an entity with the given key. When the entity already exists
/// in the state manager, it is returned directly. Otherwise, the entityDelegate is invoked and
/// the resulting entity is returned.
///
public TEntity HandleEntityAppendOnly(Func constructEntityDelegate, EntityKey entityKey, EntitySet entitySet)
{
Debug.Assert(this.MergeOption == MergeOption.AppendOnly, "only use HandleEntityAppendOnly when MergeOption is AppendOnly");
Debug.Assert(null != constructEntityDelegate, "must provide delegate to construct the entity");
TEntity result;
if (null == (object)entityKey)
{
// no entity set, so no tracking is required for this entity, just
// call the delegate to "materialize" it.
result = constructEntityDelegate(this);
}
else
{
Debug.Assert(null != entitySet, "if there is an entity key, there must also be an entity set");
// check for an existing entity with the same key
ObjectStateEntry existingEntry = this.Context.ObjectStateManager.FindObjectStateEntry(entityKey);
if (null != existingEntry && !existingEntry.IsKeyEntry)
{
Debug.Assert(existingEntry.EntityKey.Equals(entityKey), "Found ObjectStateEntry with wrong EntityKey");
if (typeof(TEntity) != existingEntry.Entity.GetType())
{
throw EntityUtil.RecyclingEntity(existingEntry.EntityKey, typeof(TEntity), existingEntry.Entity.GetType());
}
if (EntityState.Added == existingEntry.State)
{
throw EntityUtil.AddedEntityAlreadyExists(existingEntry.EntityKey);
}
result = (TEntity)existingEntry.Entity;
}
else
{
// We don't already have the entity, so construct it and if it isn't
// tracked yet, attach it
result = constructEntityDelegate(this);
if (null == existingEntry)
{
Context.ObjectStateManager.AddEntry(result, entityKey, entitySet, "HandleEntity", false);
}
else
{
Context.ObjectStateManager.PromoteKeyEntry(existingEntry, result, (IExtendedDataRecord)null, false, /*setIsLoaded*/ true, /*keyEntryInitialized*/ false, "HandleEntity");
}
}
}
return result;
}
///
/// Call to ensure a collection of full-spanned elements are added
/// into the state manager properly. We registers an action to be called
/// when the collection is closed that pulls the collection of full spanned
/// objects into the state manager.
///
public T_SourceEntity HandleFullSpanCollection(T_SourceEntity entity, Coordinator coordinator, AssociationEndMember targetMember)
{
IEntityWithRelationships sourceEntity = entity as IEntityWithRelationships;
if (null != sourceEntity)
{
coordinator.RegisterCloseHandler((state, spannedEntities) => FullSpanAction(sourceEntity, spannedEntities, targetMember));
}
return entity;
}
///
/// Call to ensure a single full-spanned element is added into
/// the state manager properly.
///
public T_SourceEntity HandleFullSpanElement(T_SourceEntity entity, T_TargetEntity spannedEntity, AssociationEndMember targetMember)
{
IEntityWithRelationships sourceEntity = entity as IEntityWithRelationships;
if (null != sourceEntity)
{
List spannedEntities = null;
if (spannedEntity != null)
{
// There was a single entity in the column
// Create a list so we can perform the same logic as a collection of entities
spannedEntities = new List(1);
spannedEntities.Add(spannedEntity);
}
else
{
EntityKey sourceKey = ObjectStateManager.FindKeyOnEntityWithRelationships(sourceEntity);
CheckClearedEntryOnSpan(spannedEntity, sourceEntity, sourceKey, targetMember);
}
FullSpanAction(sourceEntity, spannedEntities, targetMember);
}
return entity;
}
///
/// Call to ensure a target entities key is added into the state manager
/// properly
///
public T_SourceEntity HandleRelationshipSpan(T_SourceEntity entity, EntityKey targetKey, AssociationEndMember targetMember)
{
IEntityWithRelationships sourceEntity = entity as IEntityWithRelationships;
if (null == sourceEntity)
{
return entity;
}
Debug.Assert(targetMember != null);
Debug.Assert(targetMember.RelationshipMultiplicity == RelationshipMultiplicity.One || targetMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne);
EntityUtil.CheckKeyForRelationship(sourceEntity, MergeOption);
EntityKey sourceKey = ObjectStateManager.FindKeyOnEntityWithRelationships(sourceEntity);
AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember);
CheckClearedEntryOnSpan(targetKey, sourceEntity, sourceKey, targetMember);
if (null != (object)targetKey)
{
EntitySet targetEntitySet;
EntityContainer entityContainer = this.Context.MetadataWorkspace.GetEntityContainer(
targetKey.EntityContainerName, DataSpace.CSpace);
// find the correct AssociationSet
AssociationSet associationSet = MetadataHelper.GetAssociationsForEntitySetAndAssociationType(entityContainer,
targetKey.EntitySetName, (AssociationType)(targetMember.DeclaringType), targetMember.Name, out targetEntitySet);
Debug.Assert(associationSet != null, "associationSet should not be null");
ObjectStateManager manager = Context.ObjectStateManager;
EntityState newEntryState;
// If there is an existing relationship entry, update it based on its current state and the MergeOption, otherwise add a new one
if (!ObjectStateManager.TryUpdateExistingRelationships(this.Context, this.MergeOption, associationSet, sourceMember, sourceKey, sourceEntity, targetMember, targetKey, /*setIsLoaded*/ true, out newEntryState))
{
// Try to find a state entry for the target key
ObjectStateEntry targetEntry = null;
if (!manager.TryGetObjectStateEntry(targetKey, out targetEntry))
{
// no entry exists for the target key
// create a key entry for the target
targetEntry = manager.AddKeyEntry(targetKey, targetEntitySet);
}
// SQLBU 557105. For 1-1 relationships we have to take care of the relationships of targetEntity
bool needNewRelationship = true;
switch (sourceMember.RelationshipMultiplicity)
{
case RelationshipMultiplicity.ZeroOrOne:
case RelationshipMultiplicity.One:
// devnote: targetEntry can be a key entry (targetEntry.Entity == null),
// but it that case this parameter won't be used in TryUpdateExistingRelationships
needNewRelationship = !ObjectStateManager.TryUpdateExistingRelationships(this.Context,
this.MergeOption,
associationSet,
targetMember,
targetKey,
targetEntry.Entity as IEntityWithRelationships,
sourceMember,
sourceKey,
/*setIsLoaded*/ true,
out newEntryState);
// It is possible that as part of removing existing relationships, the key entry was deleted
// If that is the case, recreate the key entry
if (targetEntry.State == EntityState.Detached)
{
targetEntry = manager.AddKeyEntry(targetKey, targetEntitySet);
}
break;
case RelationshipMultiplicity.Many:
// we always need a new relationship with Many-To-Many, if there was no exact match between these two entities, so do nothing
break;
default:
Debug.Assert(false, "Unexpected sourceMember.RelationshipMultiplicity");
break;
}
if (needNewRelationship)
{
// If the target entry is a key entry, then we need to add a relation
// between the source and target entries
// If we are in a state where we just need to add a new Deleted relation, we
// only need to do that and not touch the related ends
// If the target entry is a full entity entry, then we need to add
// the target entity to the source collection or reference
if (targetEntry.IsKeyEntry || newEntryState == EntityState.Deleted)
{
// Add a relationship between the source entity and the target key entry
RelationshipWrapper wrapper = new RelationshipWrapper(associationSet, sourceMember.Name, sourceKey, targetMember.Name, targetKey);
manager.AddNewRelation(wrapper, newEntryState);
}
else
{
Debug.Assert(!targetEntry.IsRelationship, "how IsRelationship?");
if (targetEntry.State != EntityState.Deleted)
{
// The entry contains an entity, do collection or reference fixup
// This will also try to create a new relationship entry or will revert the delete on an existing deleted relationship
ObjectStateManager.AddEntityToCollectionOrReference(
this.MergeOption, sourceEntity, sourceMember,
targetEntry.Entity as IEntityWithRelationships,
targetMember,
/*setIsLoaded*/ true,
/*relationshipAlreadyExists*/ false,
/* inKeyEntryPromotion */ false);
}
else
{
// if the target entry is deleted, then the materializer needs to create a deleted relationship
// between the entity and the target entry so that if the entity is deleted, the update
// pipeline can find the relationship (even though it is deleted)
RelationshipWrapper wrapper = new RelationshipWrapper(associationSet, sourceMember.Name, sourceKey, targetMember.Name, targetKey);
manager.AddNewRelation(wrapper, EntityState.Deleted);
}
}
}
}
}
else
{
IRelatedEnd relatedEnd;
if(sourceEntity.RelationshipManager.TryGetRelatedEnd(sourceMember.DeclaringType.FullName, targetMember.Name, out relatedEnd))
{
SetIsLoadedForSpan((RelatedEnd)relatedEnd, false);
}
}
// else there is nothing else for us to do, the relationship has been handled already
return entity;
}
///
/// Sets the IsLoaded flag to "true"
/// There are also rules for when this can be set based on MergeOption and the current value(s) in the related end.
///
private void SetIsLoadedForSpan(RelatedEnd relatedEnd, bool forceToTrue)
{
Debug.Assert(relatedEnd != null, "RelatedEnd should not be null");
// We can now say this related end is "Loaded"
// The cases where we should set this to true are:
// AppendOnly: the related end is empty and does not point to a stub
// PreserveChanges: the related end is empty and does not point to a stub (otherwise, an Added item exists and IsLoaded should not change)
// OverwriteChanges: always
// NoTracking: always
if (!forceToTrue)
{
// Detect the empty value state of the relatedEnd
forceToTrue = relatedEnd.IsEmpty();
EntityReference reference = relatedEnd as EntityReference;
if (reference != null)
{
forceToTrue &= reference.EntityKey == null;
}
}
if (forceToTrue || this.MergeOption == MergeOption.OverwriteChanges)
{
relatedEnd.SetIsLoaded(true);
}
}
///
/// REQUIRES:: entity is not null; entity set may be null.
/// Sets up RelationshipManager on IEntityWithRelationships instance. Returns the input
/// entity so that the call can be composed within a ShaperEmitter Expression delegate.
///
public TEntity HandleIEntityWithRelationships(TEntity entity, EntitySet entitySet)
where TEntity : IEntityWithRelationships
{
Debug.Assert(null != entity, "entity null");
if (entitySet != null)
{
EntityUtil.AttachContext(entity, this.Context, entitySet, this.MergeOption == MergeOption.NoTracking ? MergeOption.NoTracking : MergeOption.AppendOnly);
}
return entity;
}
///
/// REQUIRES:: entity is not null and MergeOption is OverwriteChanges or PreserveChanges
/// Calls through to HandleEntity after retrieving the EntityKey from the given entity.
///
public TEntity HandleIEntityWithKey(TEntity entity, EntitySet entitySet)
where TEntity : IEntityWithKey
{
return HandleEntity(entity, entity.EntityKey, entitySet);
}
///
/// Calls through to the specified RecordState to set the value for the specified column ordinal.
///
public bool SetColumnValue(int recordStateSlotNumber, int ordinal, object value)
{
RecordState recordState = (RecordState)this.State[recordStateSlotNumber];
recordState.SetColumnValue(ordinal, value);
return true; // TRICKY: return true so we can use BitwiseOr expressions to string these guys together.
}
///
/// Calls through to the specified RecordState to set the value for the EntityRecordInfo.
///
public bool SetEntityRecordInfo(int recordStateSlotNumber, EntityKey entityKey, EntitySet entitySet)
{
RecordState recordState = (RecordState)this.State[recordStateSlotNumber];
recordState.SetEntityRecordInfo(entityKey, entitySet);
return true; // TRICKY: return true so we can use BitwiseOr expressions to string these guys together.
}
///
/// REQUIRES:: should be called only by delegate allocating this state.
/// Utility method assigning a value to a state slot. Returns an arbitrary value
/// allowing the method call to be composed in a ShapeEmitter Expression delegate.
///
public bool SetState(int ordinal, T value)
{
this.State[ordinal] = value;
return true; // TRICKY: return true so we can use BitwiseOr expressions to string these guys together.
}
///
/// REQUIRES:: should be called only by delegate allocating this state.
/// Utility method assigning a value to a state slot and return the value, allowing
/// the value to be accessed/set in a ShapeEmitter Expression delegate and later
/// retrieved.
///
public T SetStatePassthrough(int ordinal, T value)
{
this.State[ordinal] = value;
return value;
}
///
/// Used to retrieve a property value with exception handling. Normally compiled
/// delegates directly call typed methods on the DbDataReader (e.g. GetInt32)
/// but when an exception occurs we retry using this method to potentially get
/// a more useful error message to the user.
///
public TProperty GetPropertyValueWithErrorHandling(int ordinal, string propertyName, string typeName)
{
TProperty result = new PropertyErrorHandlingValueReader(propertyName, typeName).GetValue(this.Reader, ordinal);
return result;
}
///
/// Used to retrieve a column value with exception handling. Normally compiled
/// delegates directly call typed methods on the DbDataReader (e.g. GetInt32)
/// but when an exception occurs we retry using this method to potentially get
/// a more useful error message to the user.
///
public TColumn GetColumnValueWithErrorHandling(int ordinal)
{
TColumn result = new ColumnErrorHandlingValueReader().GetValue(this.Reader, ordinal);
return result;
}
#endregion
#region helper methods (used by runtime callable code)
private void CheckClearedEntryOnSpan(object targetValue, IEntityWithRelationships sourceEntity, EntityKey sourceKey, AssociationEndMember targetMember)
{
// If a relationship does not exist on the server but does exist on the client,
// we may need to remove it, depending on the current state and the MergeOption
if ((null != (object)sourceKey) && (null == targetValue) &&
(this.MergeOption == MergeOption.PreserveChanges ||
this.MergeOption == MergeOption.OverwriteChanges))
{
// When the spanned value is null, it may be because the spanned association applies to a
// subtype of the entity's type, and the entity is not actually an instance of that type.
AssociationEndMember sourceEnd = MetadataHelper.GetOtherAssociationEnd(targetMember);
EdmType expectedSourceType = ((RefType)sourceEnd.TypeUsage.EdmType).ElementType;
TypeUsage entityTypeUsage;
if (!this.Context.Perspective.TryGetType(sourceEntity.GetType(), out entityTypeUsage) ||
entityTypeUsage.EdmType.EdmEquals(expectedSourceType) ||
TypeSemantics.IsSubTypeOf(entityTypeUsage.EdmType, expectedSourceType))
{
// Otherwise, the source entity is the correct type (exactly or a subtype) for the source
// end of the spanned association, so validate that the relationhip that was spanned is
// part of the Container owning the EntitySet of the root entity.
// This can be done by comparing the EntitySet of the row's entity to the relationships
// in the Container and their AssociationSetEnd's type
CheckClearedEntryOnSpan(sourceKey, sourceEntity, targetMember);
}
}
}
private void CheckClearedEntryOnSpan(EntityKey sourceKey, IEntityWithRelationships sourceEntity, AssociationEndMember targetMember)
{
Debug.Assert(null != (object)sourceKey);
Debug.Assert(sourceEntity != null);
Debug.Assert(targetMember != null);
Debug.Assert(this.Context != null);
AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember);
EntityContainer entityContainer = this.Context.MetadataWorkspace.GetEntityContainer(sourceKey.EntityContainerName,
DataSpace.CSpace);
EntitySet sourceEntitySet;
AssociationSet associationSet = MetadataHelper.GetAssociationsForEntitySetAndAssociationType(entityContainer, sourceKey.EntitySetName,
(AssociationType)sourceMember.DeclaringType, sourceMember.Name, out sourceEntitySet);
if (associationSet != null)
{
Debug.Assert(associationSet.AssociationSetEnds[sourceMember.Name].EntitySet == sourceEntitySet);
ObjectStateManager.RemoveRelationships(Context, MergeOption, associationSet, sourceKey, sourceMember);
}
}
///
/// Wire's one or more full-spanned entities into the state manager; used by
/// both full-spanned collections and full-spanned entities.
///
private void FullSpanAction(IEntityWithRelationships sourceEntity, IList spannedEntities, AssociationEndMember targetMember)
{
EntityUtil.CheckKeyForRelationship(sourceEntity, MergeOption);
EntityKey sourceKey = ObjectStateManager.FindKeyOnEntityWithRelationships(sourceEntity);
if (sourceEntity != null)
{
IRelatedEnd relatedEnd;
if(sourceEntity.RelationshipManager.TryGetRelatedEnd(targetMember.DeclaringType.FullName, targetMember.Name, out relatedEnd))
{
// Add members of the list to the source entity (item in column 0)
AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember);
int count = ObjectStateManager.UpdateRelationships(this.Context, this.MergeOption, (AssociationSet)relatedEnd.RelationshipSet, sourceMember, sourceKey, sourceEntity, targetMember, (List)spannedEntities, true);
SetIsLoadedForSpan((RelatedEnd)relatedEnd, count > 0);
}
}
}
#region update existing ObjectStateEntry
private void UpdateEntry(TEntity entity, ObjectStateEntry existingEntry)
{
Debug.Assert(null != entity, "null entity");
Debug.Assert(null != existingEntry, "null ObjectStateEntry");
Debug.Assert(null != existingEntry.Entity, "ObjectStateEntry without Entity");
Type clrType = typeof(TEntity);
if (clrType != existingEntry.Entity.GetType())
{
throw EntityUtil.RecyclingEntity(existingEntry.EntityKey, clrType, existingEntry.Entity.GetType());
}
if (EntityState.Added == existingEntry.State)
{
throw EntityUtil.AddedEntityAlreadyExists(existingEntry.EntityKey);
}
if (MergeOption.AppendOnly != MergeOption)
{ // existing entity, update CSpace values in place
Debug.Assert(EntityState.Added != existingEntry.State, "entry in State=Added");
Debug.Assert(EntityState.Detached != existingEntry.State, "entry in State=Detached");
if (MergeOption.OverwriteChanges == MergeOption)
{
if (EntityState.Deleted == existingEntry.State)
{
existingEntry.RevertDelete();
}
Shaper.UpdateRecord(entity, existingEntry.CurrentValues);
existingEntry.AcceptChanges();
}
else
{
Debug.Assert(MergeOption.PreserveChanges == MergeOption, "not MergeOption.PreserveChanges");
if (EntityState.Unchanged == existingEntry.State)
{
// same behavior as MergeOption.OverwriteChanges
UpdateRecord(entity, existingEntry.CurrentValues);
existingEntry.AcceptChanges();
}
else
{
Shaper.UpdateRecord(entity, existingEntry.EditableOriginalValues);
}
}
}
}
static internal void UpdateRecord(object value, CurrentValueRecord current)
{
Debug.Assert(null != value, "null value");
Debug.Assert(null != current, "null CurrentValueRecord");
// get Metadata for type
StateManagerTypeMetadata typeMetadata = current._metadata;
DataRecordInfo recordInfo = typeMetadata.DataRecordInfo;
IBaseList structure = TypeHelpers.GetAllStructuralMembers(recordInfo.RecordType);
foreach (FieldMetadata field in recordInfo.FieldMetadata)
{
int index = structure.IndexOf(field.FieldType);
object fieldValue = typeMetadata.Member(index).GetValue(value) ?? DBNull.Value;
if (Helper.IsComplexType(field.FieldType.TypeUsage.EdmType))
{
object existing = current.GetValue(index);
// Ensure that the existing ComplexType value is not null. This is not supported.
if (existing == DBNull.Value)
{
throw EntityUtil.NullableComplexTypesNotSupported(field.FieldType.Name);
}
else if (fieldValue != DBNull.Value)
{
// There is both an IExtendedDataRecord and an existing CurrentValueRecord
Shaper.UpdateRecord(fieldValue, (CurrentValueRecord)existing);
}
}
else
{
Debug.Assert(Helper.IsPrimitiveType(field.FieldType.TypeUsage.EdmType),
"Property is not PrimitiveType");
object existing = current.GetValue(index) ?? DBNull.Value;
if ((existing != fieldValue) &&
(((object)DBNull.Value == fieldValue) ||
((object)DBNull.Value == existing) ||
(!existing.Equals(fieldValue))))
{
current.SetValue(index, fieldValue);
}
}
}
}
#endregion
#endregion
#region nested types
private abstract class ErrorHandlingValueReader
{
///
/// Gets value from reader using the same pattern as the materializer delegate. Avoids
/// the need to compile multiple delegates for error handling. If there is a failure
/// reading a value
///
internal T GetValue(DbDataReader reader, int ordinal)
{
T result;
bool isNullable;
MethodInfo readerMethod = Translator.GetReaderMethod(typeof(T), out isNullable);
if (reader.IsDBNull(ordinal))
{
try
{
result = (T)(object)null;
}
catch (NullReferenceException)
{
// NullReferenceException is thrown when casting null to a value type.
// We don't use isNullable here because of an issue with GetReaderMethod
//
throw CreateNullValueException();
}
}
else
{
try
{
// use the specific reader.GetXXX method
result = (T)readerMethod.Invoke(reader, new object[] { ordinal });
}
catch (Exception e)
{
if (EntityUtil.IsCatchableExceptionType(e))
{
// determine if the problem is with the result type
// (note that if we throw on this call, it's ok
// for it to percolate up -- we only intercept type
// and null mismatches)
object untypedResult = reader.GetValue(ordinal);
Type resultType = null == untypedResult ? null : untypedResult.GetType();
if (!typeof(T).IsAssignableFrom(resultType))
{
throw CreateWrongTypeException(resultType);
}
}
throw;
}
}
return result;
}
///
/// Creates the exception thrown when the reader returns a null value
/// for a non nullable property/column.
///
protected abstract Exception CreateNullValueException();
///
/// Creates the exception thrown when the reader returns a value with
/// an incompatible type.
///
protected abstract Exception CreateWrongTypeException(Type resultType);
}
private class ColumnErrorHandlingValueReader : ErrorHandlingValueReader
{
internal ColumnErrorHandlingValueReader()
{
}
protected override Exception CreateNullValueException()
{
return EntityUtil.ValueNullReferenceCast(typeof(TColumn));
}
protected override Exception CreateWrongTypeException(Type resultType)
{
return EntityUtil.ValueInvalidCast(resultType, typeof(TColumn));
}
}
private class PropertyErrorHandlingValueReader : ErrorHandlingValueReader
{
private readonly string _propertyName;
private readonly string _typeName;
internal PropertyErrorHandlingValueReader(string propertyName, string typeName)
{
_propertyName = propertyName;
_typeName = typeName;
}
protected override Exception CreateNullValueException()
{
return EntityUtil.Constraint(
System.Data.Entity.Strings.Materializer_SetInvalidValue(
(Nullable.GetUnderlyingType(typeof(TProperty)) ?? typeof(TProperty)).Name,
_typeName, _propertyName, "null"));
}
protected override Exception CreateWrongTypeException(Type resultType)
{
return EntityUtil.InvalidOperation(
System.Data.Entity.Strings.Materializer_SetInvalidValue(
(Nullable.GetUnderlyingType(typeof(TProperty)) ?? typeof(TProperty)).Name,
_typeName, _propertyName, resultType.Name));
}
}
#endregion
}
///
/// Typed Shaper. Includes logic to enumerate results and wraps the _rootCoordinator,
/// which includes materializer delegates for the root query collection.
///
internal sealed class Shaper : Shaper
{
#region private state
///
/// Shapers and Coordinators work together in harmony to materialize the data
/// from the store; the shaper contains the state, the coordinator contains the
/// code.
///
internal readonly Coordinator RootCoordinator;
///
/// What we need to call when we read to ensure we're maintaining security; will
/// do the approprate security demands.
///
private readonly Action CheckPermissionsAction;
///
/// Which type of query is this, object layer (true) or value layer (false)
///
private readonly bool IsObjectQuery;
///
/// Keeps track of whether we've completed processing or not.
///
private bool _isActive;
///
/// The enumerator we're using to read data; really only populated for value
/// layer queries.
///
private IEnumerator _rootEnumerator;
///
/// Whether the current value of _rootEnumerator has been returned by a bridge
/// data reader.
///
private bool _dataWaiting;
#endregion
#region constructor
internal Shaper(DbDataReader reader, ObjectContext context, MetadataWorkspace workspace, MergeOption mergeOption, int stateCount, CoordinatorFactory rootCoordinatorFactory, Action checkPermissions)
: base(reader, context, workspace, mergeOption, stateCount)
{
RootCoordinator = new Coordinator(rootCoordinatorFactory, /*parent*/ null, /*next*/ null);
CheckPermissionsAction = checkPermissions;
IsObjectQuery = !(typeof(T) == typeof(RecordState));
_isActive = true;
RootCoordinator.Initialize(this);
}
#endregion
#region "public" surface area
///
/// Events raised when the shaper has finished enumerating results. Useful for callback
/// to set parameter values.
///
internal event EventHandler OnDone;
///
/// Used to handle the read-ahead requirements of value-layer queries. This
/// field indicates the status of the current value of the _rootEnumerator; when
/// a bridge data reader "accepts responsibility" for the current value, it sets
/// this to false.
///
internal bool DataWaiting
{
get { return _dataWaiting; }
set { _dataWaiting = value; }
}
///
/// The enumerator that the value-layer bridge will use to read data; all nested
/// data readers need to use the same enumerator, so we put it on the Shaper, since
/// that is something that all the nested data readers (and data records) have access
/// to -- it prevents us from having to pass two objects around.
///
internal IEnumerator RootEnumerator
{
get
{
if (_rootEnumerator == null)
{
InitializeRecordStates(RootCoordinator.CoordinatorFactory);
_rootEnumerator = GetEnumerator();
}
return _rootEnumerator;
}
}
///
/// Initialize the RecordStateFactory objects in their StateSlots.
///
private void InitializeRecordStates(CoordinatorFactory coordinatorFactory)
{
foreach (RecordStateFactory recordStateFactory in coordinatorFactory.RecordStateFactories)
{
State[recordStateFactory.StateSlotNumber] = recordStateFactory.Create(coordinatorFactory);
}
foreach (CoordinatorFactory nestedCoordinatorFactory in coordinatorFactory.NestedCoordinators)
{
InitializeRecordStates(nestedCoordinatorFactory);
}
}
public IEnumerator GetEnumerator()
{
// we can use a simple enumerator if there are no nested results, no keys and no "has data"
// discriminator
if (RootCoordinator.CoordinatorFactory.IsSimple)
{
return new SimpleEnumerator(this);
}
else
{
RowNestedResultEnumerator rowEnumerator = new Shaper.RowNestedResultEnumerator(this);
if (this.IsObjectQuery)
{
return new ObjectQueryNestedEnumerator(rowEnumerator);
}
else
{
return (IEnumerator)(object)(new RecordStateEnumerator(rowEnumerator));
}
}
}
#endregion
#region enumerator helpers
///
/// Called when enumeration of results has completed.
///
private void Finally()
{
if (_isActive)
{
_isActive = false;
// I'd prefer not to special case this, but value-layer behavior is that you
// must explicitly close the data reader; if we automatically dispose of the
// reader here, we won't have that behavior.
if (IsObjectQuery)
{
this.Reader.Dispose();
}
// This case includes when the ObjectResult is disposed before it
// created an ObjectQueryEnumeration; at this time, the connection can be released
if (this.Context != null)
{
this.Context.ReleaseConnection();
}
if (null != this.OnDone)
{
this.OnDone(this, new EventArgs());
}
}
}
///
/// Reads the next row from the store. If there is a failure, throws an exception message
/// in some scenarios (note that we respond to failure rather than anticipate failure,
/// avoiding repeated checks in the inner materialization loop)
///
private bool StoreRead()
{
try
{
return this.Reader.Read();
}
catch (Exception e)
{
// check if the reader is closed; if so, throw friendlier exception
if (this.Reader.IsClosed)
{
const string operation = "Read";
throw EntityUtil.DataReaderClosed(operation);
}
// wrap exception if necessary
if (EntityUtil.IsCatchableEntityExceptionType(e))
{
throw EntityUtil.CommandExecution(System.Data.Entity.Strings.EntityClient_StoreReaderFailed, e);
}
throw;
}
}
#endregion
#region simple enumerator
///
/// Optimized enumerator for queries not including nested results.
///
private class SimpleEnumerator : IEnumerator
{
private readonly Shaper _shaper;
internal SimpleEnumerator(Shaper shaper)
{
_shaper = shaper;
}
public T Current
{
get { return _shaper.RootCoordinator.Current; }
}
object System.Collections.IEnumerator.Current
{
get { return _shaper.RootCoordinator.Current; }
}
public void Dispose()
{
// For backwards compatibility, we set the current value to the
// default value, so you can still call Current.
_shaper.RootCoordinator.SetCurrentToDefault();
_shaper.Finally();
}
public bool MoveNext()
{
if (!_shaper._isActive)
{
return false;
}
if (_shaper.StoreRead())
{
if (null != _shaper.CheckPermissionsAction)
{
_shaper.CheckPermissionsAction();
}
_shaper.RootCoordinator.ReadNextElement(_shaper);
return true;
}
this.Dispose();
return false;
}
public void Reset()
{
throw EntityUtil.NotSupported();
}
}
#endregion
#region nested enumerator
///
/// Enumerates (for each row in the input) an array of all coordinators producing new elements. The array
/// contains a position for each 'depth' in the result. A null value in any position indicates that no new
/// results were produced for the given row at the given depth. It is possible for a row to contain no
/// results for any row.
///
private class RowNestedResultEnumerator : IEnumerator
{
private readonly Shaper _shaper;
private readonly Coordinator[] _current;
internal RowNestedResultEnumerator(Shaper shaper)
{
_shaper = shaper;
_current = new Coordinator[_shaper.RootCoordinator.MaxDistanceToLeaf() + 1];
}
public Coordinator[] Current
{
get { return _current; }
}
public void Dispose()
{
_shaper.Finally();
}
object System.Collections.IEnumerator.Current
{
get { return _current; }
}
public bool MoveNext()
{
Coordinator currentCoordinator = _shaper.RootCoordinator;
if (!_shaper.StoreRead())
{
// Reset all collections
this.RootCoordinator.ResetCollection(_shaper);
return false;
}
int depth = 0;
bool haveInitializedChildren = false;
for (; depth < _current.Length; depth++)
{
// find a coordinator at this depth that currently has data (if any)
while (currentCoordinator != null && !currentCoordinator.CoordinatorFactory.HasData(_shaper))
{
currentCoordinator = currentCoordinator.Next;
}
if (null == currentCoordinator)
{
break;
}
// check if this row contains a new element for this coordinator
if (currentCoordinator.HasNextElement(_shaper))
{
// if we have children and haven't initialized them yet, do so now
if (!haveInitializedChildren && null != currentCoordinator.Child)
{
currentCoordinator.Child.ResetCollection(_shaper);
}
haveInitializedChildren = true;
// read the next element
currentCoordinator.ReadNextElement(_shaper);
// place the coordinator in the result array to indicate there is a new
// element at this depth
_current[depth] = currentCoordinator;
}
else
{
// clear out the coordinator in result array to indicate there is no new
// element at this depth
_current[depth] = null;
}
// move to child (in the next iteration we deal with depth + 1
currentCoordinator = currentCoordinator.Child;
}
// clear out all positions below the depth we reached before we ran out of data
for (; depth < _current.Length; depth++)
{
_current[depth] = null;
}
return true;
}
public void Reset()
{
throw EntityUtil.NotSupported();
}
internal Coordinator RootCoordinator
{
get { return _shaper.RootCoordinator; }
}
}
///
/// Wraps RowNestedResultEnumerator and yields results appropriate to an ObjectQuery instance. In particular,
/// root level elements (T) are returned only after aggregating all child elements.
///
private class ObjectQueryNestedEnumerator : IEnumerator
{
private readonly RowNestedResultEnumerator _rowEnumerator;
private T _previousElement;
private State _state;
internal ObjectQueryNestedEnumerator(RowNestedResultEnumerator rowEnumerator)
{
_rowEnumerator = rowEnumerator;
_previousElement = default(T);
_state = State.Start;
}
public T Current { get { return _previousElement; } }
public void Dispose()
{
_rowEnumerator.Dispose();
}
object System.Collections.IEnumerator.Current { get { return this.Current; } }
public bool MoveNext()
{
// See the documentation for enum State to understand the behaviors and requirements
// for each state.
switch (_state)
{
case State.Start:
{
if (TryReadToNextElement())
{
// if there's an element in the reader...
ReadElement();
}
else
{
// no data at all...
_state = State.NoRows;
}
};
break;
case State.Reading:
{
ReadElement();
};
break;
case State.NoRowsLastElementPending:
{
// nothing to do but move to the next state...
_state = State.NoRows;
};
break;
}
bool result;
if (_state == State.NoRows)
{
_previousElement = default(T);
result = false;
}
else
{
result = true;
}
return result;
}
///
/// Requires: the row is currently positioned at the start of an element.
///
/// Reads all rows in the element and sets up state for the next element (if any).
///
private void ReadElement()
{
// remember the element we're currently reading
_previousElement = _rowEnumerator.RootCoordinator.Current;
// now we need to read to the next element (or the end of the
// reader) so that we can return the first element
if (TryReadToNextElement())
{
// we're positioned at the start of the next element (which
// corresponds to the 'reading' state)
_state = State.Reading;
}
else
{
// we're positioned at the end of the reader
_state = State.NoRowsLastElementPending;
}
}
///
/// Reads rows until the start of a new element is found. If no element
/// is found before all rows are consumed, returns false.
///
private bool TryReadToNextElement()
{
bool result = false;
while (_rowEnumerator.MoveNext())
{
// if we hit a new element, return true
if (_rowEnumerator.Current[0] != null)
{
result = true;
break;
}
}
return result;
}
public void Reset()
{
_rowEnumerator.Reset();
}
///
/// Describes the state of this enumerator with respect to the _rowEnumerator
/// it wraps.
///
private enum State
{
///
/// No rows have been read yet
///
Start,
///
/// Positioned at the start of a new root element. The previous element must
/// be stored in _previousElement. We read ahead in this manner so that
/// the previous element is fully populated (all of its children loaded)
/// before returning.
///
Reading,
///
/// Positioned past the end of the rows. The last element in the enumeration
/// has not yet been returned to the user however, and is stored in _previousElement.
///
NoRowsLastElementPending,
///
/// Positioned past the end of the rows. The last element has been returned to
/// the user.
///
NoRows,
}
}
///
/// Wraps RowNestedResultEnumerator and yields results appropriate to an EntityReader instance. In particular,
/// yields RecordState whenever a new element becomes available at any depth in the result hierarchy.
///
private class RecordStateEnumerator : IEnumerator
{
private readonly RowNestedResultEnumerator _rowEnumerator;
private RecordState _current;
///
/// Gets depth of coordinator we're currently consuming. If _depth == -1, it means we haven't started
/// to consume the next row yet.
///
private int _depth;
private bool _readerConsumed;
internal RecordStateEnumerator(RowNestedResultEnumerator rowEnumerator)
{
_rowEnumerator = rowEnumerator;
_current = null;
_depth = -1;
_readerConsumed = false;
}
public RecordState Current
{
get { return _current; }
}
public void Dispose()
{
_rowEnumerator.Dispose();
}
object System.Collections.IEnumerator.Current
{
get { return _current; }
}
public bool MoveNext()
{
if (!_readerConsumed)
{
while (true)
{
// keep on cycling until we find a result
if (-1 == _depth || _rowEnumerator.Current.Length == _depth)
{
// time to move to the next row...
if (!_rowEnumerator.MoveNext())
{
// no more rows...
_current = null;
_readerConsumed = true;
break;
}
_depth = 0;
}
// check for results at the current depth
Coordinator currentCoordinator = _rowEnumerator.Current[_depth];
if (null != currentCoordinator)
{
_current = ((Coordinator)currentCoordinator).Current;
_depth++;
break;
}
_depth++;
}
}
return !_readerConsumed;
}
public void Reset()
{
_rowEnumerator.Reset();
}
}
#endregion
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// [....]
// [....]
//-----------------------------------------------------------------------------
using System.Collections.Generic;
using System.Data.Common.Utils;
using System.Linq;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Diagnostics;
using System.Reflection;
namespace System.Data.Common.Internal.Materialization
{
///
/// Shapes store reader values into EntityClient/ObjectQuery results. Also maintains
/// state used by materializer delegates.
///
internal abstract class Shaper
{
#region constructor
internal Shaper(DbDataReader reader, ObjectContext context, MetadataWorkspace workspace, MergeOption mergeOption, int stateCount)
{
Debug.Assert(context == null || workspace == context.MetadataWorkspace, "workspace must match context's workspace");
this.Reader = reader;
this.MergeOption = mergeOption;
this.State = new object[stateCount];
this.Context = context;
this.Workspace = workspace;
}
#endregion
#region runtime callable/accessible code
// Code in this section is called from the delegates produced by the Translator. It
// may not show up if you search using Find All References...use Find in Files instead.
//
// Many items on this class are public, simply to make the job of producing the
// expressions that use them simpler. If you have a hankering to make them private,
// you will need to modify the code in the Translator that does the GetMethod/GetField
// to use BindingFlags.NonPublic | BindingFlags.Instance as well.
//
// Debug.Asserts that fire from the code in this region will probably create a
// SecurityException in the Coordinator's Read method since those are restricted when
// running the Shaper.
///
/// The store data reader we're pulling data from
///
public readonly DbDataReader Reader;
///
/// The state slots we use in the coordinator expression.
///
public readonly object[] State;
///
/// The context the shaper is performing for.
///
public readonly ObjectContext Context;
///
/// The workspace we are performing for; yes we could get it from the context, but
/// it's much easier to just have it handy.
///
public readonly MetadataWorkspace Workspace;
///
/// The merge option this shaper is performing under/for.
///
public readonly MergeOption MergeOption;
///
/// Utility method used to evaluate a multi-discriminator column map. Takes
/// discriminator values and determines the appropriate entity type, then looks up
/// the appropriate handler and invokes it.
///
public TElement Discriminate(object[] discriminatorValues, Func discriminate, KeyValuePair>[] elementDelegates)
{
EntityType entityType = discriminate(discriminatorValues);
Func elementDelegate = null;
foreach (KeyValuePair> typeDelegatePair in elementDelegates)
{
if (typeDelegatePair.Key == entityType)
{
elementDelegate = typeDelegatePair.Value;
}
}
return elementDelegate(this);
}
///
/// REQUIRES:: entity is not null and MergeOption is OverwriteChanges or PreserveChanges
/// Handles state management for an entity returned by a query. Where an existing entry
/// exists, updates that entry and returns the existing entity. Otherwise, the entity
/// passed in is returned.
///
public TEntity HandleEntity(TEntity entity, EntityKey entityKey, EntitySet entitySet)
{
Debug.Assert(MergeOption.NoTracking != this.MergeOption, "no need to HandleEntity if there's no tracking");
Debug.Assert(MergeOption.AppendOnly != this.MergeOption, "use HandleEntityAppendOnly instead...");
Debug.Assert(null != entity, "if HandleEntity is called, there must be an entity");
TEntity result = entity;
// no entity set, so no tracking is required for this entity
if (null != (object)entityKey)
{
Debug.Assert(null != entitySet, "if there is an entity key, there must also be an entity set");
// check for an existing entity with the same key
ObjectStateEntry existingEntry = this.Context.ObjectStateManager.FindObjectStateEntry(entityKey);
if (null != existingEntry && !existingEntry.IsKeyEntry)
{
Debug.Assert(existingEntry.EntityKey.Equals(entityKey), "Found ObjectStateEntry with wrong EntityKey");
UpdateEntry(entity, existingEntry);
result = (TEntity)existingEntry.Entity;
}
else
{
// if the entity isn't tracked yet, attach it
if (null == existingEntry)
{
Context.ObjectStateManager.AddEntry(entity, entityKey, entitySet, "HandleEntity", false);
}
else
{
Context.ObjectStateManager.PromoteKeyEntry(existingEntry, entity, (IExtendedDataRecord)null, false, /*setIsLoaded*/ true, /*keyEntryInitialized*/ false, "HandleEntity");
}
}
}
return result;
}
///
/// REQUIRES:: entity exists; MergeOption is AppendOnly
/// Handles state management for an entity with the given key. When the entity already exists
/// in the state manager, it is returned directly. Otherwise, the entityDelegate is invoked and
/// the resulting entity is returned.
///
public TEntity HandleEntityAppendOnly(Func constructEntityDelegate, EntityKey entityKey, EntitySet entitySet)
{
Debug.Assert(this.MergeOption == MergeOption.AppendOnly, "only use HandleEntityAppendOnly when MergeOption is AppendOnly");
Debug.Assert(null != constructEntityDelegate, "must provide delegate to construct the entity");
TEntity result;
if (null == (object)entityKey)
{
// no entity set, so no tracking is required for this entity, just
// call the delegate to "materialize" it.
result = constructEntityDelegate(this);
}
else
{
Debug.Assert(null != entitySet, "if there is an entity key, there must also be an entity set");
// check for an existing entity with the same key
ObjectStateEntry existingEntry = this.Context.ObjectStateManager.FindObjectStateEntry(entityKey);
if (null != existingEntry && !existingEntry.IsKeyEntry)
{
Debug.Assert(existingEntry.EntityKey.Equals(entityKey), "Found ObjectStateEntry with wrong EntityKey");
if (typeof(TEntity) != existingEntry.Entity.GetType())
{
throw EntityUtil.RecyclingEntity(existingEntry.EntityKey, typeof(TEntity), existingEntry.Entity.GetType());
}
if (EntityState.Added == existingEntry.State)
{
throw EntityUtil.AddedEntityAlreadyExists(existingEntry.EntityKey);
}
result = (TEntity)existingEntry.Entity;
}
else
{
// We don't already have the entity, so construct it and if it isn't
// tracked yet, attach it
result = constructEntityDelegate(this);
if (null == existingEntry)
{
Context.ObjectStateManager.AddEntry(result, entityKey, entitySet, "HandleEntity", false);
}
else
{
Context.ObjectStateManager.PromoteKeyEntry(existingEntry, result, (IExtendedDataRecord)null, false, /*setIsLoaded*/ true, /*keyEntryInitialized*/ false, "HandleEntity");
}
}
}
return result;
}
///
/// Call to ensure a collection of full-spanned elements are added
/// into the state manager properly. We registers an action to be called
/// when the collection is closed that pulls the collection of full spanned
/// objects into the state manager.
///
public T_SourceEntity HandleFullSpanCollection(T_SourceEntity entity, Coordinator coordinator, AssociationEndMember targetMember)
{
IEntityWithRelationships sourceEntity = entity as IEntityWithRelationships;
if (null != sourceEntity)
{
coordinator.RegisterCloseHandler((state, spannedEntities) => FullSpanAction(sourceEntity, spannedEntities, targetMember));
}
return entity;
}
///
/// Call to ensure a single full-spanned element is added into
/// the state manager properly.
///
public T_SourceEntity HandleFullSpanElement(T_SourceEntity entity, T_TargetEntity spannedEntity, AssociationEndMember targetMember)
{
IEntityWithRelationships sourceEntity = entity as IEntityWithRelationships;
if (null != sourceEntity)
{
List spannedEntities = null;
if (spannedEntity != null)
{
// There was a single entity in the column
// Create a list so we can perform the same logic as a collection of entities
spannedEntities = new List(1);
spannedEntities.Add(spannedEntity);
}
else
{
EntityKey sourceKey = ObjectStateManager.FindKeyOnEntityWithRelationships(sourceEntity);
CheckClearedEntryOnSpan(spannedEntity, sourceEntity, sourceKey, targetMember);
}
FullSpanAction(sourceEntity, spannedEntities, targetMember);
}
return entity;
}
///
/// Call to ensure a target entities key is added into the state manager
/// properly
///
public T_SourceEntity HandleRelationshipSpan(T_SourceEntity entity, EntityKey targetKey, AssociationEndMember targetMember)
{
IEntityWithRelationships sourceEntity = entity as IEntityWithRelationships;
if (null == sourceEntity)
{
return entity;
}
Debug.Assert(targetMember != null);
Debug.Assert(targetMember.RelationshipMultiplicity == RelationshipMultiplicity.One || targetMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne);
EntityUtil.CheckKeyForRelationship(sourceEntity, MergeOption);
EntityKey sourceKey = ObjectStateManager.FindKeyOnEntityWithRelationships(sourceEntity);
AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember);
CheckClearedEntryOnSpan(targetKey, sourceEntity, sourceKey, targetMember);
if (null != (object)targetKey)
{
EntitySet targetEntitySet;
EntityContainer entityContainer = this.Context.MetadataWorkspace.GetEntityContainer(
targetKey.EntityContainerName, DataSpace.CSpace);
// find the correct AssociationSet
AssociationSet associationSet = MetadataHelper.GetAssociationsForEntitySetAndAssociationType(entityContainer,
targetKey.EntitySetName, (AssociationType)(targetMember.DeclaringType), targetMember.Name, out targetEntitySet);
Debug.Assert(associationSet != null, "associationSet should not be null");
ObjectStateManager manager = Context.ObjectStateManager;
EntityState newEntryState;
// If there is an existing relationship entry, update it based on its current state and the MergeOption, otherwise add a new one
if (!ObjectStateManager.TryUpdateExistingRelationships(this.Context, this.MergeOption, associationSet, sourceMember, sourceKey, sourceEntity, targetMember, targetKey, /*setIsLoaded*/ true, out newEntryState))
{
// Try to find a state entry for the target key
ObjectStateEntry targetEntry = null;
if (!manager.TryGetObjectStateEntry(targetKey, out targetEntry))
{
// no entry exists for the target key
// create a key entry for the target
targetEntry = manager.AddKeyEntry(targetKey, targetEntitySet);
}
// SQLBU 557105. For 1-1 relationships we have to take care of the relationships of targetEntity
bool needNewRelationship = true;
switch (sourceMember.RelationshipMultiplicity)
{
case RelationshipMultiplicity.ZeroOrOne:
case RelationshipMultiplicity.One:
// devnote: targetEntry can be a key entry (targetEntry.Entity == null),
// but it that case this parameter won't be used in TryUpdateExistingRelationships
needNewRelationship = !ObjectStateManager.TryUpdateExistingRelationships(this.Context,
this.MergeOption,
associationSet,
targetMember,
targetKey,
targetEntry.Entity as IEntityWithRelationships,
sourceMember,
sourceKey,
/*setIsLoaded*/ true,
out newEntryState);
// It is possible that as part of removing existing relationships, the key entry was deleted
// If that is the case, recreate the key entry
if (targetEntry.State == EntityState.Detached)
{
targetEntry = manager.AddKeyEntry(targetKey, targetEntitySet);
}
break;
case RelationshipMultiplicity.Many:
// we always need a new relationship with Many-To-Many, if there was no exact match between these two entities, so do nothing
break;
default:
Debug.Assert(false, "Unexpected sourceMember.RelationshipMultiplicity");
break;
}
if (needNewRelationship)
{
// If the target entry is a key entry, then we need to add a relation
// between the source and target entries
// If we are in a state where we just need to add a new Deleted relation, we
// only need to do that and not touch the related ends
// If the target entry is a full entity entry, then we need to add
// the target entity to the source collection or reference
if (targetEntry.IsKeyEntry || newEntryState == EntityState.Deleted)
{
// Add a relationship between the source entity and the target key entry
RelationshipWrapper wrapper = new RelationshipWrapper(associationSet, sourceMember.Name, sourceKey, targetMember.Name, targetKey);
manager.AddNewRelation(wrapper, newEntryState);
}
else
{
Debug.Assert(!targetEntry.IsRelationship, "how IsRelationship?");
if (targetEntry.State != EntityState.Deleted)
{
// The entry contains an entity, do collection or reference fixup
// This will also try to create a new relationship entry or will revert the delete on an existing deleted relationship
ObjectStateManager.AddEntityToCollectionOrReference(
this.MergeOption, sourceEntity, sourceMember,
targetEntry.Entity as IEntityWithRelationships,
targetMember,
/*setIsLoaded*/ true,
/*relationshipAlreadyExists*/ false,
/* inKeyEntryPromotion */ false);
}
else
{
// if the target entry is deleted, then the materializer needs to create a deleted relationship
// between the entity and the target entry so that if the entity is deleted, the update
// pipeline can find the relationship (even though it is deleted)
RelationshipWrapper wrapper = new RelationshipWrapper(associationSet, sourceMember.Name, sourceKey, targetMember.Name, targetKey);
manager.AddNewRelation(wrapper, EntityState.Deleted);
}
}
}
}
}
else
{
IRelatedEnd relatedEnd;
if(sourceEntity.RelationshipManager.TryGetRelatedEnd(sourceMember.DeclaringType.FullName, targetMember.Name, out relatedEnd))
{
SetIsLoadedForSpan((RelatedEnd)relatedEnd, false);
}
}
// else there is nothing else for us to do, the relationship has been handled already
return entity;
}
///
/// Sets the IsLoaded flag to "true"
/// There are also rules for when this can be set based on MergeOption and the current value(s) in the related end.
///
private void SetIsLoadedForSpan(RelatedEnd relatedEnd, bool forceToTrue)
{
Debug.Assert(relatedEnd != null, "RelatedEnd should not be null");
// We can now say this related end is "Loaded"
// The cases where we should set this to true are:
// AppendOnly: the related end is empty and does not point to a stub
// PreserveChanges: the related end is empty and does not point to a stub (otherwise, an Added item exists and IsLoaded should not change)
// OverwriteChanges: always
// NoTracking: always
if (!forceToTrue)
{
// Detect the empty value state of the relatedEnd
forceToTrue = relatedEnd.IsEmpty();
EntityReference reference = relatedEnd as EntityReference;
if (reference != null)
{
forceToTrue &= reference.EntityKey == null;
}
}
if (forceToTrue || this.MergeOption == MergeOption.OverwriteChanges)
{
relatedEnd.SetIsLoaded(true);
}
}
///
/// REQUIRES:: entity is not null; entity set may be null.
/// Sets up RelationshipManager on IEntityWithRelationships instance. Returns the input
/// entity so that the call can be composed within a ShaperEmitter Expression delegate.
///
public TEntity HandleIEntityWithRelationships(TEntity entity, EntitySet entitySet)
where TEntity : IEntityWithRelationships
{
Debug.Assert(null != entity, "entity null");
if (entitySet != null)
{
EntityUtil.AttachContext(entity, this.Context, entitySet, this.MergeOption == MergeOption.NoTracking ? MergeOption.NoTracking : MergeOption.AppendOnly);
}
return entity;
}
///
/// REQUIRES:: entity is not null and MergeOption is OverwriteChanges or PreserveChanges
/// Calls through to HandleEntity after retrieving the EntityKey from the given entity.
///
public TEntity HandleIEntityWithKey(TEntity entity, EntitySet entitySet)
where TEntity : IEntityWithKey
{
return HandleEntity(entity, entity.EntityKey, entitySet);
}
///
/// Calls through to the specified RecordState to set the value for the specified column ordinal.
///
public bool SetColumnValue(int recordStateSlotNumber, int ordinal, object value)
{
RecordState recordState = (RecordState)this.State[recordStateSlotNumber];
recordState.SetColumnValue(ordinal, value);
return true; // TRICKY: return true so we can use BitwiseOr expressions to string these guys together.
}
///
/// Calls through to the specified RecordState to set the value for the EntityRecordInfo.
///
public bool SetEntityRecordInfo(int recordStateSlotNumber, EntityKey entityKey, EntitySet entitySet)
{
RecordState recordState = (RecordState)this.State[recordStateSlotNumber];
recordState.SetEntityRecordInfo(entityKey, entitySet);
return true; // TRICKY: return true so we can use BitwiseOr expressions to string these guys together.
}
///
/// REQUIRES:: should be called only by delegate allocating this state.
/// Utility method assigning a value to a state slot. Returns an arbitrary value
/// allowing the method call to be composed in a ShapeEmitter Expression delegate.
///
public bool SetState(int ordinal, T value)
{
this.State[ordinal] = value;
return true; // TRICKY: return true so we can use BitwiseOr expressions to string these guys together.
}
///
/// REQUIRES:: should be called only by delegate allocating this state.
/// Utility method assigning a value to a state slot and return the value, allowing
/// the value to be accessed/set in a ShapeEmitter Expression delegate and later
/// retrieved.
///
public T SetStatePassthrough(int ordinal, T value)
{
this.State[ordinal] = value;
return value;
}
///
/// Used to retrieve a property value with exception handling. Normally compiled
/// delegates directly call typed methods on the DbDataReader (e.g. GetInt32)
/// but when an exception occurs we retry using this method to potentially get
/// a more useful error message to the user.
///
public TProperty GetPropertyValueWithErrorHandling(int ordinal, string propertyName, string typeName)
{
TProperty result = new PropertyErrorHandlingValueReader(propertyName, typeName).GetValue(this.Reader, ordinal);
return result;
}
///
/// Used to retrieve a column value with exception handling. Normally compiled
/// delegates directly call typed methods on the DbDataReader (e.g. GetInt32)
/// but when an exception occurs we retry using this method to potentially get
/// a more useful error message to the user.
///
public TColumn GetColumnValueWithErrorHandling(int ordinal)
{
TColumn result = new ColumnErrorHandlingValueReader().GetValue(this.Reader, ordinal);
return result;
}
#endregion
#region helper methods (used by runtime callable code)
private void CheckClearedEntryOnSpan(object targetValue, IEntityWithRelationships sourceEntity, EntityKey sourceKey, AssociationEndMember targetMember)
{
// If a relationship does not exist on the server but does exist on the client,
// we may need to remove it, depending on the current state and the MergeOption
if ((null != (object)sourceKey) && (null == targetValue) &&
(this.MergeOption == MergeOption.PreserveChanges ||
this.MergeOption == MergeOption.OverwriteChanges))
{
// When the spanned value is null, it may be because the spanned association applies to a
// subtype of the entity's type, and the entity is not actually an instance of that type.
AssociationEndMember sourceEnd = MetadataHelper.GetOtherAssociationEnd(targetMember);
EdmType expectedSourceType = ((RefType)sourceEnd.TypeUsage.EdmType).ElementType;
TypeUsage entityTypeUsage;
if (!this.Context.Perspective.TryGetType(sourceEntity.GetType(), out entityTypeUsage) ||
entityTypeUsage.EdmType.EdmEquals(expectedSourceType) ||
TypeSemantics.IsSubTypeOf(entityTypeUsage.EdmType, expectedSourceType))
{
// Otherwise, the source entity is the correct type (exactly or a subtype) for the source
// end of the spanned association, so validate that the relationhip that was spanned is
// part of the Container owning the EntitySet of the root entity.
// This can be done by comparing the EntitySet of the row's entity to the relationships
// in the Container and their AssociationSetEnd's type
CheckClearedEntryOnSpan(sourceKey, sourceEntity, targetMember);
}
}
}
private void CheckClearedEntryOnSpan(EntityKey sourceKey, IEntityWithRelationships sourceEntity, AssociationEndMember targetMember)
{
Debug.Assert(null != (object)sourceKey);
Debug.Assert(sourceEntity != null);
Debug.Assert(targetMember != null);
Debug.Assert(this.Context != null);
AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember);
EntityContainer entityContainer = this.Context.MetadataWorkspace.GetEntityContainer(sourceKey.EntityContainerName,
DataSpace.CSpace);
EntitySet sourceEntitySet;
AssociationSet associationSet = MetadataHelper.GetAssociationsForEntitySetAndAssociationType(entityContainer, sourceKey.EntitySetName,
(AssociationType)sourceMember.DeclaringType, sourceMember.Name, out sourceEntitySet);
if (associationSet != null)
{
Debug.Assert(associationSet.AssociationSetEnds[sourceMember.Name].EntitySet == sourceEntitySet);
ObjectStateManager.RemoveRelationships(Context, MergeOption, associationSet, sourceKey, sourceMember);
}
}
///
/// Wire's one or more full-spanned entities into the state manager; used by
/// both full-spanned collections and full-spanned entities.
///
private void FullSpanAction(IEntityWithRelationships sourceEntity, IList spannedEntities, AssociationEndMember targetMember)
{
EntityUtil.CheckKeyForRelationship(sourceEntity, MergeOption);
EntityKey sourceKey = ObjectStateManager.FindKeyOnEntityWithRelationships(sourceEntity);
if (sourceEntity != null)
{
IRelatedEnd relatedEnd;
if(sourceEntity.RelationshipManager.TryGetRelatedEnd(targetMember.DeclaringType.FullName, targetMember.Name, out relatedEnd))
{
// Add members of the list to the source entity (item in column 0)
AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember);
int count = ObjectStateManager.UpdateRelationships(this.Context, this.MergeOption, (AssociationSet)relatedEnd.RelationshipSet, sourceMember, sourceKey, sourceEntity, targetMember, (List)spannedEntities, true);
SetIsLoadedForSpan((RelatedEnd)relatedEnd, count > 0);
}
}
}
#region update existing ObjectStateEntry
private void UpdateEntry(TEntity entity, ObjectStateEntry existingEntry)
{
Debug.Assert(null != entity, "null entity");
Debug.Assert(null != existingEntry, "null ObjectStateEntry");
Debug.Assert(null != existingEntry.Entity, "ObjectStateEntry without Entity");
Type clrType = typeof(TEntity);
if (clrType != existingEntry.Entity.GetType())
{
throw EntityUtil.RecyclingEntity(existingEntry.EntityKey, clrType, existingEntry.Entity.GetType());
}
if (EntityState.Added == existingEntry.State)
{
throw EntityUtil.AddedEntityAlreadyExists(existingEntry.EntityKey);
}
if (MergeOption.AppendOnly != MergeOption)
{ // existing entity, update CSpace values in place
Debug.Assert(EntityState.Added != existingEntry.State, "entry in State=Added");
Debug.Assert(EntityState.Detached != existingEntry.State, "entry in State=Detached");
if (MergeOption.OverwriteChanges == MergeOption)
{
if (EntityState.Deleted == existingEntry.State)
{
existingEntry.RevertDelete();
}
Shaper.UpdateRecord(entity, existingEntry.CurrentValues);
existingEntry.AcceptChanges();
}
else
{
Debug.Assert(MergeOption.PreserveChanges == MergeOption, "not MergeOption.PreserveChanges");
if (EntityState.Unchanged == existingEntry.State)
{
// same behavior as MergeOption.OverwriteChanges
UpdateRecord(entity, existingEntry.CurrentValues);
existingEntry.AcceptChanges();
}
else
{
Shaper.UpdateRecord(entity, existingEntry.EditableOriginalValues);
}
}
}
}
static internal void UpdateRecord(object value, CurrentValueRecord current)
{
Debug.Assert(null != value, "null value");
Debug.Assert(null != current, "null CurrentValueRecord");
// get Metadata for type
StateManagerTypeMetadata typeMetadata = current._metadata;
DataRecordInfo recordInfo = typeMetadata.DataRecordInfo;
IBaseList structure = TypeHelpers.GetAllStructuralMembers(recordInfo.RecordType);
foreach (FieldMetadata field in recordInfo.FieldMetadata)
{
int index = structure.IndexOf(field.FieldType);
object fieldValue = typeMetadata.Member(index).GetValue(value) ?? DBNull.Value;
if (Helper.IsComplexType(field.FieldType.TypeUsage.EdmType))
{
object existing = current.GetValue(index);
// Ensure that the existing ComplexType value is not null. This is not supported.
if (existing == DBNull.Value)
{
throw EntityUtil.NullableComplexTypesNotSupported(field.FieldType.Name);
}
else if (fieldValue != DBNull.Value)
{
// There is both an IExtendedDataRecord and an existing CurrentValueRecord
Shaper.UpdateRecord(fieldValue, (CurrentValueRecord)existing);
}
}
else
{
Debug.Assert(Helper.IsPrimitiveType(field.FieldType.TypeUsage.EdmType),
"Property is not PrimitiveType");
object existing = current.GetValue(index) ?? DBNull.Value;
if ((existing != fieldValue) &&
(((object)DBNull.Value == fieldValue) ||
((object)DBNull.Value == existing) ||
(!existing.Equals(fieldValue))))
{
current.SetValue(index, fieldValue);
}
}
}
}
#endregion
#endregion
#region nested types
private abstract class ErrorHandlingValueReader
{
///
/// Gets value from reader using the same pattern as the materializer delegate. Avoids
/// the need to compile multiple delegates for error handling. If there is a failure
/// reading a value
///
internal T GetValue(DbDataReader reader, int ordinal)
{
T result;
bool isNullable;
MethodInfo readerMethod = Translator.GetReaderMethod(typeof(T), out isNullable);
if (reader.IsDBNull(ordinal))
{
try
{
result = (T)(object)null;
}
catch (NullReferenceException)
{
// NullReferenceException is thrown when casting null to a value type.
// We don't use isNullable here because of an issue with GetReaderMethod
//
throw CreateNullValueException();
}
}
else
{
try
{
// use the specific reader.GetXXX method
result = (T)readerMethod.Invoke(reader, new object[] { ordinal });
}
catch (Exception e)
{
if (EntityUtil.IsCatchableExceptionType(e))
{
// determine if the problem is with the result type
// (note that if we throw on this call, it's ok
// for it to percolate up -- we only intercept type
// and null mismatches)
object untypedResult = reader.GetValue(ordinal);
Type resultType = null == untypedResult ? null : untypedResult.GetType();
if (!typeof(T).IsAssignableFrom(resultType))
{
throw CreateWrongTypeException(resultType);
}
}
throw;
}
}
return result;
}
///
/// Creates the exception thrown when the reader returns a null value
/// for a non nullable property/column.
///
protected abstract Exception CreateNullValueException();
///
/// Creates the exception thrown when the reader returns a value with
/// an incompatible type.
///
protected abstract Exception CreateWrongTypeException(Type resultType);
}
private class ColumnErrorHandlingValueReader : ErrorHandlingValueReader
{
internal ColumnErrorHandlingValueReader()
{
}
protected override Exception CreateNullValueException()
{
return EntityUtil.ValueNullReferenceCast(typeof(TColumn));
}
protected override Exception CreateWrongTypeException(Type resultType)
{
return EntityUtil.ValueInvalidCast(resultType, typeof(TColumn));
}
}
private class PropertyErrorHandlingValueReader : ErrorHandlingValueReader
{
private readonly string _propertyName;
private readonly string _typeName;
internal PropertyErrorHandlingValueReader(string propertyName, string typeName)
{
_propertyName = propertyName;
_typeName = typeName;
}
protected override Exception CreateNullValueException()
{
return EntityUtil.Constraint(
System.Data.Entity.Strings.Materializer_SetInvalidValue(
(Nullable.GetUnderlyingType(typeof(TProperty)) ?? typeof(TProperty)).Name,
_typeName, _propertyName, "null"));
}
protected override Exception CreateWrongTypeException(Type resultType)
{
return EntityUtil.InvalidOperation(
System.Data.Entity.Strings.Materializer_SetInvalidValue(
(Nullable.GetUnderlyingType(typeof(TProperty)) ?? typeof(TProperty)).Name,
_typeName, _propertyName, resultType.Name));
}
}
#endregion
}
///
/// Typed Shaper. Includes logic to enumerate results and wraps the _rootCoordinator,
/// which includes materializer delegates for the root query collection.
///
internal sealed class Shaper : Shaper
{
#region private state
///
/// Shapers and Coordinators work together in harmony to materialize the data
/// from the store; the shaper contains the state, the coordinator contains the
/// code.
///
internal readonly Coordinator RootCoordinator;
///
/// What we need to call when we read to ensure we're maintaining security; will
/// do the approprate security demands.
///
private readonly Action CheckPermissionsAction;
///
/// Which type of query is this, object layer (true) or value layer (false)
///
private readonly bool IsObjectQuery;
///
/// Keeps track of whether we've completed processing or not.
///
private bool _isActive;
///
/// The enumerator we're using to read data; really only populated for value
/// layer queries.
///
private IEnumerator _rootEnumerator;
///
/// Whether the current value of _rootEnumerator has been returned by a bridge
/// data reader.
///
private bool _dataWaiting;
#endregion
#region constructor
internal Shaper(DbDataReader reader, ObjectContext context, MetadataWorkspace workspace, MergeOption mergeOption, int stateCount, CoordinatorFactory rootCoordinatorFactory, Action checkPermissions)
: base(reader, context, workspace, mergeOption, stateCount)
{
RootCoordinator = new Coordinator(rootCoordinatorFactory, /*parent*/ null, /*next*/ null);
CheckPermissionsAction = checkPermissions;
IsObjectQuery = !(typeof(T) == typeof(RecordState));
_isActive = true;
RootCoordinator.Initialize(this);
}
#endregion
#region "public" surface area
///
/// Events raised when the shaper has finished enumerating results. Useful for callback
/// to set parameter values.
///
internal event EventHandler OnDone;
///
/// Used to handle the read-ahead requirements of value-layer queries. This
/// field indicates the status of the current value of the _rootEnumerator; when
/// a bridge data reader "accepts responsibility" for the current value, it sets
/// this to false.
///
internal bool DataWaiting
{
get { return _dataWaiting; }
set { _dataWaiting = value; }
}
///
/// The enumerator that the value-layer bridge will use to read data; all nested
/// data readers need to use the same enumerator, so we put it on the Shaper, since
/// that is something that all the nested data readers (and data records) have access
/// to -- it prevents us from having to pass two objects around.
///
internal IEnumerator RootEnumerator
{
get
{
if (_rootEnumerator == null)
{
InitializeRecordStates(RootCoordinator.CoordinatorFactory);
_rootEnumerator = GetEnumerator();
}
return _rootEnumerator;
}
}
///
/// Initialize the RecordStateFactory objects in their StateSlots.
///
private void InitializeRecordStates(CoordinatorFactory coordinatorFactory)
{
foreach (RecordStateFactory recordStateFactory in coordinatorFactory.RecordStateFactories)
{
State[recordStateFactory.StateSlotNumber] = recordStateFactory.Create(coordinatorFactory);
}
foreach (CoordinatorFactory nestedCoordinatorFactory in coordinatorFactory.NestedCoordinators)
{
InitializeRecordStates(nestedCoordinatorFactory);
}
}
public IEnumerator GetEnumerator()
{
// we can use a simple enumerator if there are no nested results, no keys and no "has data"
// discriminator
if (RootCoordinator.CoordinatorFactory.IsSimple)
{
return new SimpleEnumerator(this);
}
else
{
RowNestedResultEnumerator rowEnumerator = new Shaper.RowNestedResultEnumerator(this);
if (this.IsObjectQuery)
{
return new ObjectQueryNestedEnumerator(rowEnumerator);
}
else
{
return (IEnumerator)(object)(new RecordStateEnumerator(rowEnumerator));
}
}
}
#endregion
#region enumerator helpers
///
/// Called when enumeration of results has completed.
///
private void Finally()
{
if (_isActive)
{
_isActive = false;
// I'd prefer not to special case this, but value-layer behavior is that you
// must explicitly close the data reader; if we automatically dispose of the
// reader here, we won't have that behavior.
if (IsObjectQuery)
{
this.Reader.Dispose();
}
// This case includes when the ObjectResult is disposed before it
// created an ObjectQueryEnumeration; at this time, the connection can be released
if (this.Context != null)
{
this.Context.ReleaseConnection();
}
if (null != this.OnDone)
{
this.OnDone(this, new EventArgs());
}
}
}
///
/// Reads the next row from the store. If there is a failure, throws an exception message
/// in some scenarios (note that we respond to failure rather than anticipate failure,
/// avoiding repeated checks in the inner materialization loop)
///
private bool StoreRead()
{
try
{
return this.Reader.Read();
}
catch (Exception e)
{
// check if the reader is closed; if so, throw friendlier exception
if (this.Reader.IsClosed)
{
const string operation = "Read";
throw EntityUtil.DataReaderClosed(operation);
}
// wrap exception if necessary
if (EntityUtil.IsCatchableEntityExceptionType(e))
{
throw EntityUtil.CommandExecution(System.Data.Entity.Strings.EntityClient_StoreReaderFailed, e);
}
throw;
}
}
#endregion
#region simple enumerator
///
/// Optimized enumerator for queries not including nested results.
///
private class SimpleEnumerator : IEnumerator
{
private readonly Shaper _shaper;
internal SimpleEnumerator(Shaper shaper)
{
_shaper = shaper;
}
public T Current
{
get { return _shaper.RootCoordinator.Current; }
}
object System.Collections.IEnumerator.Current
{
get { return _shaper.RootCoordinator.Current; }
}
public void Dispose()
{
// For backwards compatibility, we set the current value to the
// default value, so you can still call Current.
_shaper.RootCoordinator.SetCurrentToDefault();
_shaper.Finally();
}
public bool MoveNext()
{
if (!_shaper._isActive)
{
return false;
}
if (_shaper.StoreRead())
{
if (null != _shaper.CheckPermissionsAction)
{
_shaper.CheckPermissionsAction();
}
_shaper.RootCoordinator.ReadNextElement(_shaper);
return true;
}
this.Dispose();
return false;
}
public void Reset()
{
throw EntityUtil.NotSupported();
}
}
#endregion
#region nested enumerator
///
/// Enumerates (for each row in the input) an array of all coordinators producing new elements. The array
/// contains a position for each 'depth' in the result. A null value in any position indicates that no new
/// results were produced for the given row at the given depth. It is possible for a row to contain no
/// results for any row.
///
private class RowNestedResultEnumerator : IEnumerator
{
private readonly Shaper _shaper;
private readonly Coordinator[] _current;
internal RowNestedResultEnumerator(Shaper shaper)
{
_shaper = shaper;
_current = new Coordinator[_shaper.RootCoordinator.MaxDistanceToLeaf() + 1];
}
public Coordinator[] Current
{
get { return _current; }
}
public void Dispose()
{
_shaper.Finally();
}
object System.Collections.IEnumerator.Current
{
get { return _current; }
}
public bool MoveNext()
{
Coordinator currentCoordinator = _shaper.RootCoordinator;
if (!_shaper.StoreRead())
{
// Reset all collections
this.RootCoordinator.ResetCollection(_shaper);
return false;
}
int depth = 0;
bool haveInitializedChildren = false;
for (; depth < _current.Length; depth++)
{
// find a coordinator at this depth that currently has data (if any)
while (currentCoordinator != null && !currentCoordinator.CoordinatorFactory.HasData(_shaper))
{
currentCoordinator = currentCoordinator.Next;
}
if (null == currentCoordinator)
{
break;
}
// check if this row contains a new element for this coordinator
if (currentCoordinator.HasNextElement(_shaper))
{
// if we have children and haven't initialized them yet, do so now
if (!haveInitializedChildren && null != currentCoordinator.Child)
{
currentCoordinator.Child.ResetCollection(_shaper);
}
haveInitializedChildren = true;
// read the next element
currentCoordinator.ReadNextElement(_shaper);
// place the coordinator in the result array to indicate there is a new
// element at this depth
_current[depth] = currentCoordinator;
}
else
{
// clear out the coordinator in result array to indicate there is no new
// element at this depth
_current[depth] = null;
}
// move to child (in the next iteration we deal with depth + 1
currentCoordinator = currentCoordinator.Child;
}
// clear out all positions below the depth we reached before we ran out of data
for (; depth < _current.Length; depth++)
{
_current[depth] = null;
}
return true;
}
public void Reset()
{
throw EntityUtil.NotSupported();
}
internal Coordinator RootCoordinator
{
get { return _shaper.RootCoordinator; }
}
}
///
/// Wraps RowNestedResultEnumerator and yields results appropriate to an ObjectQuery instance. In particular,
/// root level elements (T) are returned only after aggregating all child elements.
///
private class ObjectQueryNestedEnumerator : IEnumerator
{
private readonly RowNestedResultEnumerator _rowEnumerator;
private T _previousElement;
private State _state;
internal ObjectQueryNestedEnumerator(RowNestedResultEnumerator rowEnumerator)
{
_rowEnumerator = rowEnumerator;
_previousElement = default(T);
_state = State.Start;
}
public T Current { get { return _previousElement; } }
public void Dispose()
{
_rowEnumerator.Dispose();
}
object System.Collections.IEnumerator.Current { get { return this.Current; } }
public bool MoveNext()
{
// See the documentation for enum State to understand the behaviors and requirements
// for each state.
switch (_state)
{
case State.Start:
{
if (TryReadToNextElement())
{
// if there's an element in the reader...
ReadElement();
}
else
{
// no data at all...
_state = State.NoRows;
}
};
break;
case State.Reading:
{
ReadElement();
};
break;
case State.NoRowsLastElementPending:
{
// nothing to do but move to the next state...
_state = State.NoRows;
};
break;
}
bool result;
if (_state == State.NoRows)
{
_previousElement = default(T);
result = false;
}
else
{
result = true;
}
return result;
}
///
/// Requires: the row is currently positioned at the start of an element.
///
/// Reads all rows in the element and sets up state for the next element (if any).
///
private void ReadElement()
{
// remember the element we're currently reading
_previousElement = _rowEnumerator.RootCoordinator.Current;
// now we need to read to the next element (or the end of the
// reader) so that we can return the first element
if (TryReadToNextElement())
{
// we're positioned at the start of the next element (which
// corresponds to the 'reading' state)
_state = State.Reading;
}
else
{
// we're positioned at the end of the reader
_state = State.NoRowsLastElementPending;
}
}
///
/// Reads rows until the start of a new element is found. If no element
/// is found before all rows are consumed, returns false.
///
private bool TryReadToNextElement()
{
bool result = false;
while (_rowEnumerator.MoveNext())
{
// if we hit a new element, return true
if (_rowEnumerator.Current[0] != null)
{
result = true;
break;
}
}
return result;
}
public void Reset()
{
_rowEnumerator.Reset();
}
///
/// Describes the state of this enumerator with respect to the _rowEnumerator
/// it wraps.
///
private enum State
{
///
/// No rows have been read yet
///
Start,
///
/// Positioned at the start of a new root element. The previous element must
/// be stored in _previousElement. We read ahead in this manner so that
/// the previous element is fully populated (all of its children loaded)
/// before returning.
///
Reading,
///
/// Positioned past the end of the rows. The last element in the enumeration
/// has not yet been returned to the user however, and is stored in _previousElement.
///
NoRowsLastElementPending,
///
/// Positioned past the end of the rows. The last element has been returned to
/// the user.
///
NoRows,
}
}
///
/// Wraps RowNestedResultEnumerator and yields results appropriate to an EntityReader instance. In particular,
/// yields RecordState whenever a new element becomes available at any depth in the result hierarchy.
///
private class RecordStateEnumerator : IEnumerator
{
private readonly RowNestedResultEnumerator _rowEnumerator;
private RecordState _current;
///
/// Gets depth of coordinator we're currently consuming. If _depth == -1, it means we haven't started
/// to consume the next row yet.
///
private int _depth;
private bool _readerConsumed;
internal RecordStateEnumerator(RowNestedResultEnumerator rowEnumerator)
{
_rowEnumerator = rowEnumerator;
_current = null;
_depth = -1;
_readerConsumed = false;
}
public RecordState Current
{
get { return _current; }
}
public void Dispose()
{
_rowEnumerator.Dispose();
}
object System.Collections.IEnumerator.Current
{
get { return _current; }
}
public bool MoveNext()
{
if (!_readerConsumed)
{
while (true)
{
// keep on cycling until we find a result
if (-1 == _depth || _rowEnumerator.Current.Length == _depth)
{
// time to move to the next row...
if (!_rowEnumerator.MoveNext())
{
// no more rows...
_current = null;
_readerConsumed = true;
break;
}
_depth = 0;
}
// check for results at the current depth
Coordinator currentCoordinator = _rowEnumerator.Current[_depth];
if (null != currentCoordinator)
{
_current = ((Coordinator)currentCoordinator).Current;
_depth++;
break;
}
_depth++;
}
}
return !_readerConsumed;
}
public void Reset()
{
_rowEnumerator.Reset();
}
}
#endregion
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.