Code:
/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / DataEntity / System / Data / Map / Update / Internal / UpdateTranslator.cs / 1305376 / UpdateTranslator.cs
//---------------------------------------------------------------------- //// Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- 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.Globalization; using System.Data.Entity; using System.Linq; namespace System.Data.Mapping.Update.Internal { ////// This class performs to following tasks to persist C-Space changes to the store: /// internal partial class UpdateTranslator { #region Constructors //////
///- Extract changes from the entity state manager
///- Group changes by C-Space extent
///- For each affected S-Space table, perform propagation (get changes in S-Space terms)
///- Merge S-Space inserts and deletes into updates where appropriate
///- Produce S-Space commands implementating the modifications (insert, delete and update SQL statements)
////// Constructs a grouper based on the contents of the given entity state manager. /// /// Entity state manager containing changes to be processed. /// Metadata workspace. /// Map connection /// Timeout for update commands; null means 'use provider default' 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(); m_functionChanges = new Dictionary >(); m_stateEntries = new List (); m_knownEntityKeys = new Set (); m_requiredEntities = new Dictionary (); m_optionalEntities = new Set (); m_includedValueEntities = new Set (); // 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 , ExtractorMetadata>(); ; // key management KeyManager = new KeyManager(this); KeyComparer = CompositeKey.CreateComparer(KeyManager); } #endregion #region Fields // propagation state private readonly Dictionary m_changes; private readonly Dictionary > m_functionChanges; private readonly List m_stateEntries; private readonly Set m_knownEntityKeys; private readonly Dictionary m_requiredEntities; private readonly Set m_optionalEntities; private readonly Set 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 m_functionCommandDefinitions; // metadata cache private readonly Dictionary , ExtractorMetadata> m_extractorMetadata; // static members private static readonly List s_emptyMemberList = new List (); #endregion #region Properties /// /// Gets workspace used in this session. /// internal MetadataWorkspace MetadataWorkspace { get { return m_metadataWorkspace; } } ////// Gets key manager that handles interpretation of keys (including resolution of /// referential-integrity/foreign key constraints) /// internal readonly KeyManager KeyManager; ////// Gets the view loader metadata wrapper for the current workspace. /// internal ViewLoader ViewLoader { get { return m_viewLoader; } } ////// Gets record converter which translates state entry records into propagator results. /// internal RecordConverter RecordConverter { get { return m_recordConverter; } } ////// Gets command timeout for update commands. If null, use default. /// internal int? CommandTimeout { get { return m_commandTimeout; } } internal readonly IEqualityComparerKeyComparer; #endregion #region Methods /// /// 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. /// /// State entry 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.Enumerator principalPropertyEnum = constraint.FromProperties.GetEnumerator()) using (ReadOnlyMetadataCollection .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); } /// /// Yields all relationship state entries with the given key as an end. /// /// ///internal IEnumerable GetRelationships(EntityKey entityKey) { return m_stateManager.FindRelationshipsByKey(entityKey); } /// /// Persists stateManager changes to the store. /// /// StateManager containing changes to persist. /// Map adapter requesting the changes. ///Total number of state entries affected internal static Int32 Update(IEntityStateManager stateManager, IEntityAdapter adapter) { IntPtr cookie; EntityBid.ScopeEnter(out cookie, ""); // provider/connection details EntityConnection connection = (EntityConnection)adapter.Connection; MetadataWorkspace metadataWorkspace = connection.GetMetadataWorkspace(); int? commandTimeout = adapter.CommandTimeout; try { UpdateTranslator translator = new UpdateTranslator(stateManager, metadataWorkspace, connection, commandTimeout); // tracks values for identifiers in this session Dictionary identifierValues = new Dictionary (); // tracks values for generated values in this session List > generatedValues = new List >(); IEnumerable 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; } finally { EntityBid.ScopeLeave(ref cookie); } } private IEnumerable 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 dynamicCommands = this.ProduceDynamicCommands(); IEnumerable functionCommands = this.ProduceFunctionCommands(); UpdateCommandOrderer orderer = new UpdateCommandOrderer(dynamicCommands.Concat(functionCommands), this); IEnumerable orderedCommands; IEnumerable 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 DetermineStateEntriesFromSource(UpdateCommand source) { if (null == source) { return Enumerable.Empty (); } 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 > generatedValues) { foreach (KeyValuePair 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 static 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); } } /// /// Aligns a value returned from the store with the expected type for the member. /// /// Value to convert. /// Metadata for the member being set. /// The context generating the return value. ///Converted return value private static 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 { // 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 types"); PrimitiveType primitiveType = (PrimitiveType)member.TypeUsage.EdmType; Type clrType = primitiveType.ClrEquivalentType; try { value = Convert.ChangeType(value, clrType, CultureInfo.InvariantCulture); } catch (Exception e) { // we should not be wrapping all exceptions if (UpdateTranslator.RequiresContext(e)) { throw EntityUtil.Update(System.Data.Entity.Strings.Update_ReturnValueHasUnexpectedType( value.GetType().FullName, clrType.FullName, member.Name, member.DeclaringType.FullName), e); } throw; } } // return the adjusted value return value; } ////// Accept changes to entities and relationships processed by this translator instance. /// /// Data adapter ///Number of state entries affected. 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; } ////// Gets extents for which this translator has identified changes to be handled /// by the standard update pipeline. /// ///Enumeration of modified C-Space extents. private IEnumerableGetDynamicModifiedExtents() { return m_changes.Keys; } /// /// Gets extents for which this translator has identified changes to be handled /// by function mappings. /// ///Enumreation of modified C-Space extents. private IEnumerableGetFunctionModifiedExtents() { return m_functionChanges.Keys; } /// /// Produce dynamic store commands for this translator's changes. /// ///Database commands in a safe order private IEnumerableProduceDynamicCommands() { // Initialize DBCommand update compiler UpdateCompiler updateCompiler = new UpdateCompiler(this); // Determine affected Set tables = new Set (); foreach (EntitySetBase extent in GetDynamicModifiedExtents()) { Set 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(StorageFunctionMapping functionMapping) { if (null == m_functionCommandDefinitions) { m_functionCommandDefinitions = new Dictionary (); } DbCommandDefinition commandDefinition; if (!m_functionCommandDefinitions.TryGetValue(functionMapping, out commandDefinition)) { // synthesize a RowType for this mapping TypeUsage resultType = null; if (null != functionMapping.ResultBindings && 0 < functionMapping.ResultBindings.Count) { List properties = new List (functionMapping.ResultBindings.Count); foreach (StorageFunctionResultBinding 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 > functionParams = functionMapping.Function.Parameters.Select(paramInfo => new KeyValuePair (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 ProduceFunctionCommands() { foreach (EntitySetBase extent in GetFunctionModifiedExtents()) { // Get a handle on the appropriate translator FunctionMappingTranslator 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; } } } } } /// /// Gets a metadata wrapper for the given type. The wrapper makes /// certain tasks in the update pipeline more efficient. /// /// Structural type ///Metadata wrapper 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; } ////// 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. /// private UpdateException DependencyOrderingError(IEnumerableremainder) { Debug.Assert(null != remainder && remainder.Count() > 0, "must provide non-empty remainder"); HashSet stateEntries = new HashSet (); 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); } /// /// Creates a command in the current context. /// /// DbCommand tree ///DbCommand produced by the current provider. 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; } ////// Determines whether the given exception requires additional context from the update pipeline (in other /// words, whether the exception should be wrapped in an UpdateException). /// /// Exception to test. ///true if exception should be wrapped; false otherwise 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 ////// Retrieve all modified entries from the state manager. /// 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); } } ////// Retrieve all required/optional/value entries into the state manager. These are entries that -- /// although unmodified -- affect or are affected by updates. /// private void PullUnchangedEntriesFromStateManager() { foreach (KeyValuePairrequired 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); } } } } /// /// Validates and tracks a state entry being processed by this translator. /// /// 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); } } ////// 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) /// private void AddValidAncillaryKey(EntityKey key, SetkeySet) { // 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 functionEntries = GetExtentFunctionModifications(extent); functionEntries.Add(extractedStateEntry); } } /// /// Retrieve a change node for an extent. If none exists, creates and registers a new one. /// /// Extent for which to return a change node. ///Change node for requested extent. 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; } ////// Retrieve a list of state entries being processed by custom user functions. /// /// Extent for which to return entries. ///List storing the entries. internal ListGetExtentFunctionModifications(EntitySetBase extent) { EntityUtil.CheckArgumentNull(extent, "extent"); Debug.Assert(null != m_functionChanges, "method called before translator initialized"); List entries; if (!m_functionChanges.TryGetValue(extent, out entries)) { entries = new List (); m_functionChanges.Add(extent, entries); } return entries; } #endregion #endregion } /// /// Enumeration of possible operators. /// ////// 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. /// internal enum ModificationOperator : byte { Update = 0, Delete = 1, Insert = 2, } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- ServiceModelConfigurationSectionCollection.cs
- ProfessionalColors.cs
- XmlLangPropertyAttribute.cs
- TextTreeNode.cs
- IdleTimeoutMonitor.cs
- WebPartManagerInternals.cs
- GridViewSortEventArgs.cs
- _Win32.cs
- CultureInfoConverter.cs
- EntityObject.cs
- XmlObjectSerializerContext.cs
- DiffuseMaterial.cs
- BStrWrapper.cs
- GridViewRowEventArgs.cs
- FormParameter.cs
- XamlPoint3DCollectionSerializer.cs
- KeyNotFoundException.cs
- UserControl.cs
- StatusBarAutomationPeer.cs
- SQLBoolean.cs
- AnchoredBlock.cs
- HwndMouseInputProvider.cs
- CodeSnippetTypeMember.cs
- Lease.cs
- XmlSchemaObjectTable.cs
- EntityTypeEmitter.cs
- ExtendedProperty.cs
- ObjectQuery_EntitySqlExtensions.cs
- ReachPageContentSerializer.cs
- COM2TypeInfoProcessor.cs
- AccessText.cs
- SBCSCodePageEncoding.cs
- FrameworkContextData.cs
- TabletDeviceInfo.cs
- DbInsertCommandTree.cs
- HeaderedContentControl.cs
- SqlTypesSchemaImporter.cs
- AddInContractAttribute.cs
- InkCanvasSelection.cs
- InputLanguageProfileNotifySink.cs
- CustomError.cs
- SByte.cs
- IconConverter.cs
- WindowsListViewItem.cs
- ListViewCancelEventArgs.cs
- RegexReplacement.cs
- UpdatePanelControlTrigger.cs
- PtsContext.cs
- ListBindingHelper.cs
- ClientSettings.cs
- TimeZone.cs
- CompilationSection.cs
- SynchronizedInputPattern.cs
- TextDecorationCollection.cs
- HttpListenerResponse.cs
- AuthenticationSection.cs
- QilFunction.cs
- CacheChildrenQuery.cs
- SqlCrossApplyToCrossJoin.cs
- FilterEventArgs.cs
- SelectionRangeConverter.cs
- WindowsGrip.cs
- COM2EnumConverter.cs
- SelectionItemPatternIdentifiers.cs
- AuthenticationException.cs
- QueryOperationResponseOfT.cs
- RootProfilePropertySettingsCollection.cs
- ExpandButtonVisibilityConverter.cs
- WebPartCancelEventArgs.cs
- WCFModelStrings.Designer.cs
- DesignerEditorPartChrome.cs
- BoolExpressionVisitors.cs
- ExpandoObject.cs
- DrawingDrawingContext.cs
- CellLabel.cs
- RelationshipFixer.cs
- RectAnimationClockResource.cs
- RoleServiceManager.cs
- PageParserFilter.cs
- PropertyManager.cs
- UserValidatedEventArgs.cs
- StrokeRenderer.cs
- RequestBringIntoViewEventArgs.cs
- RSAPKCS1SignatureFormatter.cs
- EntityCommand.cs
- StateDesignerConnector.cs
- RequestTimeoutManager.cs
- CompositeDuplexBindingElementImporter.cs
- SudsWriter.cs
- CodeCommentStatement.cs
- HostExecutionContextManager.cs
- DeploymentSectionCache.cs
- HitTestParameters3D.cs
- SimpleLine.cs
- SystemResourceKey.cs
- CodeAttributeArgumentCollection.cs
- DataGridViewCellEventArgs.cs
- PrintingPermissionAttribute.cs
- XmlSerializerAssemblyAttribute.cs
- HandlerWithFactory.cs