File: System\Data\Mapping\Update\Internal\UpdateTranslator.cs
Project: ndp\fx\src\DataEntity\System.Data.Entity.csproj (System.Data.Entity)
//---------------------------------------------------------------------
// <copyright file="UpdateTranslator.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
 
using System.Collections.Generic;
using System.Data.Objects;
using System.Data.Common.Utils;
using System.Data.Common.CommandTrees;
using System.Data.Common;
using System.Threading;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Data.Metadata.Edm;
using System.Data.EntityClient;
using System.Data.Spatial;
using System.Globalization;
using System.Data.Entity;
using System.Linq;
 
namespace System.Data.Mapping.Update.Internal
{
    /// <summary>
    /// This class performs to following tasks to persist C-Space changes to the store:
    /// <list>
    /// <item>Extract changes from the entity state manager</item>
    /// <item>Group changes by C-Space extent</item>
    /// <item>For each affected S-Space table, perform propagation (get changes in S-Space terms)</item>
    /// <item>Merge S-Space inserts and deletes into updates where appropriate</item>
    /// <item>Produce S-Space commands implementating the modifications (insert, delete and update SQL statements)</item>
    /// </list>
    /// </summary>
    internal partial class UpdateTranslator
    {
        #region Constructors
        /// <summary>
        /// Constructs a grouper based on the contents of the given entity state manager.
        /// </summary>
        /// <param name="stateManager">Entity state manager containing changes to be processed.</param>
        /// <param name="metadataWorkspace">Metadata workspace.</param>
        /// <param name="connection">Map connection</param>
        /// <param name="commandTimeout">Timeout for update commands; null means 'use provider default'</param>
        private UpdateTranslator(IEntityStateManager stateManager, MetadataWorkspace metadataWorkspace, EntityConnection connection, int? commandTimeout)
        {
            EntityUtil.CheckArgumentNull(stateManager, "stateManager");
            EntityUtil.CheckArgumentNull(metadataWorkspace, "metadataWorkspace");
            EntityUtil.CheckArgumentNull(connection, "connection");
 
            // propagation state
            m_changes = new Dictionary<EntitySetBase, ChangeNode>();
            m_functionChanges = new Dictionary<EntitySetBase, List<ExtractedStateEntry>>();
            m_stateEntries = new List<IEntityStateEntry>();
            m_knownEntityKeys = new Set<EntityKey>();
            m_requiredEntities = new Dictionary<EntityKey, AssociationSet>();
            m_optionalEntities = new Set<EntityKey>();
            m_includedValueEntities = new Set<EntityKey>();
 
            // workspace state
            m_metadataWorkspace = metadataWorkspace;
            m_viewLoader = metadataWorkspace.GetUpdateViewLoader();
            m_stateManager = stateManager;
 
            // ancillary propagation services
            m_recordConverter = new RecordConverter(this);
            m_constraintValidator = new RelationshipConstraintValidator(this);
 
            m_providerServices = DbProviderServices.GetProviderServices(connection.StoreProviderFactory);
            m_connection = connection;
            m_commandTimeout = commandTimeout;
 
            // metadata cache
            m_extractorMetadata = new Dictionary<Tuple<EntitySetBase, StructuralType>, ExtractorMetadata>(); ;
 
            // key management
            KeyManager = new KeyManager(this);
            KeyComparer = CompositeKey.CreateComparer(KeyManager);
        }
 
        #endregion
 
        #region Fields
        // propagation state
        private readonly Dictionary<EntitySetBase, ChangeNode> m_changes;
        private readonly Dictionary<EntitySetBase, List<ExtractedStateEntry>> m_functionChanges;
        private readonly List<IEntityStateEntry> m_stateEntries;
        private readonly Set<EntityKey> m_knownEntityKeys;
        private readonly Dictionary<EntityKey, AssociationSet> m_requiredEntities;
        private readonly Set<EntityKey> m_optionalEntities;
        private readonly Set<EntityKey> m_includedValueEntities;
 
        // workspace state
        private readonly MetadataWorkspace m_metadataWorkspace;
        private readonly ViewLoader m_viewLoader;
        private readonly IEntityStateManager m_stateManager;
 
        // ancillary propagation services
        private readonly RecordConverter m_recordConverter;
        private readonly RelationshipConstraintValidator m_constraintValidator;
 
        // provider information
        private readonly DbProviderServices m_providerServices;
        private readonly EntityConnection m_connection;
        private readonly int? m_commandTimeout;
        private Dictionary<StorageModificationFunctionMapping, DbCommandDefinition> m_modificationFunctionCommandDefinitions;
 
        // metadata cache
        private readonly Dictionary<Tuple<EntitySetBase, StructuralType>, ExtractorMetadata> m_extractorMetadata;
 
        // static members
        private static readonly List<string> s_emptyMemberList = new List<string>();
        #endregion
 
        #region Properties
        /// <summary>
        /// Gets workspace used in this session.
        /// </summary>
        internal MetadataWorkspace MetadataWorkspace
        {
            get { return m_metadataWorkspace; }
        }
 
        /// <summary>
        /// Gets key manager that handles interpretation of keys (including resolution of 
        /// referential-integrity/foreign key constraints)
        /// </summary>
        internal readonly KeyManager KeyManager;
        
        /// <summary>
        /// Gets the view loader metadata wrapper for the current workspace.
        /// </summary>
        internal ViewLoader ViewLoader
        {
            get { return m_viewLoader; }
        }
 
        /// <summary>
        /// Gets record converter which translates state entry records into propagator results.
        /// </summary>
        internal RecordConverter RecordConverter
        {
            get { return m_recordConverter; }
        }
 
        /// <summary>
        /// Gets command timeout for update commands. If null, use default.
        /// </summary>
        internal int? CommandTimeout
        {
            get { return m_commandTimeout; }
        }
 
        internal readonly IEqualityComparer<CompositeKey> KeyComparer;
        #endregion
 
        #region Methods
        /// <summary>
        /// Registers any referential constraints contained in the state entry (so that
        /// constrained members have the same identifier values). Only processes relationships
        /// with referential constraints defined.
        /// </summary>
        /// <param name="stateEntry">State entry</param>
        internal void RegisterReferentialConstraints(IEntityStateEntry stateEntry)
        {
            if (stateEntry.IsRelationship)
            {
                AssociationSet associationSet = (AssociationSet)stateEntry.EntitySet;
                if (0 < associationSet.ElementType.ReferentialConstraints.Count)
                {
                    DbDataRecord record = stateEntry.State == EntityState.Added ?
                        (DbDataRecord)stateEntry.CurrentValues : stateEntry.OriginalValues;
                    foreach (ReferentialConstraint constraint in associationSet.ElementType.ReferentialConstraints)
                    {
                        // retrieve keys at the ends
                        EntityKey principalKey = (EntityKey)record[constraint.FromRole.Name];
                        EntityKey dependentKey = (EntityKey)record[constraint.ToRole.Name];
 
                        // associate keys, where the from side 'owns' the to side
                        using (ReadOnlyMetadataCollection<EdmProperty>.Enumerator principalPropertyEnum = constraint.FromProperties.GetEnumerator())
                        using (ReadOnlyMetadataCollection<EdmProperty>.Enumerator dependentPropertyEnum = constraint.ToProperties.GetEnumerator())
                        {
                            while (principalPropertyEnum.MoveNext() && dependentPropertyEnum.MoveNext())
                            {
                                int principalKeyMemberCount;
                                int dependentKeyMemberCount;
 
                                // get offsets for from and to key properties
                                int principalOffset = GetKeyMemberOffset(constraint.FromRole, principalPropertyEnum.Current,
                                    out principalKeyMemberCount);
                                int dependentOffset = GetKeyMemberOffset(constraint.ToRole, dependentPropertyEnum.Current,
                                    out dependentKeyMemberCount);
 
                                int principalIdentifier = this.KeyManager.GetKeyIdentifierForMemberOffset(principalKey, principalOffset, principalKeyMemberCount);
                                int dependentIdentifier = this.KeyManager.GetKeyIdentifierForMemberOffset(dependentKey, dependentOffset, dependentKeyMemberCount);
 
                                // register equivalence of identifiers
                                this.KeyManager.AddReferentialConstraint(stateEntry, dependentIdentifier, principalIdentifier);
                            }
                        }
                    }
                }
            }
            else if (!stateEntry.IsKeyEntry)
            {
                if (stateEntry.State == EntityState.Added || stateEntry.State == EntityState.Modified)
                {
                    RegisterEntityReferentialConstraints(stateEntry, true);
                }
                if (stateEntry.State == EntityState.Deleted || stateEntry.State == EntityState.Modified)
                {
                    RegisterEntityReferentialConstraints(stateEntry, false);
                }
            }
        }
 
        private void RegisterEntityReferentialConstraints(IEntityStateEntry stateEntry, bool currentValues)
        {
            IExtendedDataRecord record = currentValues
                ? (IExtendedDataRecord)stateEntry.CurrentValues
                : (IExtendedDataRecord)stateEntry.OriginalValues;
            EntitySet entitySet = (EntitySet)stateEntry.EntitySet;
            EntityKey dependentKey = stateEntry.EntityKey;
 
            foreach (var foreignKey in entitySet.ForeignKeyDependents)
            {
                AssociationSet associationSet = foreignKey.Item1;
                ReferentialConstraint constraint = foreignKey.Item2;
                EntityType dependentType = MetadataHelper.GetEntityTypeForEnd((AssociationEndMember)constraint.ToRole);
                if (dependentType.IsAssignableFrom(record.DataRecordInfo.RecordType.EdmType))
                {
                    EntityKey principalKey = null;
 
                    // First, check for an explicit reference
                    if (!currentValues || !m_stateManager.TryGetReferenceKey(dependentKey, (AssociationEndMember)constraint.FromRole, out principalKey))
                    {
                        // build a key based on the foreign key values
                        EntityType principalType = MetadataHelper.GetEntityTypeForEnd((AssociationEndMember)constraint.FromRole);
                        bool hasNullValue = false;
                        object[] keyValues = new object[principalType.KeyMembers.Count];
                        for (int i = 0, n = keyValues.Length; i < n; i++)
                        {
                            EdmProperty keyMember = (EdmProperty)principalType.KeyMembers[i];
 
                            // Find corresponding foreign key value
                            int constraintOrdinal = constraint.FromProperties.IndexOf((EdmProperty)keyMember);
                            int recordOrdinal = record.GetOrdinal(constraint.ToProperties[constraintOrdinal].Name);
                            if (record.IsDBNull(recordOrdinal))
                            {
                                hasNullValue = true;
                                break;
                            }
                            keyValues[i] = record.GetValue(recordOrdinal);
                        }
 
                        if (!hasNullValue)
                        {
                            EntitySet principalSet = associationSet.AssociationSetEnds[constraint.FromRole.Name].EntitySet;
                            if (1 == keyValues.Length)
                            {
                                principalKey = new EntityKey(principalSet, keyValues[0]);
                            }
                            else
                            {
                                principalKey = new EntityKey(principalSet, keyValues);
                            }
                        }
                    }
 
                    if (null != principalKey)
                    {
                        // find the right principal key... (first, existing entities; then, added entities; finally, just the key)
                        IEntityStateEntry existingPrincipal;
                        EntityKey tempKey;
                        if (m_stateManager.TryGetEntityStateEntry(principalKey, out existingPrincipal))
                        {
                            // nothing to do. the principal key will resolve to the existing entity
                        }
                        else if (currentValues && this.KeyManager.TryGetTempKey(principalKey, out tempKey))
                        {
                            // if we aren't dealing with current values, we cannot resolve to a temp key (original values
                            // cannot indicate a relationship to an 'added' entity).
                            if (null == tempKey)
                            {
                                throw EntityUtil.Update(Strings.Update_AmbiguousForeignKey(constraint.ToRole.DeclaringType.FullName), null, stateEntry);
                            }
                            else
                            {
                                principalKey = tempKey;
                            }
                        }
 
                        // pull the principal end into the update pipeline (supports value propagation)
                        AddValidAncillaryKey(principalKey, m_optionalEntities);
 
                        // associate keys, where the from side 'owns' the to side
                        for (int i = 0, n = constraint.FromProperties.Count; i < n; i++)
                        {
                            var principalProperty = constraint.FromProperties[i];
                            var dependentProperty = constraint.ToProperties[i];
 
                            int principalKeyMemberCount;
 
                            // get offsets for from and to key properties
                            int principalOffset = GetKeyMemberOffset(constraint.FromRole, principalProperty, out principalKeyMemberCount);
                            int principalIdentifier = this.KeyManager.GetKeyIdentifierForMemberOffset(principalKey, principalOffset, principalKeyMemberCount);
                            int dependentIdentifier;
 
                            if (entitySet.ElementType.KeyMembers.Contains(dependentProperty))
                            {
                                int dependentKeyMemberCount;
                                int dependentOffset = GetKeyMemberOffset(constraint.ToRole, dependentProperty,
                                    out dependentKeyMemberCount);
                                dependentIdentifier = this.KeyManager.GetKeyIdentifierForMemberOffset(dependentKey, dependentOffset, dependentKeyMemberCount);
                            }
                            else
                            {
                                dependentIdentifier = this.KeyManager.GetKeyIdentifierForMember(dependentKey, dependentProperty.Name, currentValues);
                            }
 
                            // don't allow the user to insert or update an entity that refers to a deleted principal
                            if (currentValues && null != existingPrincipal && existingPrincipal.State == EntityState.Deleted &&
                                (stateEntry.State == EntityState.Added || stateEntry.State == EntityState.Modified))
                            {
                                throw EntityUtil.Update(
                                    Strings.Update_InsertingOrUpdatingReferenceToDeletedEntity(associationSet.ElementType.FullName),
                                    null,
                                    stateEntry,
                                    existingPrincipal);
                            }
 
                            // register equivalence of identifiers
                            this.KeyManager.AddReferentialConstraint(stateEntry, dependentIdentifier, principalIdentifier);
                        }
                    }
                }
            }
        }
 
        // requires: role must not be null and property must be a key member for the role end
        private static int GetKeyMemberOffset(RelationshipEndMember role, EdmProperty property, out int keyMemberCount)
        {
            Debug.Assert(null != role);
            Debug.Assert(null != property);
            Debug.Assert(BuiltInTypeKind.RefType == role.TypeUsage.EdmType.BuiltInTypeKind,
                "relationship ends must be of RefType");
            RefType endType = (RefType)role.TypeUsage.EdmType;
            Debug.Assert(BuiltInTypeKind.EntityType == endType.ElementType.BuiltInTypeKind,
                "relationship ends must reference EntityType");
            EntityType entityType = (EntityType)endType.ElementType;
            keyMemberCount = entityType.KeyMembers.Count;
            return entityType.KeyMembers.IndexOf(property);
        }
 
        /// <summary>
        /// Yields all relationship state entries with the given key as an end.
        /// </summary>
        /// <param name="entityKey"></param>
        /// <returns></returns>
        internal IEnumerable<IEntityStateEntry> GetRelationships(EntityKey entityKey)
        {
            return m_stateManager.FindRelationshipsByKey(entityKey);
        }
 
        /// <summary>
        /// Persists stateManager changes to the store.
        /// </summary>
        /// <param name="stateManager">StateManager containing changes to persist.</param>
        /// <param name="adapter">Map adapter requesting the changes.</param>
        /// <returns>Total number of state entries affected</returns>
        internal static Int32 Update(IEntityStateManager stateManager, IEntityAdapter adapter)
        {
            // provider/connection details
            EntityConnection connection = (EntityConnection)adapter.Connection;
            MetadataWorkspace metadataWorkspace = connection.GetMetadataWorkspace();
            int? commandTimeout = adapter.CommandTimeout;
 
            UpdateTranslator translator = new UpdateTranslator(stateManager, metadataWorkspace, connection, commandTimeout);
                
            // tracks values for identifiers in this session
            Dictionary<int, object> identifierValues = new Dictionary<int, object>();
 
            // tracks values for generated values in this session
            List<KeyValuePair<PropagatorResult, object>> generatedValues = new List<KeyValuePair<PropagatorResult, object>>();
 
            IEnumerable<UpdateCommand> orderedCommands = translator.ProduceCommands();
 
            // used to track the source of commands being processed in case an exception is thrown
            UpdateCommand source = null;
            try
            {
                foreach (UpdateCommand command in orderedCommands)
                {
                    // Remember the data sources so that we can throw meaningful exception
                    source = command;
                    long rowsAffected = command.Execute(translator, connection, identifierValues, generatedValues);
                    translator.ValidateRowsAffected(rowsAffected, source);
                }
            }
            catch (Exception e)
            {
                // we should not be wrapping all exceptions
                if (UpdateTranslator.RequiresContext(e))
                {
                    throw EntityUtil.Update(System.Data.Entity.Strings.Update_GeneralExecutionException, e, translator.DetermineStateEntriesFromSource(source));
                }
                throw;
            }
 
            translator.BackPropagateServerGen(generatedValues);
 
            int totalStateEntries = translator.AcceptChanges(adapter);
 
            return totalStateEntries;
        }
 
        private IEnumerable<UpdateCommand> ProduceCommands()
        {
            // load all modified state entries
            PullModifiedEntriesFromStateManager();
            PullUnchangedEntriesFromStateManager();
 
            // check constraints
            m_constraintValidator.ValidateConstraints();
            this.KeyManager.ValidateReferentialIntegrityGraphAcyclic();
            
            // gather all commands (aggregate in a dependency orderer to determine operation order
            IEnumerable<UpdateCommand> dynamicCommands = this.ProduceDynamicCommands();
            IEnumerable<UpdateCommand> functionCommands = this.ProduceFunctionCommands();
            UpdateCommandOrderer orderer = new UpdateCommandOrderer(dynamicCommands.Concat(functionCommands), this);
            IEnumerable<UpdateCommand> orderedCommands;
            IEnumerable<UpdateCommand> remainder;
            if (!orderer.TryTopologicalSort(out orderedCommands, out remainder))
            {
                // throw an exception if it is not possible to perform dependency ordering
                throw DependencyOrderingError(remainder);
            }
 
            return orderedCommands;
        }
 
        // effects: given rows affected, throws if the count suggests a concurrency failure.
        // Throws a concurrency exception based on the current command sources (which allow
        // us to populated the EntityStateEntries on UpdateException)
        private void ValidateRowsAffected(long rowsAffected, UpdateCommand source)
        {
            // 0 rows affected indicates a concurrency failure; negative values suggest rowcount is off; 
            // positive values suggest at least one row was affected (we generally expect exactly one, 
            // but triggers/view logic/logging may change this value)
            if (0 == rowsAffected)
            {
                var stateEntries = DetermineStateEntriesFromSource(source);
                throw EntityUtil.UpdateConcurrency(rowsAffected, null, stateEntries);
            }
        }
 
        private IEnumerable<IEntityStateEntry> DetermineStateEntriesFromSource(UpdateCommand source)
        {
            if (null == source)
            {
                return Enumerable.Empty<IEntityStateEntry>();
            }
            return source.GetStateEntries(this);
        }
 
        // effects: Given a list of pairs describing the contexts for server generated values and their actual
        // values, backpropagates to the relevant state entries
        private void BackPropagateServerGen(List<KeyValuePair<PropagatorResult, object>> generatedValues)
        {
            foreach (KeyValuePair<PropagatorResult, object> generatedValue in generatedValues)
            {
                PropagatorResult context;
 
                // check if a redirect to "owner" result is possible
                if (PropagatorResult.NullIdentifier == generatedValue.Key.Identifier ||
                    !KeyManager.TryGetIdentifierOwner(generatedValue.Key.Identifier, out context))                
                {
                    // otherwise, just use the straightforward context
                    context = generatedValue.Key;
                }
 
                object value = generatedValue.Value;
                if (context.Identifier == PropagatorResult.NullIdentifier)
                {
                    SetServerGenValue(context, value);
                }
                else
                {
                    // check if we need to back propagate this value to any other positions (e.g. for foreign keys)
                    foreach (int dependent in this.KeyManager.GetDependents(context.Identifier))
                    {
                        if (this.KeyManager.TryGetIdentifierOwner(dependent, out context))
                        {
                            SetServerGenValue(context, value);
                        }
                    }
                }
            }
        }
 
        private void SetServerGenValue(PropagatorResult context, object value)
        {
            if (context.RecordOrdinal != PropagatorResult.NullOrdinal)
            {
                CurrentValueRecord targetRecord = context.Record;
 
                // determine if type compensation is required
                IExtendedDataRecord recordWithMetadata = (IExtendedDataRecord)targetRecord;
                EdmMember member = recordWithMetadata.DataRecordInfo.FieldMetadata[context.RecordOrdinal].FieldType;
 
                value = value ?? DBNull.Value; // records expect DBNull rather than null
                value = AlignReturnValue(value, member, context);
                targetRecord.SetValue(context.RecordOrdinal, value);
            }
        }
 
        /// <summary>
        /// Aligns a value returned from the store with the expected type for the member.
        /// </summary>
        /// <param name="value">Value to convert.</param>
        /// <param name="member">Metadata for the member being set.</param>
        /// <param name="context">The context generating the return value.</param>
        /// <returns>Converted return value</returns>
        private object AlignReturnValue(object value, EdmMember member, PropagatorResult context)
        {
            if (DBNull.Value.Equals(value))
            {
                // check if there is a nullability constraint on the value
                if (BuiltInTypeKind.EdmProperty == member.BuiltInTypeKind &&
                    !((EdmProperty)member).Nullable)
                {
                    throw EntityUtil.Update(System.Data.Entity.Strings.Update_NullReturnValueForNonNullableMember(
                        member.Name, 
                        member.DeclaringType.FullName), null);
                }
            }
            else if (!Helper.IsSpatialType(member.TypeUsage))
            {
                Type clrType;
                Type clrEnumType = null;
                if (Helper.IsEnumType(member.TypeUsage.EdmType))
                {
                    PrimitiveType underlyingType = Helper.AsPrimitive(member.TypeUsage.EdmType);
                    clrEnumType = context.Record.GetFieldType(context.RecordOrdinal);
                    clrType = underlyingType.ClrEquivalentType;
                    Debug.Assert(clrEnumType.IsEnum); 
                }
                else
                {
                    // convert the value to the appropriate CLR type
                    Debug.Assert(BuiltInTypeKind.PrimitiveType == member.TypeUsage.EdmType.BuiltInTypeKind,
                        "we only allow return values that are instances of EDM primitive or enum types");
                    PrimitiveType primitiveType = (PrimitiveType)member.TypeUsage.EdmType;
                    clrType = primitiveType.ClrEquivalentType;
                }
 
                try
                {
                    value = Convert.ChangeType(value, clrType, CultureInfo.InvariantCulture);
                    if (clrEnumType != null)
                    {
                        value = Enum.ToObject(clrEnumType, value);
                    }
                }
                catch (Exception e)
                {
                    // we should not be wrapping all exceptions
                    if (UpdateTranslator.RequiresContext(e)) 
                    {
                        Type userClrType = clrEnumType ?? clrType;
                        throw EntityUtil.Update(System.Data.Entity.Strings.Update_ReturnValueHasUnexpectedType(
                            value.GetType().FullName,
                            userClrType.FullName,
                            member.Name,
                            member.DeclaringType.FullName), e);
                    }
                    throw;
                }
            }
 
            // return the adjusted value
            return value;
        }
 
        /// <summary>
        /// Accept changes to entities and relationships processed by this translator instance.
        /// </summary>
        /// <param name="adapter">Data adapter</param>
        /// <returns>Number of state entries affected.</returns>
        private int AcceptChanges(IEntityAdapter adapter)
        {
            int affectedCount = 0;
            foreach (IEntityStateEntry stateEntry in m_stateEntries)
            {
                // only count and accept changes for state entries that are being explicitly modified
                if (EntityState.Unchanged != stateEntry.State)
                {
                    if (adapter.AcceptChangesDuringUpdate)
                    {
                        stateEntry.AcceptChanges();
                    }
                    affectedCount++;
                }
            }
            return affectedCount;
        }
 
        /// <summary>
        /// Gets extents for which this translator has identified changes to be handled
        /// by the standard update pipeline.
        /// </summary>
        /// <returns>Enumeration of modified C-Space extents.</returns>
        private IEnumerable<EntitySetBase> GetDynamicModifiedExtents()
        {
            return m_changes.Keys;
        }
 
        /// <summary>
        /// Gets extents for which this translator has identified changes to be handled
        /// by function mappings.
        /// </summary>
        /// <returns>Enumreation of modified C-Space extents.</returns>
        private IEnumerable<EntitySetBase> GetFunctionModifiedExtents()
        {
            return m_functionChanges.Keys;
        }
 
        /// <summary>
        /// Produce dynamic store commands for this translator's changes.
        /// </summary>
        /// <returns>Database commands in a safe order</returns>
        private IEnumerable<UpdateCommand> ProduceDynamicCommands()
        {
            // Initialize DBCommand update compiler
            UpdateCompiler updateCompiler = new UpdateCompiler(this);
            
            // Determine affected
            Set<EntitySet> tables = new Set<EntitySet>();
 
            foreach (EntitySetBase extent in GetDynamicModifiedExtents())
            {
                Set<EntitySet> affectedTables = m_viewLoader.GetAffectedTables(extent, m_metadataWorkspace);
                //Since these extents don't have Functions defined for update operations,
                //the affected tables should be provided via MSL.
                //If we dont find any throw an exception
                if (affectedTables.Count == 0)
                {
                    throw EntityUtil.Update(System.Data.Entity.Strings.Update_MappingNotFound(
                        extent.Name), null /*stateEntries*/);
                }
 
                foreach (EntitySet table in affectedTables)
                {
                    tables.Add(table);
                }
            }
 
            // Determine changes to apply to each table
            foreach (EntitySet table in tables)
            {
                DbQueryCommandTree umView = m_connection.GetMetadataWorkspace().GetCqtView(table);
                
                // Propagate changes to root of tree (at which point they are S-Space changes)
                ChangeNode changeNode = Propagator.Propagate(this, table, umView);
                
                // Process changes for the table
                TableChangeProcessor change = new TableChangeProcessor(table);
                foreach (UpdateCommand command in change.CompileCommands(changeNode, updateCompiler))
                {
                    yield return command;
                }
            }
        }
 
        // Generates and caches a command definition for the given function
        internal DbCommandDefinition GenerateCommandDefinition(StorageModificationFunctionMapping functionMapping)
        {
            if (null == m_modificationFunctionCommandDefinitions) 
            { 
                m_modificationFunctionCommandDefinitions = new Dictionary<StorageModificationFunctionMapping,DbCommandDefinition>();
            }
            DbCommandDefinition commandDefinition;
            if (!m_modificationFunctionCommandDefinitions.TryGetValue(functionMapping, out commandDefinition))
            {
                // synthesize a RowType for this mapping
                TypeUsage resultType = null;
                if (null != functionMapping.ResultBindings && 0 < functionMapping.ResultBindings.Count)
                {
                    List<EdmProperty> properties = new List<EdmProperty>(functionMapping.ResultBindings.Count);
                    foreach (StorageModificationFunctionResultBinding resultBinding in functionMapping.ResultBindings)
                    {
                        properties.Add(new EdmProperty(resultBinding.ColumnName, resultBinding.Property.TypeUsage));
                    }
                    RowType rowType = new RowType(properties);
                    CollectionType collectionType = new CollectionType(rowType);
                    resultType = TypeUsage.Create(collectionType);
                }
 
                // add function parameters
                IEnumerable<KeyValuePair<string, TypeUsage>> functionParams =
                    functionMapping.Function.Parameters.Select(paramInfo => new KeyValuePair<string, TypeUsage>(paramInfo.Name, paramInfo.TypeUsage));
                
                // construct DbFunctionCommandTree including implict return type
                DbFunctionCommandTree tree = new DbFunctionCommandTree(m_metadataWorkspace, DataSpace.SSpace,
                    functionMapping.Function, resultType, functionParams);
                                
                commandDefinition = m_providerServices.CreateCommandDefinition(tree);
            }
            return commandDefinition;
        }
 
        // Produces all function commands in a safe order
        private IEnumerable<UpdateCommand> ProduceFunctionCommands()
        {
            foreach (EntitySetBase extent in GetFunctionModifiedExtents())
            {
                // Get a handle on the appropriate translator
                ModificationFunctionMappingTranslator translator = m_viewLoader.GetFunctionMappingTranslator(extent, m_metadataWorkspace);
 
                if (null != translator)
                {
                    // Compile commands
                    foreach (ExtractedStateEntry stateEntry in GetExtentFunctionModifications(extent))
                    {
                        FunctionUpdateCommand command = translator.Translate(this, stateEntry);
                        if (null != command) 
                        {
                            yield return command;
                        }
                    }
                }
            }
        }
 
        /// <summary>
        /// Gets a metadata wrapper for the given type. The wrapper makes
        /// certain tasks in the update pipeline more efficient.
        /// </summary>
        /// <param name="type">Structural type</param>
        /// <returns>Metadata wrapper</returns>
        internal ExtractorMetadata GetExtractorMetadata(EntitySetBase entitySetBase, StructuralType type)
        {
            ExtractorMetadata metadata;
            var key = Tuple.Create(entitySetBase, type);
            if (!m_extractorMetadata.TryGetValue(key, out metadata))
            {
                metadata = new ExtractorMetadata(entitySetBase, type, this);
                m_extractorMetadata.Add(key, metadata);
            }
            return metadata;
        }
 
        /// <summary>
        /// Returns error when it is not possible to order update commands. Argument is the 'remainder', or commands
        /// that could not be ordered due to a cycle.
        /// </summary>
        private UpdateException DependencyOrderingError(IEnumerable<UpdateCommand> remainder)
        {
            Debug.Assert(null != remainder && remainder.Count() > 0, "must provide non-empty remainder");
 
            HashSet<IEntityStateEntry> stateEntries = new HashSet<IEntityStateEntry>();
 
            foreach (UpdateCommand command in remainder)
            {
                stateEntries.UnionWith(command.GetStateEntries(this));
            }
 
            // throw exception containing all related state entries
            throw EntityUtil.Update(System.Data.Entity.Strings.Update_ConstraintCycle, null, stateEntries);
        }
 
        /// <summary>
        /// Creates a command in the current context.
        /// </summary>
        /// <param name="commandTree">DbCommand tree</param>
        /// <returns>DbCommand produced by the current provider.</returns>
        internal DbCommand CreateCommand(DbModificationCommandTree commandTree)
        {
            DbCommand command;
            Debug.Assert(null != m_providerServices, "constructor ensures either the command definition " +
                    "builder or provider service is available");
            Debug.Assert(null != m_connection.StoreConnection, "EntityAdapter.Update ensures the store connection is set");
            try 
            {
                command = m_providerServices.CreateCommand(commandTree);
            }
            catch (Exception e) 
            {
                // we should not be wrapping all exceptions
                if (UpdateTranslator.RequiresContext(e))
                {
                    // we don't wan't folks to have to know all the various types of exceptions that can 
                    // occur, so we just rethrow a CommandDefinitionException and make whatever we caught  
                    // the inner exception of it.
                    throw EntityUtil.CommandCompilation(System.Data.Entity.Strings.EntityClient_CommandDefinitionPreparationFailed, e);
                }
                throw;
            }
            return command;
        }
 
        /// <summary>
        /// Helper method to allow the setting of parameter values to update stored procedures.
        /// Allows the DbProvider an opportunity to rewrite the parameter to suit provider specific needs.
        /// </summary>
        /// <param name="parameter">Parameter to set.</param>
        /// <param name="typeUsage">The type of the parameter.</param>
        /// <param name="value">The value to which to set the parameter.</param>
        internal void SetParameterValue(DbParameter parameter, TypeUsage typeUsage, object value)
        {
            m_providerServices.SetParameterValue(parameter, typeUsage, value);
        }
 
        /// <summary>
        /// Determines whether the given exception requires additional context from the update pipeline (in other
        /// words, whether the exception should be wrapped in an UpdateException).
        /// </summary>
        /// <param name="e">Exception to test.</param>
        /// <returns>true if exception should be wrapped; false otherwise</returns>
        internal static bool RequiresContext(Exception e)
        {
            // if the exception isn't catchable, never wrap
            if (!EntityUtil.IsCatchableExceptionType(e)) { return false; }
 
            // update and incompatible provider exceptions already contain the necessary context
            return !(e is UpdateException) && !(e is ProviderIncompatibleException);
        }
 
        #region Private initialization methods
        /// <summary>
        /// Retrieve all modified entries from the state manager.
        /// </summary>
        private void PullModifiedEntriesFromStateManager()
        {
            // do a first pass over added entries to register 'by value' entity key targets that may be resolved as 
            // via a foreign key
            foreach (IEntityStateEntry addedEntry in m_stateManager.GetEntityStateEntries(EntityState.Added))
            {
                if (!addedEntry.IsRelationship && !addedEntry.IsKeyEntry)
                {
                    this.KeyManager.RegisterKeyValueForAddedEntity(addedEntry);
                }
            }
 
            // do a second pass over entries to register referential integrity constraints
            // for server-generation
            foreach (IEntityStateEntry modifiedEntry in m_stateManager.GetEntityStateEntries(EntityState.Modified | EntityState.Added | EntityState.Deleted))
            {
                RegisterReferentialConstraints(modifiedEntry);
            }
 
            foreach (IEntityStateEntry modifiedEntry in m_stateManager.GetEntityStateEntries(EntityState.Modified | EntityState.Added | EntityState.Deleted))
            {
                LoadStateEntry(modifiedEntry);
            }
        }
 
 
        /// <summary>
        /// Retrieve all required/optional/value entries into the state manager. These are entries that --
        /// although unmodified -- affect or are affected by updates.
        /// </summary>
        private void PullUnchangedEntriesFromStateManager()
        {
            foreach (KeyValuePair<EntityKey, AssociationSet> required in m_requiredEntities)
            {
                EntityKey key = required.Key;
 
                if (!m_knownEntityKeys.Contains(key))
                {
                    // pull the value into the translator if we don't already it
                    IEntityStateEntry requiredEntry;
 
                    if (m_stateManager.TryGetEntityStateEntry(key, out requiredEntry) && !requiredEntry.IsKeyEntry)
                    {
                        // load the object as a no-op update
                        LoadStateEntry(requiredEntry);
                    }
                    else
                    {
                        // throw an exception
                        throw EntityUtil.UpdateMissingEntity(required.Value.Name, TypeHelpers.GetFullName(key.EntityContainerName, key.EntitySetName));
                    }
                }
            }
 
            foreach (EntityKey key in m_optionalEntities)
            {
                if (!m_knownEntityKeys.Contains(key))
                {
                    IEntityStateEntry optionalEntry;
 
                    if (m_stateManager.TryGetEntityStateEntry(key, out optionalEntry) && !optionalEntry.IsKeyEntry)
                    {
                        // load the object as a no-op update
                        LoadStateEntry(optionalEntry);
                    }
                }
            }
 
            foreach (EntityKey key in m_includedValueEntities)
            {
                if (!m_knownEntityKeys.Contains(key))
                {
                    IEntityStateEntry valueEntry;
 
                    if (m_stateManager.TryGetEntityStateEntry(key, out valueEntry))
                    {
                        // Convert state entry so that its values are known to the update pipeline.
                        var result = m_recordConverter.ConvertCurrentValuesToPropagatorResult(valueEntry, ModifiedPropertiesBehavior.NoneModified);
                    }
                }
            }
        }
 
        /// <summary>
        /// Validates and tracks a state entry being processed by this translator.
        /// </summary>
        /// <param name="stateEntry"></param>
        private void ValidateAndRegisterStateEntry(IEntityStateEntry stateEntry)
        {
            EntityUtil.CheckArgumentNull(stateEntry, "stateEntry");
 
            EntitySetBase extent = stateEntry.EntitySet;
            if (null == extent) 
            {
                throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.InvalidStateEntry, 1);
            }
 
            // Determine the key. May be null if the state entry does not represent an entity.
            EntityKey entityKey = stateEntry.EntityKey;
            IExtendedDataRecord record = null;
 
            // verify the structure of the entry values
            if (0 != ((EntityState.Added | EntityState.Modified | EntityState.Unchanged) & stateEntry.State))
            {
                // added, modified and unchanged entries have current values
                record = (IExtendedDataRecord)stateEntry.CurrentValues;
                ValidateRecord(extent, record, stateEntry);
            }
            if (0 != ((EntityState.Modified | EntityState.Deleted | EntityState.Unchanged) & stateEntry.State))
            {
                // deleted, modified and unchanged entries have original values
                record = (IExtendedDataRecord)stateEntry.OriginalValues;
                ValidateRecord(extent, record, stateEntry);
            }
            Debug.Assert(null != record, "every state entry must contain a record");
 
            // check for required ends of relationships
            AssociationSet associationSet = extent as AssociationSet;
            if (null != associationSet)
            {
                AssociationSetMetadata associationSetMetadata = m_viewLoader.GetAssociationSetMetadata(associationSet, m_metadataWorkspace);
 
                if (associationSetMetadata.HasEnds)
                {
                    foreach (FieldMetadata field in record.DataRecordInfo.FieldMetadata)
                    {
                        // ends of relationship record must be EntityKeys
                        EntityKey end = (EntityKey)record.GetValue(field.Ordinal);
 
                        // ends of relationships must have AssociationEndMember metadata
                        AssociationEndMember endMetadata = (AssociationEndMember)field.FieldType;
 
                        if (associationSetMetadata.RequiredEnds.Contains(endMetadata))
                        {
                            if (!m_requiredEntities.ContainsKey(end))
                            {
                                m_requiredEntities.Add(end, associationSet);
                            }
                        }
 
                        else if (associationSetMetadata.OptionalEnds.Contains(endMetadata))
                        {
                            AddValidAncillaryKey(end, m_optionalEntities);
                        }
 
                        else if (associationSetMetadata.IncludedValueEnds.Contains(endMetadata))
                        {
                            AddValidAncillaryKey(end, m_includedValueEntities);
                        }
                    }
                }
 
                // register relationship with validator
                m_constraintValidator.RegisterAssociation(associationSet, record, stateEntry);
            }
            else
            {
                // register entity with validator
                m_constraintValidator.RegisterEntity(stateEntry);
            }
 
            // add to the list of entries being tracked
            m_stateEntries.Add(stateEntry);
            if (null != (object)entityKey) { m_knownEntityKeys.Add(entityKey); }
        }
 
        /// <summary>
        /// effects: given an entity key and a set, adds key to the set iff. the corresponding entity
        /// is:
        /// 
        ///     not a stub (or 'key') entry, and;
        ///     not a core element in the update pipeline (it's not being directly modified)
        /// </summary>
        private void AddValidAncillaryKey(EntityKey key, Set<EntityKey> keySet)
        {
            // Note: an entity is ancillary iff. it is unchanged (otherwise it is tracked as a "standard" changed entity)
            IEntityStateEntry endEntry;
            if (m_stateManager.TryGetEntityStateEntry(key, out endEntry) && // make sure the entity is tracked
                !endEntry.IsKeyEntry && // make sure the entity is not a stub
                endEntry.State == EntityState.Unchanged) // if the entity is being modified, it's already included anyways
            {
                keySet.Add(key);
            }
        }
 
        private void ValidateRecord(EntitySetBase extent, IExtendedDataRecord record, IEntityStateEntry entry)
        {
            Debug.Assert(null != extent, "must be verified by caller");
 
            DataRecordInfo recordInfo;
            if ((null == record) ||
                (null == (recordInfo = record.DataRecordInfo)) ||
                (null == recordInfo.RecordType))
            {
                throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.InvalidStateEntry, 2);
            }
 
            VerifyExtent(MetadataWorkspace, extent);
 
            // additional validation happens lazily as values are loaded from the record
        }
 
        // Verifies the given extent is present in the given workspace.
        private static void VerifyExtent(MetadataWorkspace workspace, EntitySetBase extent)
        {
            // get the container to which the given extent belongs
            EntityContainer actualContainer = extent.EntityContainer;
 
            // try to retrieve the container in the given workspace
            EntityContainer referenceContainer = null;
            if (null != actualContainer)
            {
                workspace.TryGetEntityContainer(
                    actualContainer.Name, actualContainer.DataSpace, out referenceContainer);
            }
            
            // determine if the given extent lives in a container from the given workspace
            // (the item collections for each container are reference equivalent when they are declared in the
            // same item collection)
            if (null == actualContainer || null == referenceContainer ||
                !Object.ReferenceEquals(actualContainer, referenceContainer)) 
            {
                // 
 
 
 
                throw EntityUtil.Update(System.Data.Entity.Strings.Update_WorkspaceMismatch, null);
            }
        }
 
        private void LoadStateEntry(IEntityStateEntry stateEntry)
        {
            Debug.Assert(null != stateEntry, "state entry must exist");
 
            // make sure the state entry doesn't contain invalid data and register it with the
            // update pipeline
            ValidateAndRegisterStateEntry(stateEntry);
 
            // use data structure internal to the update pipeline instead of the raw state entry
            ExtractedStateEntry extractedStateEntry = new ExtractedStateEntry(this, stateEntry);
 
            // figure out if this state entry is being handled by a function (stored procedure) or
            // through dynamic SQL
            EntitySetBase extent = stateEntry.EntitySet;
            if (null == m_viewLoader.GetFunctionMappingTranslator(extent, m_metadataWorkspace))
            {
                // if there is no function mapping, register a ChangeNode (used for update
                // propagation and dynamic SQL generation)
                ChangeNode changeNode = GetExtentModifications(extent);
                if (null != extractedStateEntry.Original)
                {
                    changeNode.Deleted.Add(extractedStateEntry.Original);
                }
                if (null != extractedStateEntry.Current)
                {
                    changeNode.Inserted.Add(extractedStateEntry.Current);
                }
            }
            else
            {
                // for function updates, store off the extracted state entry in its entirety
                // (used when producing FunctionUpdateCommands)
                List<ExtractedStateEntry> functionEntries = GetExtentFunctionModifications(extent);
                functionEntries.Add(extractedStateEntry);
            }
        }
 
 
 
        /// <summary>
        /// Retrieve a change node for an extent. If none exists, creates and registers a new one.
        /// </summary>
        /// <param name="extent">Extent for which to return a change node.</param>
        /// <returns>Change node for requested extent.</returns>
        internal ChangeNode GetExtentModifications(EntitySetBase extent)
        {
            EntityUtil.CheckArgumentNull(extent, "extent");
            Debug.Assert(null != m_changes, "(UpdateTranslator/GetChangeNodeForExtent) method called before translator initialized");
 
            ChangeNode changeNode;
 
            if (!m_changes.TryGetValue(extent, out changeNode))
            {
                changeNode = new ChangeNode(TypeUsage.Create(extent.ElementType));
                m_changes.Add(extent, changeNode);
            }
 
            return changeNode;
        }
        
        /// <summary>
        /// Retrieve a list of state entries being processed by custom user functions.
        /// </summary>
        /// <param name="extent">Extent for which to return entries.</param>
        /// <returns>List storing the entries.</returns>
        internal List<ExtractedStateEntry> GetExtentFunctionModifications(EntitySetBase extent)
        {
            EntityUtil.CheckArgumentNull(extent, "extent");
            Debug.Assert(null != m_functionChanges, "method called before translator initialized");
 
            List<ExtractedStateEntry> entries;
 
            if (!m_functionChanges.TryGetValue(extent, out entries))
            {
                entries = new List<ExtractedStateEntry>();
                m_functionChanges.Add(extent, entries);
            }
 
            return entries;
        }
        #endregion
        #endregion
    }
 
    /// <summary>
    /// Enumeration of possible operators. 
    /// </summary>
    /// <remarks>
    /// The values are used to determine the order of operations (in the absence of any strong dependencies). 
    /// The chosen order is based on the observation that hidden dependencies (e.g. due to temporary keys in 
    /// the state manager or unknown FKs) favor deletes before inserts and updates before deletes. For instance, 
    /// a deleted entity may have the same real key value as an inserted entity. Similarly, a self-reference 
    /// may require a new dependent row to be updated before the prinpical row is inserted. Obviously, the actual
    /// constraints are required to make reliable decisions so this ordering is merely a heuristic.
    /// </remarks>
    internal enum ModificationOperator : byte
    {
        Update = 0,
        Delete = 1,
        Insert = 2,
    }
}