//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Common;
using System.Data.EntityClient;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
using System.Xml.Linq;
namespace SchoolSample.EntityModel
{
public static class SelfTrackingEntitiesContextExtensions
{
/// <summary>
/// ApplyChanges takes the changes in a connected set of entities and applies them to an ObjectContext.
/// </summary>
/// <typeparam name="TEntity">Expected type of the ObjectSet</typeparam>
/// <param name="objectSet">The ObjectSet referencing the ObjectContext to which changes will be applied.</param>
/// <param name="entity">The entity serving as the entry point of the object graph that contains changes.</param>
public static void ApplyChanges<TEntity>(this ObjectSet<TEntity> objectSet, TEntity entity) where TEntity : class, IObjectWithChangeTracker
{
if (objectSet == null)
{
throw new ArgumentNullException("objectSet");
}
objectSet.Context.ApplyChanges<TEntity>(objectSet.EntitySet.EntityContainer.Name + "." + objectSet.EntitySet.Name, entity);
}
/// <summary>
/// ApplyChanges takes the changes in a connected set of entities and applies them to an ObjectContext.
/// </summary>
/// <typeparam name="TEntity">Expected type of the EntitySet</typeparam>
/// <param name="context">The ObjectContext to which changes will be applied.</param>
/// <param name="entitySetName">The EntitySet name of the entity.</param>
/// <param name="entity">The entity serving as the entry point of the object graph that contains changes.</param>
public static void ApplyChanges<TEntity>(this ObjectContext context, string entitySetName, TEntity entity) where TEntity : IObjectWithChangeTracker
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (String.IsNullOrEmpty(entitySetName))
{
throw new ArgumentException("String parameter cannot be null or empty.", "entitySetName");
}
if (entity == null)
{
throw new ArgumentNullException("entity");
}
bool lazyLoadingSetting = context.ContextOptions.LazyLoadingEnabled;
try
{
context.ContextOptions.LazyLoadingEnabled = false;
EntityIndex entityIndex = AddHelper.AddAllEntities(context, entitySetName, entity);
RelationshipSet allRelationships = new RelationshipSet(context, entityIndex.AllEntities);
#region Handle Initial Entity State
foreach (IObjectWithChangeTracker changedEntity in entityIndex.AllEntities.Where(x => x.ChangeTracker.State == ObjectState.Deleted))
{
HandleDeletedEntity(context, entityIndex, allRelationships, changedEntity);
}
foreach (IObjectWithChangeTracker changedEntity in entityIndex.AllEntities.Where(x => x.ChangeTracker.State != ObjectState.Deleted))
{
HandleEntity(context, entityIndex, allRelationships, changedEntity);
}
#endregion
#region Loop through each object state entries
foreach (IObjectWithChangeTracker changedEntity in entityIndex.AllEntities)
{
ObjectStateEntry entry = context.ObjectStateManager.GetObjectStateEntry(changedEntity);
EntityType entityType = context.MetadataWorkspace.GetCSpaceEntityType(changedEntity);
foreach (NavigationProperty navProp in entityType.NavigationProperties)
{
RelatedEnd relatedEnd = entry.GetRelatedEnd(navProp.Name);
if(!((AssociationType)relatedEnd.RelationshipSet.ElementType).IsForeignKey)
{
ApplyChangesToIndependentAssociation(context, (IObjectWithChangeTracker)changedEntity, entry, navProp, relatedEnd, allRelationships);
}
}
}
#endregion
// Change all the remaining relationships to the appropriate state
foreach (var relationship in allRelationships)
{
context.ObjectStateManager.ChangeRelationshipState(
relationship.End0,
relationship.End1,
relationship.AssociationSet.ElementType.FullName,
relationship.AssociationEndMembers[1].Name,
relationship.State);
}
}
finally
{
context.ContextOptions.LazyLoadingEnabled = lazyLoadingSetting;
}
}
private static void ApplyChangesToIndependentAssociation(ObjectContext context, IObjectWithChangeTracker changedEntity, ObjectStateEntry entry, NavigationProperty navProp,
IRelatedEnd relatedEnd, RelationshipSet allRelationships)
{
ObjectChangeTracker changeTracker = changedEntity.ChangeTracker;
if (changeTracker.State == ObjectState.Added)
{
// Relationships should remain added so remove them from the list of allRelationships
foreach (object relatedEntity in relatedEnd)
{
ObjectStateEntry addedRelationshipEntry =
context.ObjectStateManager.ChangeRelationshipState(
changedEntity,
relatedEntity,
navProp.Name,
EntityState.Added);
allRelationships.Remove(addedRelationshipEntry);
}
}
else
{
if (navProp.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
{
//Handle removal to FixupCollections
ObjectList collectionPropertyChanges = null;
if (changeTracker.ObjectsRemovedFromCollectionProperties.TryGetValue(navProp.Name, out collectionPropertyChanges))
{
foreach (var removedEntityFromAssociation in collectionPropertyChanges)
{
ObjectStateEntry deletedRelationshipEntry =
context.ObjectStateManager.ChangeRelationshipState(
changedEntity,
removedEntityFromAssociation,
navProp.Name,
EntityState.Deleted);
allRelationships.Remove(deletedRelationshipEntry);
}
}
//Handle addition to FixupCollection
if (changeTracker.ObjectsAddedToCollectionProperties.TryGetValue(navProp.Name, out collectionPropertyChanges))
{
foreach (var addedEntityFromAssociation in collectionPropertyChanges)
{
allRelationships.Remove(AddRelationshipUnlessExists(context, relatedEnd, entry, addedEntityFromAssociation, navProp.Name));
}
}
}
else
{
// Handle original relationship values
object originalReferenceValue;
if (changeTracker.OriginalValues.TryGetValue(navProp.Name, out originalReferenceValue))
{
if (originalReferenceValue != null)
{
//Capture the deletion of association
ObjectStateEntry deletedRelationshipEntry =
context.ObjectStateManager.ChangeRelationshipState(
entry.Entity,
originalReferenceValue,
navProp.Name,
EntityState.Deleted);
allRelationships.Remove(deletedRelationshipEntry);
}
//Capture the Addition of association
object currentReferenceValue = null;
foreach (object o in relatedEnd)
{
currentReferenceValue = o;
break;
}
if (currentReferenceValue != null)
{
allRelationships.Remove(AddRelationshipUnlessExists(context, relatedEnd, entry, currentReferenceValue, navProp.Name));
}
// if the current value of the reference is null, then the user must set the entity reference to null
// which is already being handled by the deletion of the relationship
}
}
}
}
// Creates an Added relationship entry unless the relationship already exists in the Unchanged state
private static ObjectStateEntry AddRelationshipUnlessExists(ObjectContext context, IRelatedEnd relatedEnd, ObjectStateEntry fromEntry, object toEntity, string navPropName)
{
var toEntry = context.ObjectStateManager.GetObjectStateEntry(toEntity);
var associationSet = ((AssociationSet)relatedEnd.RelationshipSet);
var fromEnd = associationSet.AssociationSetEnds[relatedEnd.SourceRoleName].CorrespondingAssociationEndMember;
var toEnd = associationSet.AssociationSetEnds[relatedEnd.TargetRoleName].CorrespondingAssociationEndMember;
ObjectStateEntry existingRelationship;
if (!context.TryGetObjectStateEntry(fromEntry.EntityKey, toEntry.EntityKey, associationSet, fromEnd, toEnd, out existingRelationship) ||
existingRelationship.State != EntityState.Unchanged)
{
return context.ObjectStateManager.ChangeRelationshipState(
fromEntry.Entity,
toEntity,
navPropName,
EntityState.Added);
}
return existingRelationship;
}
// Extracts the relationship key information from the ExtendedProperties and OriginalValues records of each ObjectChangeTracker
// This is done by:
// 1. Creating any existing relationship specified in the ExtendedProperties
// 2. Determine if there was a previous relationship, and if there was create a deleted relationship between the entity and the previous entity or key value
private static void HandleRelationshipKeys(ObjectContext context, EntityIndex entityIndex, RelationshipSet allRelationships, IObjectWithChangeTracker entity)
{
ObjectChangeTracker changeTracker = entity.ChangeTracker;
if (changeTracker.State == ObjectState.Unchanged ||
changeTracker.State == ObjectState.Modified ||
changeTracker.State == ObjectState.Deleted)
{
ObjectStateEntry entry = context.ObjectStateManager.GetObjectStateEntry(entity);
EntityType entityType = context.MetadataWorkspace.GetCSpaceEntityType(entity);
RelationshipManager relationshipManager = context.ObjectStateManager.GetRelationshipManager(entity);
foreach (var entityReference in EnumerateSaveReferences(relationshipManager))
{
AssociationSet associationSet = ((AssociationSet)entityReference.RelationshipSet);
AssociationEndMember fromEnd = associationSet.AssociationSetEnds[entityReference.SourceRoleName].CorrespondingAssociationEndMember;
AssociationEndMember toEnd = associationSet.AssociationSetEnds[entityReference.TargetRoleName].CorrespondingAssociationEndMember;
// Find if there is a NavigationProperty for this candidate
NavigationProperty navigationProperty = entityType.NavigationProperties.
SingleOrDefault(x => x.RelationshipType == associationSet.ElementType &&
x.FromEndMember == fromEnd &&
x.ToEndMember == toEnd);
// Only handle relationship keys in one of these cases
// 1. There is no navigation property
// 2. The navigation property has a null current reference value and there are no removes or adds
// 3. The navigation property has a current reference value, but there is no remove
EntityKey currentKey = GetSavedReferenceKey(entityIndex, entityReference, entity, navigationProperty, changeTracker.ExtendedProperties);
// Get any original value from the change tracking information
object originalValue = null;
EntityKey originalKey = null;
bool hasOriginalValue = false;
if (changeTracker.OriginalValues != null)
{
// Try to get the original value from the NavigationProperty first
if (navigationProperty != null)
{
hasOriginalValue = changeTracker.OriginalValues.TryGetValue(navigationProperty.Name, out originalValue);
}
// Try to get the original value from the reference key second
if (!hasOriginalValue || originalValue == null)
{
originalKey = GetSavedReferenceKey(entityIndex, entityReference, entity, navigationProperty, changeTracker.OriginalValues);
}
}
// Create the current relationship
if (currentKey != null)
{
// If the key is for a deleted entity, move that key to an originalValue and fixup the entities key values
// Otherwise create a new relationship
ObjectStateEntry currentEntry;
if (context.ObjectStateManager.TryGetObjectStateEntry(currentKey, out currentEntry) &&
currentEntry.Entity != null &&
currentEntry.State == EntityState.Deleted)
{
entityReference.EntityKey = null;
MoveSavedReferenceKey(entityReference, entity, navigationProperty, changeTracker.ExtendedProperties, changeTracker.OriginalValues);
originalKey = currentKey;
}
else
{
CreateRelationship(context, entityReference, entry.EntityKey, currentKey, originalKey == null ? EntityState.Unchanged : EntityState.Added);
}
}
else
{
// Find the current key
// Cannot get the EntityKey directly because this is null when it points to an Added entity
currentKey = entityReference.GetCurrentEntityKey(context);
}
// Create the original relationship
if (originalKey != null)
{
// If the key is for a deleted entity, remember to create a deleted relationship,
// otherwise use the entityReference to setup the deleted relationship
ObjectStateEntry originalEntry = null;
ObjectStateEntry deletedRelationshipEntry = null;
if (context.ObjectStateManager.TryGetObjectStateEntry(originalKey, out originalEntry) &&
originalEntry.Entity != null &&
originalEntry.State == EntityState.Deleted)
{
allRelationships.Add(entityReference, entry.Entity, originalEntry.Entity, EntityState.Deleted);
}
else
{
// To create a deleted relationship to a key, first detach the existing relationship between entry and currentKey
EntityState currentRelationshipState = DetachRelationship(context, entityReference, entry, currentKey);
// If the relationship is 1 to 0..1, detach the relationship from currentKey to its target (targetKey)
EntityState targetRelationshipState = EntityState.Detached;
EntityReference targetReference = null;
EntityKey targetKey = null;
if (originalEntry != null &&
originalEntry.Entity != null &&
originalEntry.RelationshipManager != null &&
associationSet.AssociationSetEnds[fromEnd.Name].CorrespondingAssociationEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many)
{
targetReference = originalEntry.RelationshipManager.GetRelatedEnd(entityReference.RelationshipName, entityReference.SourceRoleName) as EntityReference;
targetKey = targetReference.GetCurrentEntityKey(context);
if (targetKey != null)
{
targetRelationshipState = DetachRelationship(context, targetReference, originalEntry, targetKey);
}
}
// Create the deleted relationship between entry and originalKey
deletedRelationshipEntry = CreateRelationship(context, entityReference, entry.EntityKey, originalKey, EntityState.Deleted);
// Set the previous relationship between entry and currentKey back
CreateRelationship(context, entityReference, entry.EntityKey, currentKey, currentRelationshipState);
// Set the previous relationship between originalEntry and targetKey back
if (targetKey != null)
{
CreateRelationship(context, targetReference, originalEntry.EntityKey, targetKey, targetRelationshipState);
}
}
if (deletedRelationshipEntry != null)
{
// Remove the deleted relationship from those that need to be processed later in ApplyChanges
allRelationships.Remove(deletedRelationshipEntry);
}
}
else if (currentKey == null && originalValue != null && entityReference.IsDependentEndOfReferentialConstraint())
{
// the graph won't have this hooked up because there is no current value, but there is an original value,
// so the relationship processing code will want to delete a relationship.
// we can add this one so it has a relationship to change to deleted.
context.ObjectStateManager.ChangeRelationshipState(
entry.Entity,
originalValue,
entityReference.RelationshipName,
entityReference.TargetRoleName,
EntityState.Added);
}
}
}
}
private static ObjectStateEntry CreateRelationship(ObjectContext context, EntityReference entityReference, EntityKey fromKey, EntityKey toKey, EntityState state)
{
if (state != EntityState.Detached)
{
AssociationSet associationSet = ((AssociationSet)entityReference.RelationshipSet);
AssociationEndMember fromEnd = associationSet.AssociationSetEnds[entityReference.SourceRoleName].CorrespondingAssociationEndMember;
AssociationEndMember toEnd = associationSet.AssociationSetEnds[entityReference.TargetRoleName].CorrespondingAssociationEndMember;
// set the relationship to the original relationship in the unchanged state
Debug.Assert(toKey != null, "why/how would we do a delete with a null originalKey?");
if (toKey.IsTemporary)
{
// Clear any existing relationship
entityReference.EntityKey = null;
// If the target entity is Added, use Add on RelatedEnd
ObjectStateEntry targetEntry;
context.ObjectStateManager.TryGetObjectStateEntry(toKey, out targetEntry);
Debug.Assert(targetEntry != null, "Should have found the state entry");
((IRelatedEnd)entityReference).Add(targetEntry.Entity);
}
else
{
entityReference.EntityKey = toKey;
}
ObjectStateEntry relationshipEntry;
bool found = context.TryGetObjectStateEntry(fromKey, toKey, associationSet, fromEnd, toEnd, out relationshipEntry);
Debug.Assert(found, "Did not find the created relationship.");
switch (state)
{
case EntityState.Added:
break;
case EntityState.Unchanged:
relationshipEntry.AcceptChanges();
break;
case EntityState.Deleted:
relationshipEntry.AcceptChanges();
entityReference.EntityKey = null;
break;
}
return relationshipEntry;
}
return null;
}
private static EntityState DetachRelationship(ObjectContext context, EntityReference entityReference, ObjectStateEntry fromEntry, EntityKey toKey)
{
EntityState currentRelationshipState = EntityState.Detached;
if (toKey != null)
{
AssociationSet associationSet = ((AssociationSet)entityReference.RelationshipSet);
AssociationEndMember fromEnd = associationSet.AssociationSetEnds[entityReference.SourceRoleName].CorrespondingAssociationEndMember;
AssociationEndMember toEnd = associationSet.AssociationSetEnds[entityReference.TargetRoleName].CorrespondingAssociationEndMember;
ObjectStateEntry currentRelationshipEntry = null;
if (context.TryGetObjectStateEntry(fromEntry.EntityKey, toKey, associationSet, fromEnd, toEnd, out currentRelationshipEntry))
{
currentRelationshipState = currentRelationshipEntry.State;
entityReference.EntityKey = null;
if (currentRelationshipEntry.State == EntityState.Deleted)
{
currentRelationshipEntry.AcceptChanges();
}
Debug.Assert(currentRelationshipEntry.State == EntityState.Detached, "relationship was not detached");
}
}
return currentRelationshipState;
}
private static string CreateReferenceKeyLookup(string keyMemberName, EntityReference reference, NavigationProperty navigationProperty)
{
// use the more usable navigation property name to qualify the member
// if available
if (navigationProperty != null)
{
return String.Format(CultureInfo.InvariantCulture, "{0}.{1}", navigationProperty.Name, keyMemberName);
}
else
{
return String.Format(CultureInfo.InvariantCulture, "Navigate({0}.{1}).{2}", reference.RelationshipSet.ElementType.FullName, reference.TargetRoleName, keyMemberName);
}
}
// retrieves the key corresponding to the passed in EntityReference
// these keys can be set during the ObjectMaterialized event or through relationship fixup
private static EntityKey GetSavedReferenceKey(EntityIndex entityIndex, EntityReference reference, object entity, NavigationProperty navigationProperty, IDictionary<string, object> values)
{
Debug.Assert(navigationProperty == null || reference.RelationshipSet.ElementType == navigationProperty.RelationshipType, "the reference and navigationProperty should correspond");
EntitySet entitySet = ((AssociationSet)reference.RelationshipSet).AssociationSetEnds[reference.TargetRoleName].EntitySet;
List<EntityKeyMember> foundKeyMembers = new List<EntityKeyMember>(1);
bool foundNone = true;
bool missingSome = false;
foreach (var keyMember in entitySet.ElementType.KeyMembers)
{
string lookupKey = CreateReferenceKeyLookup(keyMember.Name, reference, navigationProperty);
object value;
if (values.TryGetValue(lookupKey, out value))
{
foundKeyMembers.Add(new EntityKeyMember(keyMember.Name, value));
foundNone = false;
}
else
{
missingSome = true;
}
}
if (foundNone)
{
// we didn't find a key
return null;
}
else if (missingSome)
{
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentCulture,
"The OriginalValues or ExtendedProperties collections on the type '{0}' contained only a partial key to satisfy the relationship '{1}' targeting the role '{2}'",
ObjectContext.GetObjectType(entity.GetType()).FullName,
reference.RelationshipName,
reference.TargetRoleName));
}
EntityKey key = entityIndex.ConvertEntityKey(new EntityKey(reference.GetEntitySetName(), foundKeyMembers));
return key;
}
// Moves the key corresponding to the passed in EntityReference from a source collection to a target collection
private static void MoveSavedReferenceKey(EntityReference reference, object entity, NavigationProperty navigationProperty, IDictionary<string, object> sourceValues, IDictionary<string, object> targetValues)
{
Debug.Assert(navigationProperty == null || reference.RelationshipSet.ElementType == navigationProperty.RelationshipType, " the reference and navigationProperty should correspond");
EntitySet entitySet = ((AssociationSet)reference.RelationshipSet).AssociationSetEnds[reference.TargetRoleName].EntitySet;
bool missingSome = false;
foreach (var keyMember in entitySet.ElementType.KeyMembers)
{
string lookupKey = CreateReferenceKeyLookup(keyMember.Name, reference, navigationProperty);
object value;
if (sourceValues.TryGetValue(lookupKey, out value))
{
if (targetValues.ContainsKey(lookupKey))
{
targetValues[lookupKey] = value;
}
else
{
targetValues.Add(lookupKey, value);
}
sourceValues.Remove(lookupKey);
}
else
{
missingSome = true;
}
}
if (missingSome)
{
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentCulture,
" The OriginalValues or ExtendedProperties collections on the type '{0}' contained only a partial key to satisfy the relationship '{1}' targeting the role '{2}'",
ObjectContext.GetObjectType(entity.GetType()).FullName,
reference.RelationshipName,
reference.TargetRoleName));
}
}
private static IEnumerable<EntityReference> EnumerateSaveReferences(RelationshipManager manager)
{
return manager.GetAllRelatedEnds().OfType<EntityReference>()
.Where(er => er.RelationshipSet.ElementType.RelationshipEndMembers[er.SourceRoleName].RelationshipMultiplicity != RelationshipMultiplicity.One &&
!((AssociationSet)er.RelationshipSet).ElementType.IsForeignKey);
}
internal static void StoreReferenceKeyValues(this ObjectContext context, IObjectWithChangeTracker entity)
{
if(entity == null)
{
throw new ArgumentNullException("entity");
}
ObjectStateEntry entry;
if (!context.ObjectStateManager.TryGetObjectStateEntry(entity, out entry))
{
// must be a no tracking query, the reference key info won't be available
return;
}
var relationshipManager = entry.RelationshipManager;
EntityType entityType = context.MetadataWorkspace.GetCSpaceEntityType(entity);
foreach (EntityReference entityReference in EnumerateSaveReferences(relationshipManager))
{
NavigationProperty navigationProperty = entityType.NavigationProperties.FirstOrDefault(n => n.RelationshipType == entityReference.RelationshipSet.ElementType &&
n.FromEndMember.Name == entityReference.SourceRoleName &&
n.ToEndMember.Name == entityReference.TargetRoleName);
object value = entityReference.GetValue();
if ((navigationProperty == null || value == null) && entityReference.EntityKey != null)
{
foreach (var item in entityReference.EntityKey.EntityKeyValues)
{
string key = CreateReferenceKeyLookup(item.Key, entityReference, navigationProperty);
entity.ChangeTracker.ExtendedProperties.Add(key, item.Value);
}
}
}
}
private static void HandleEntity(ObjectContext context, EntityIndex entityIndex, RelationshipSet allRelationships, IObjectWithChangeTracker entity)
{
ChangeEntityStateBasedOnObjectState(context, entity);
HandleRelationshipKeys(context, entityIndex, allRelationships, entity);
UpdateOriginalValues(context, entity);
}
private static void HandleDeletedEntity(ObjectContext context, EntityIndex entityIndex, RelationshipSet allRelationships, IObjectWithChangeTracker entity)
{
HandleRelationshipKeys(context, entityIndex, allRelationships, entity);
ChangeEntityStateBasedOnObjectState(context, entity);
UpdateOriginalValues(context, entity);
}
private static void UpdateOriginalValues(ObjectContext context, IObjectWithChangeTracker entity)
{
if (entity.ChangeTracker.State == ObjectState.Unchanged ||
entity.ChangeTracker.State == ObjectState.Added ||
entity.ChangeTracker.OriginalValues == null)
{
// nothing to do here
return;
}
// we only need/want to deal with scalar and complex properties
ObjectStateEntry entry = context.ObjectStateManager.GetObjectStateEntry(entity);
OriginalValueRecord originalValueRecord = entry.GetUpdatableOriginalValues();
EntityType entityType = context.MetadataWorkspace.GetCSpaceEntityType(entity);
// walk through each property and see if we have an original value for it
// set it if we do. Walk down through ComplexType properties to set original values
// for each of them also
//
// it is expected that the original values will be sparse because we are trying
// to only capture originals for the ones we are required to have (concurrency, sproc, condition, more?)
foreach(EdmProperty property in entityType.Properties)
{
object value;
if(property.TypeUsage.EdmType is SimpleType && entity.ChangeTracker.OriginalValues.TryGetValue(property.Name, out value))
{
originalValueRecord.SetValue(property, value);
}
else if(property.TypeUsage.EdmType is ComplexType)
{
OriginalValueRecord complexOriginalValues = originalValueRecord.GetOriginalValueRecord(property.Name);
UpdateOriginalValues((ComplexType)property.TypeUsage.EdmType, ObjectContext.GetObjectType(entity.GetType()).FullName, property.Name, entity.ChangeTracker.OriginalValues, complexOriginalValues);
}
}
}
private static void UpdateOriginalValues(ComplexType complexType, string entityTypeName, string propertyPathToType, IDictionary<string, object> originalValueSource, OriginalValueRecord complexOriginalValueRecord)
{
// Note that complexOriginalValueRecord may be null
// a null complexOriginalValueRecord will only occur if a null reference is assigned
// to a ComplexType property and then given to ApplyChanges.
//
// walk through each property and see if we have an original value for it
// set it if we do. Walk down through ComplexType properties to set original values
// for each of them also
foreach (EdmProperty property in complexType.Properties)
{
object value;
string propertyPath = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", propertyPathToType, property.Name);
if (property.TypeUsage.EdmType is SimpleType && originalValueSource.TryGetValue(propertyPath, out value))
{
if (complexOriginalValueRecord != null)
{
complexOriginalValueRecord.SetValue(property, value);
}
else if (value != null)
{
Debug.Assert(complexOriginalValueRecord == null, "we only throw when the value is not null and the record is null");
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentCulture,
"Can not set the original value on the object stored in the property '{0}' on the type '{1}' because the property is null.",
propertyPathToType,
entityTypeName));
}
}
else if (property.TypeUsage.EdmType is ComplexType)
{
OriginalValueRecord nestedOriginalValueRecord = null;
if (complexOriginalValueRecord != null)
{
nestedOriginalValueRecord = complexOriginalValueRecord.GetOriginalValueRecord(property.Name);
}
// recurse down the chain of complex types
UpdateOriginalValues((ComplexType)property.TypeUsage.EdmType, entityTypeName, propertyPath, originalValueSource, nestedOriginalValueRecord);
}
}
}
private static OriginalValueRecord GetOriginalValueRecord(this OriginalValueRecord record, string name)
{
int ordinal = record.GetOrdinal(name);
if (!record.IsDBNull(ordinal))
{
return record.GetDataRecord(ordinal) as OriginalValueRecord;
}
else
{
return null;
}
}
private static void SetValue(this OriginalValueRecord record, EdmProperty edmProperty, object value)
{
PrimitiveType primitiveType = edmProperty.TypeUsage.EdmType as PrimitiveType;
if (value == null && primitiveType != null)
{
Type entityClrType = primitiveType.ClrEquivalentType;
if (entityClrType.IsValueType && !edmProperty.Nullable)
{
// Skip setting null original values on non-nullable CLR types because the ObjectStateEntry won't allow this
return;
}
}
int ordinal = record.GetOrdinal(edmProperty.Name);
record.SetValue(ordinal, value);
}
private static void ChangeEntityStateBasedOnObjectState(ObjectContext context, IObjectWithChangeTracker entity)
{
switch (entity.ChangeTracker.State)
{
case (ObjectState.Added):
// No-op: the state entry is already marked as added
Debug.Assert(context.ObjectStateManager.GetObjectStateEntry(entity).State == EntityState.Added, "State should have been Added");
break;
case (ObjectState.Unchanged):
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Unchanged);
break;
case (ObjectState.Modified):
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
break;
case (ObjectState.Deleted):
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Deleted);
break;
}
}
private static EntityType GetCSpaceEntityType(this MetadataWorkspace workspace, object entity)
{
Type type = ObjectContext.GetObjectType(entity.GetType());
EntityType ospaceEntityType = null;
StructuralType cspaceEntityType = null;
EntityType entityType = null;
if (workspace.TryGetItem<EntityType>(
type.FullName,
DataSpace.OSpace,
out ospaceEntityType))
{
if (workspace.TryGetEdmSpaceType(
ospaceEntityType,
out cspaceEntityType))
{
entityType = cspaceEntityType as EntityType;
}
}
if(entityType == null)
{
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "Unable to find a CSpace type for type {0}", type.FullName));
}
return entityType;
}
private static object GetValue(this System.Data.Objects.DataClasses.EntityReference entityReference)
{
foreach (object value in entityReference)
{
return value;
}
return null;
}
private static EntityKey GetCurrentEntityKey(this System.Data.Objects.DataClasses.EntityReference entityReference, ObjectContext context)
{
EntityKey currentKey = null;
object currentValue = entityReference.GetValue();
if (currentValue != null)
{
ObjectStateEntry relatedEntry = context.ObjectStateManager.GetObjectStateEntry(currentValue);
currentKey = relatedEntry.EntityKey;
}
else
{
currentKey = entityReference.EntityKey;
}
return currentKey;
}
private static RelatedEnd GetRelatedEnd(this ObjectStateEntry entry, string navigationPropertyIdentity)
{
NavigationProperty navigationProperty =
GetNavigationProperty(entry.ObjectStateManager.MetadataWorkspace.GetCSpaceEntityType(entry.Entity), navigationPropertyIdentity);
return entry.RelationshipManager.GetRelatedEnd(
navigationProperty.RelationshipType.FullName, navigationProperty.ToEndMember.Name) as RelatedEnd;
}
private static NavigationProperty GetNavigationProperty(this EntityType entityType, string navigationPropertyIdentity)
{
NavigationProperty navigationProperty;
if (!entityType.NavigationProperties.TryGetValue(navigationPropertyIdentity, false, out navigationProperty))
{
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentCulture,
"Could not find navigation property '{0}' in EntityType '{1}'.",
navigationPropertyIdentity,
entityType.FullName));
}
return navigationProperty;
}
private static string GetEntitySetName(this RelatedEnd relatedEnd)
{
EntitySet entitySet = ((AssociationSet)relatedEnd.RelationshipSet).AssociationSetEnds[relatedEnd.TargetRoleName].EntitySet;
return entitySet.EntityContainer.Name + "." + entitySet.Name;
}
private static bool IsDependentEndOfReferentialConstraint(this RelatedEnd relatedEnd)
{
if (null != relatedEnd.RelationshipSet)
{
// NOTE Referential constraints collection will usually contains 0 or 1 element,
// so performance shouldn't be an issue here
foreach (ReferentialConstraint constraint in ((AssociationType)relatedEnd.RelationshipSet.ElementType).ReferentialConstraints)
{
if (constraint.ToRole.Name == relatedEnd.SourceRoleName)
{
// Example:
// Client<C_ID> --- Order<O_ID, Client_ID>
// RI Constraint: Principal/From <Client.C_ID>, Dependent/To <Order.Client_ID>
// When current RelatedEnd is a CollectionOrReference in Order's relationships,
// constarint.ToRole == this._fromEndProperty == Order
return true;
}
}
}
return false;
}
private static bool TryGetObjectStateEntry(this ObjectContext context, EntityKey from, EntityKey to, AssociationSet associationSet, AssociationEndMember fromEnd, AssociationEndMember toEnd, out ObjectStateEntry entry)
{
entry = null;
foreach (var relationshipEntry in (from e in context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Unchanged)
where e.IsRelationship && e.EntitySet == associationSet
select e))
{
CurrentValueRecord currentValues = relationshipEntry.CurrentValues;
int fromOrdinal = currentValues.GetOrdinal(fromEnd.Name);
int toOrdinal = currentValues.GetOrdinal(toEnd.Name);
if (((EntityKey)currentValues.GetValue(fromOrdinal)) == from &&
((EntityKey)currentValues.GetValue(toOrdinal)) == to)
{
entry = relationshipEntry;
return true;
}
}
return false;
}
private sealed class AddHelper
{
private readonly ObjectContext _context;
private readonly EntityIndex _entityIndex;
// Used during add processing
private readonly Queue<Tuple<string, IObjectWithChangeTracker>> _entitiesToAdd;
private readonly Queue<Tuple<ObjectStateEntry, string, IEnumerable<object>>> _entitiesDuringAdd;
public static EntityIndex AddAllEntities(ObjectContext context, string entitySetName, IObjectWithChangeTracker entity)
{
AddHelper addHelper = new AddHelper(context);
try
{
// Include the root element to start the Apply
addHelper.QueueAdd(entitySetName, entity);
// Add everything
while (addHelper.HasMore)
{
Tuple<string, IObjectWithChangeTracker> entityInSet = addHelper.NextAdd();
// Only add the object if it's not already in the context
ObjectStateEntry entry = null;
if (!context.ObjectStateManager.TryGetObjectStateEntry(entityInSet.Item2, out entry))
{
context.AddObject(entityInSet.Item1, entityInSet.Item2);
}
}
}
finally
{
addHelper.Detach();
}
return addHelper.EntityIndex;
}
private AddHelper(ObjectContext context)
{
_context = context;
_context.ObjectStateManager.ObjectStateManagerChanged += this.HandleStateManagerChange;
_entityIndex = new EntityIndex(context);
_entitiesToAdd = new Queue<Tuple<string, IObjectWithChangeTracker>>();
_entitiesDuringAdd = new Queue<Tuple<ObjectStateEntry, string, IEnumerable<object>>>();
}
private void Detach()
{
_context.ObjectStateManager.ObjectStateManagerChanged -= this.HandleStateManagerChange;
}
private void HandleStateManagerChange(object sender, CollectionChangeEventArgs args)
{
if (args.Action == CollectionChangeAction.Add)
{
IObjectWithChangeTracker entity = args.Element as IObjectWithChangeTracker;
ObjectStateEntry entry = _context.ObjectStateManager.GetObjectStateEntry(entity);
ObjectChangeTracker changeTracker = entity.ChangeTracker;
changeTracker.ChangeTrackingEnabled = false;
_entityIndex.Add(entry, changeTracker);
// Queue removed reference values
var navPropNames = _context.MetadataWorkspace.GetCSpaceEntityType(entity).NavigationProperties.Select(n => n.Name);
var entityRefOriginalValues = changeTracker.OriginalValues.Where(kvp => navPropNames.Contains(kvp.Key));
foreach (KeyValuePair<string, object> originalValueWithName in entityRefOriginalValues)
{
if (originalValueWithName.Value != null)
{
_entitiesDuringAdd.Enqueue(new Tuple<ObjectStateEntry, string, IEnumerable<object>>(
entry,
originalValueWithName.Key,
new object[] { originalValueWithName.Value }));
}
}
// Queue removed collection values
foreach (KeyValuePair<string, ObjectList> collectionPropertyChangesWithName in changeTracker.ObjectsRemovedFromCollectionProperties)
{
_entitiesDuringAdd.Enqueue(new Tuple<ObjectStateEntry, string, IEnumerable<object>>(
entry,
collectionPropertyChangesWithName.Key,
collectionPropertyChangesWithName.Value));
}
}
}
private EntityIndex EntityIndex
{
get { return _entityIndex; }
}
private bool HasMore
{
get { ProcessNewAdds(); return _entitiesToAdd.Count > 0; }
}
private void QueueAdd(string entitySetName, IObjectWithChangeTracker entity)
{
if (!_entityIndex.Contains(entity))
{
// Queue the entity so that we can add the 'removed collection' items
_entitiesToAdd.Enqueue(new Tuple<string, IObjectWithChangeTracker>(entitySetName, entity));
}
}
private Tuple<string, IObjectWithChangeTracker> NextAdd()
{
ProcessNewAdds();
return _entitiesToAdd.Dequeue();
}
private void ProcessNewAdds()
{
while (_entitiesDuringAdd.Count > 0)
{
Tuple<ObjectStateEntry, string, IEnumerable<object>> relatedEntities = _entitiesDuringAdd.Dequeue();
RelatedEnd relatedEnd = relatedEntities.Item1.GetRelatedEnd(relatedEntities.Item2);
string entitySetName = relatedEnd.GetEntitySetName();
foreach (var targetEntity in relatedEntities.Item3)
{
QueueAdd(entitySetName, targetEntity as IObjectWithChangeTracker);
}
}
}
}
private sealed class EntityIndex
{
private readonly ObjectContext _context;
// Set of all entities
private readonly HashSet<IObjectWithChangeTracker> _allEntities;
// Index of the final key that will be used in the context (could be real for non-added, could be temporary for added)
// to the initial temporary key
private readonly Dictionary<EntityKey, EntityKey> _temporaryKeyMap;
public EntityIndex(ObjectContext context)
{
_context = context;
_allEntities = new HashSet<IObjectWithChangeTracker>();
_temporaryKeyMap = new Dictionary<EntityKey, EntityKey>();
}
public void Add(ObjectStateEntry entry, ObjectChangeTracker changeTracker)
{
EntityKey temporaryKey = entry.EntityKey;
EntityKey finalKey;
if (!_allEntities.Contains(entry.Entity))
{
// Track that this Apply will be handling this entity
_allEntities.Add(entry.Entity as IObjectWithChangeTracker);
}
if (changeTracker.State == ObjectState.Added)
{
finalKey = temporaryKey;
}
else
{
finalKey = _context.CreateEntityKey(temporaryKey.EntityContainerName + "." + temporaryKey.EntitySetName, entry.Entity);
}
if (!_temporaryKeyMap.ContainsKey(finalKey))
{
_temporaryKeyMap.Add(finalKey, temporaryKey);
}
}
public bool Contains(object entity)
{
return _allEntities.Contains(entity);
}
public IEnumerable<IObjectWithChangeTracker> AllEntities
{
get { return _allEntities; }
}
// Converts the passed in EntityKey to the EntityKey that is usable by the current state of ApplyChanges
public EntityKey ConvertEntityKey(EntityKey targetKey)
{
ObjectStateEntry targetEntry;
if (!_context.ObjectStateManager.TryGetObjectStateEntry(targetKey, out targetEntry))
{
// If no entry exists, then either:
// 1. This is an EntityKey that is not represented in the set of entities being dealt with during the Apply
// 2. This is an EntityKey that will represent one of the yet-to-be-processed Added entries, so look it up
EntityKey temporaryKey;
if (_temporaryKeyMap.TryGetValue(targetKey, out temporaryKey))
{
targetKey = temporaryKey;
}
}
return targetKey;
}
}
// The RelationshipSet builds a list of all relationships from an
// initial set of entities
private sealed class RelationshipSet : IEnumerable<RelationshipWrapper>
{
private readonly HashSet<RelationshipWrapper> _relationships;
private readonly ObjectContext _context;
public RelationshipSet(ObjectContext context, IEnumerable<object> allEntities)
{
_context = context;
_relationships = new HashSet<RelationshipWrapper>();
foreach (object entity in allEntities)
{
ObjectStateEntry entry = context.ObjectStateManager.GetObjectStateEntry(entity);
foreach (IRelatedEnd relatedEnd in entry.RelationshipManager.GetAllRelatedEnds())
{
if (!((AssociationType)relatedEnd.RelationshipSet.ElementType).IsForeignKey)
{
foreach (object targetEntity in relatedEnd)
{
Add(relatedEnd, entity, targetEntity, EntityState.Unchanged);
}
}
}
}
}
// Adds an entry to the index based on a IRelatedEnd
public void Add(IRelatedEnd relatedEnd, object sourceEntity, object targetEntity, EntityState state)
{
RelationshipWrapper wrapper = new RelationshipWrapper(
(AssociationSet)relatedEnd.RelationshipSet,
relatedEnd.SourceRoleName,
sourceEntity,
relatedEnd.TargetRoleName,
targetEntity,
state);
if (!_relationships.Contains(wrapper))
{
_relationships.Add(wrapper);
}
}
// Removes an entry from the index based on a relationship ObjectStateEntry
public void Remove(ObjectStateEntry relationshipEntry)
{
Debug.Assert(relationshipEntry.IsRelationship);
AssociationSet associationSet = (AssociationSet)relationshipEntry.EntitySet;
DbDataRecord values = relationshipEntry.State == EntityState.Deleted ? relationshipEntry.OriginalValues : relationshipEntry.CurrentValues;
int fromOridinal = values.GetOrdinal(associationSet.ElementType.AssociationEndMembers[0].Name);
object fromEntity = _context.ObjectStateManager.GetObjectStateEntry((EntityKey)values.GetValue(fromOridinal)).Entity;
int toOridinal = values.GetOrdinal(associationSet.ElementType.AssociationEndMembers[1].Name);
object toEntity = _context.ObjectStateManager.GetObjectStateEntry((EntityKey)values.GetValue(toOridinal)).Entity;
if (fromEntity != null && toEntity != null)
{
RelationshipWrapper wrapper = new RelationshipWrapper(
associationSet,
associationSet.ElementType.AssociationEndMembers[0].Name,
fromEntity,
associationSet.ElementType.AssociationEndMembers[1].Name,
toEntity,
EntityState.Unchanged);
_relationships.Remove(wrapper);
}
}
#region IEnumerable<RelationshipWrapper>
public IEnumerator<RelationshipWrapper> GetEnumerator()
{
return _relationships.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _relationships.GetEnumerator();
}
#endregion
}
// A RelationshipWrapper is used to identify a relationship between two entities
// The relationship is identified by the AssociationSet, and the order of the entities based
// on the roles they play (via AssociationEndMember)
private sealed class RelationshipWrapper : IEquatable<RelationshipWrapper>
{
internal readonly AssociationSet AssociationSet;
internal readonly object End0;
internal readonly object End1;
internal readonly EntityState State;
internal RelationshipWrapper(AssociationSet extent,
string role0, object end0,
string role1, object end1,
EntityState state)
{
Debug.Assert(null != extent, "null AssociationSet");
Debug.Assert(null != (object)end0, "null end0");
Debug.Assert(null != (object)end1, "null end1");
AssociationSet = extent;
Debug.Assert(extent.ElementType.AssociationEndMembers.Count == 2, "only 2 ends are supported");
State = state;
if (extent.ElementType.AssociationEndMembers[0].Name == role0)
{
Debug.Assert(extent.ElementType.AssociationEndMembers[1].Name == role1, "a)roleAndKey1 Name differs");
End0 = end0;
End1 = end1;
}
else
{
Debug.Assert(extent.ElementType.AssociationEndMembers[0].Name == role1, "b)roleAndKey1 Name differs");
Debug.Assert(extent.ElementType.AssociationEndMembers[1].Name == role0, "b)roleAndKey0 Name differs");
End0 = end1;
End1 = end0;
}
}
internal ReadOnlyMetadataCollection<AssociationEndMember> AssociationEndMembers
{
get { return this.AssociationSet.ElementType.AssociationEndMembers; }
}
public override int GetHashCode()
{
return this.AssociationSet.Name.GetHashCode() ^ (this.End0.GetHashCode() + this.End1.GetHashCode());
}
public override bool Equals(object obj)
{
return Equals(obj as RelationshipWrapper);
}
public bool Equals(RelationshipWrapper wrapper)
{
return (Object.ReferenceEquals(this, wrapper) ||
((null != wrapper) &&
Object.ReferenceEquals(this.AssociationSet, wrapper.AssociationSet) &&
Object.ReferenceEquals(this.End0, wrapper.End0) &&
Object.ReferenceEquals(this.End1, wrapper.End1)));
}
}
}
}