Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / ndp / fx / src / DataEntity / System / Data / Map / Update / Internal / UpdateTranslator.cs / 1 / 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_commandTreeContext = new DbQueryCommandTree(metadataWorkspace, DataSpace.CSpace); 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 (EqualityComparer .Default); // 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 DbCommandTree m_commandTreeContext; 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 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_commandTreeContext.MetadataWorkspace; } } ////// Gets key manager that handles interpretation of keys (including resolution of /// referential-integrity/common value 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); long principalIdentifier = this.KeyManager.GetKeyIdentifierForMemberOffset(principalKey, principalOffset, principalKeyMemberCount); long dependentIdentifier = this.KeyManager.GetKeyIdentifierForMemberOffset(dependentKey, dependentOffset, dependentKeyMemberCount); // 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 >(); List 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; int 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 List 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); List orderedCommands = new List (orderer.Vertices.Count); foreach (UpdateCommand[] commands in orderer.TryStagedTopologicalSort()) { // at each depth in the topological sort, apply sorting based on operation, // table/entityset and keys to ensure consistent update ordering Array.Sort (commands); orderedCommands.AddRange(commands); } // if there is a remainder, it suggests a cycle. this.ValidateGraphPostSort(orderer); 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(int 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; } // check that the column is actually mapped to an entity property if (context.RecordOrdinal != PropagatorResult.NullOrdinal) { object value = generatedValue.Value; 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); //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 List changes = new List (tables.Count); 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, 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); } // construct DbFunctionCommandTree including implict return type DbFunctionCommandTree tree = new DbFunctionCommandTree(m_commandTreeContext.MetadataWorkspace, DataSpace.SSpace, functionMapping.Function, resultType); // add function parameters foreach (FunctionParameter paramInfo in functionMapping.Function.Parameters) { tree.AddParameter(paramInfo.Name, paramInfo.TypeUsage); } 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); 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(StructuralType type) { ExtractorMetadata metadata; if (!m_extractorMetadata.TryGetValue(type, out metadata)) { metadata = new ExtractorMetadata(type, this); m_extractorMetadata.Add(type, metadata); } return metadata; } ////// Validates dependency graph after sort has been performed to ensure there were no cycles /// /// Dependency orderer graph to validate private void ValidateGraphPostSort(UpdateCommandOrderer orderer) { Debug.Assert(null != orderer, "Caller must verify parameters are not null"); if (0 != orderer.Remainder.Count) { SetstateEntries = new Set (); foreach (UpdateCommand command in orderer.Remainder) { stateEntries.AddRange(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 entries to register referential integrity constraints // for server-generation (only relationship are interesting, and relationships can only // be added or deleted) foreach (IEntityStateEntry entry in m_stateManager.GetEntityStateEntries(EntityState.Added | EntityState.Deleted)) { RegisterReferentialConstraints(entry); } 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)) { // 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)) { // 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, null); } } } } /// /// 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"); // make sure the view loader has loaded all information about this extent m_viewLoader.SyncInitializeEntitySet(extent, this.MetadataWorkspace); // check for required ends of relationships AssociationSet associationSet = extent as AssociationSet; if (null != associationSet) { AssociationSetMetadata associationSetMetadata = m_viewLoader.GetAssociationSetMetadata(associationSet); 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)) { // 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. /// internal enum ModificationOperator : byte { Update, Insert, Delete } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. //---------------------------------------------------------------------- //// 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_commandTreeContext = new DbQueryCommandTree(metadataWorkspace, DataSpace.CSpace); 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 (EqualityComparer .Default); // 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 DbCommandTree m_commandTreeContext; 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 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_commandTreeContext.MetadataWorkspace; } } ////// Gets key manager that handles interpretation of keys (including resolution of /// referential-integrity/common value 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); long principalIdentifier = this.KeyManager.GetKeyIdentifierForMemberOffset(principalKey, principalOffset, principalKeyMemberCount); long dependentIdentifier = this.KeyManager.GetKeyIdentifierForMemberOffset(dependentKey, dependentOffset, dependentKeyMemberCount); // 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 >(); List 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; int 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 List 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); List orderedCommands = new List (orderer.Vertices.Count); foreach (UpdateCommand[] commands in orderer.TryStagedTopologicalSort()) { // at each depth in the topological sort, apply sorting based on operation, // table/entityset and keys to ensure consistent update ordering Array.Sort (commands); orderedCommands.AddRange(commands); } // if there is a remainder, it suggests a cycle. this.ValidateGraphPostSort(orderer); 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(int 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; } // check that the column is actually mapped to an entity property if (context.RecordOrdinal != PropagatorResult.NullOrdinal) { object value = generatedValue.Value; 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); //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 List changes = new List (tables.Count); 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, 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); } // construct DbFunctionCommandTree including implict return type DbFunctionCommandTree tree = new DbFunctionCommandTree(m_commandTreeContext.MetadataWorkspace, DataSpace.SSpace, functionMapping.Function, resultType); // add function parameters foreach (FunctionParameter paramInfo in functionMapping.Function.Parameters) { tree.AddParameter(paramInfo.Name, paramInfo.TypeUsage); } 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); 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(StructuralType type) { ExtractorMetadata metadata; if (!m_extractorMetadata.TryGetValue(type, out metadata)) { metadata = new ExtractorMetadata(type, this); m_extractorMetadata.Add(type, metadata); } return metadata; } ////// Validates dependency graph after sort has been performed to ensure there were no cycles /// /// Dependency orderer graph to validate private void ValidateGraphPostSort(UpdateCommandOrderer orderer) { Debug.Assert(null != orderer, "Caller must verify parameters are not null"); if (0 != orderer.Remainder.Count) { SetstateEntries = new Set (); foreach (UpdateCommand command in orderer.Remainder) { stateEntries.AddRange(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 entries to register referential integrity constraints // for server-generation (only relationship are interesting, and relationships can only // be added or deleted) foreach (IEntityStateEntry entry in m_stateManager.GetEntityStateEntries(EntityState.Added | EntityState.Deleted)) { RegisterReferentialConstraints(entry); } 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)) { // 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)) { // 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, null); } } } } /// /// 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"); // make sure the view loader has loaded all information about this extent m_viewLoader.SyncInitializeEntitySet(extent, this.MetadataWorkspace); // check for required ends of relationships AssociationSet associationSet = extent as AssociationSet; if (null != associationSet) { AssociationSetMetadata associationSetMetadata = m_viewLoader.GetAssociationSetMetadata(associationSet); 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)) { // 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. /// internal enum ModificationOperator : byte { Update, Insert, Delete } } // 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
- ToolStripProgressBar.cs
- util.cs
- DiscriminatorMap.cs
- ModelItemExtensions.cs
- WebPartDisplayModeCancelEventArgs.cs
- InternalBufferOverflowException.cs
- LoginDesignerUtil.cs
- WindowPattern.cs
- RelatedView.cs
- PackageStore.cs
- CursorConverter.cs
- VirtualDirectoryMapping.cs
- BasicBrowserDialog.cs
- AssemblyNameProxy.cs
- OrderedDictionary.cs
- RadioButton.cs
- NetWebProxyFinder.cs
- ObjectParameterCollection.cs
- SqlTypesSchemaImporter.cs
- KeyboardNavigation.cs
- RadioButtonFlatAdapter.cs
- QfeChecker.cs
- SecurityProtocolCorrelationState.cs
- SystemWebSectionGroup.cs
- SchemeSettingElement.cs
- PolicyUtility.cs
- NodeLabelEditEvent.cs
- NavigationProperty.cs
- WebPartConnection.cs
- MemberAccessException.cs
- WindowsSysHeader.cs
- FlowDocument.cs
- DataGridViewRowHeightInfoPushedEventArgs.cs
- OdbcCommand.cs
- LabelDesigner.cs
- securestring.cs
- PageThemeParser.cs
- WebServiceErrorEvent.cs
- SafeMemoryMappedViewHandle.cs
- TextBoxAutoCompleteSourceConverter.cs
- IdnMapping.cs
- HwndHostAutomationPeer.cs
- RealProxy.cs
- SystemDropShadowChrome.cs
- WindowsFormsHost.cs
- BulletDecorator.cs
- CapabilitiesPattern.cs
- FrameworkElementFactoryMarkupObject.cs
- DataGridViewColumn.cs
- Odbc32.cs
- RegionData.cs
- MessageQueueCriteria.cs
- XmlSchemaSimpleTypeUnion.cs
- PermissionToken.cs
- Point3D.cs
- CustomAttribute.cs
- IdentityHolder.cs
- InvalidateEvent.cs
- BamlLocalizabilityResolver.cs
- PageAsyncTask.cs
- WebPartDisplayModeEventArgs.cs
- MetadataItem.cs
- Pool.cs
- UrlMappingsSection.cs
- BindToObject.cs
- DataGridViewDataErrorEventArgs.cs
- CapabilitiesState.cs
- ListViewTableCell.cs
- SqlDataSourceSelectingEventArgs.cs
- _ShellExpression.cs
- ZipIOExtraFieldZip64Element.cs
- StrokeCollection2.cs
- PropertyValueChangedEvent.cs
- TextEmbeddedObject.cs
- SqlDataSourceEnumerator.cs
- TextEffectCollection.cs
- AdapterDictionary.cs
- BridgeDataRecord.cs
- DesigntimeLicenseContextSerializer.cs
- SqlDataSourceFilteringEventArgs.cs
- SqlTriggerAttribute.cs
- DriveNotFoundException.cs
- SessionPageStatePersister.cs
- FormViewDeletedEventArgs.cs
- LocatorBase.cs
- CompiledELinqQueryState.cs
- AnchoredBlock.cs
- InternalPermissions.cs
- RuleDefinitions.cs
- CryptoStream.cs
- SamlAttributeStatement.cs
- FastEncoder.cs
- Regex.cs
- NavigationEventArgs.cs
- IteratorFilter.cs
- ReadOnlyTernaryTree.cs
- ClipboardData.cs
- Compiler.cs
- InvokeMethod.cs
- EncryptedType.cs