Click here to Skip to main content
15,878,959 members
Articles / Desktop Programming / WPF

Building WPF Applications with Self-Tracking Entity Generator and Visual Studio 2012 - Project Setup

Rate me:
Please Sign up or sign in to vote.
5.00/5 (14 votes)
17 Mar 2013CPOL8 min read 68.3K   3.5K   44  
This article describes the project setup of building a WPF sample application with Self-Tracking Entity Generator and Visual Studio 2012.
//------------------------------------------------------------------------------
// <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;
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&lt;C_ID&gt; --- Order&lt;O_ID, Client_ID&gt;
                        //    RI Constraint: Principal/From &lt;Client.C_ID&gt;,  Dependent/To &lt;Order.Client_ID&gt;
                        // 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)));
            }
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Weidong has been an information system professional since 1990. He has a Master's degree in Computer Science, and is currently a MCSD .NET

Comments and Discussions