Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / ndp / fx / src / DataEntity / System / Data / Map / Update / Internal / UpdateTranslator.cs / 4 / 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:
///
/// - 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)
///
///
internal partial class UpdateTranslator
{
#region Constructors
///
/// 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 IEqualityComparer KeyComparer;
#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 IEnumerable GetDynamicModifiedExtents()
{
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 IEnumerable GetFunctionModifiedExtents()
{
return m_functionChanges.Keys;
}
///
/// Produce dynamic store commands for this translator's changes.
///
/// Database commands in a safe order
private IEnumerable ProduceDynamicCommands()
{
// 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)
{
Set stateEntries = 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 (KeyValuePair required in m_requiredEntities)
{
EntityKey key = required.Key;
if (!m_knownEntityKeys.Contains(key))
{
// pull the value into the translator if we don't already it
IEntityStateEntry requiredEntry;
if (m_stateManager.TryGetEntityStateEntry(key, out requiredEntry))
{
// 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, Set keySet)
{
// Note: an entity is ancillary iff. it is unchanged (otherwise it is tracked as a "standard" changed entity)
IEntityStateEntry endEntry;
if (m_stateManager.TryGetEntityStateEntry(key, out endEntry) && // make sure the entity is tracked
!endEntry.IsKeyEntry && // make sure the entity is not a stub
endEntry.State == EntityState.Unchanged) // if the entity is being modified, it's already included anyways
{
keySet.Add(key);
}
}
private void ValidateRecord(EntitySetBase extent, IExtendedDataRecord record, IEntityStateEntry entry)
{
Debug.Assert(null != extent, "must be verified by caller");
DataRecordInfo recordInfo;
if ((null == record) ||
(null == (recordInfo = record.DataRecordInfo)) ||
(null == recordInfo.RecordType))
{
throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.InvalidStateEntry, 2);
}
VerifyExtent(MetadataWorkspace, extent);
// additional validation happens lazily as values are loaded from the record
}
// Verifies the given extent is present in the given workspace.
private static void VerifyExtent(MetadataWorkspace workspace, EntitySetBase extent)
{
// get the container to which the given extent belongs
EntityContainer actualContainer = extent.EntityContainer;
// try to retrieve the container in the given workspace
EntityContainer referenceContainer = null;
if (null != actualContainer)
{
workspace.TryGetEntityContainer(
actualContainer.Name, actualContainer.DataSpace, out referenceContainer);
}
// determine if the given extent lives in a container from the given workspace
// (the item collections for each container are reference equivalent when they are declared in the
// same item collection)
if (null == actualContainer || null == referenceContainer ||
!Object.ReferenceEquals(actualContainer, referenceContainer))
{
//
throw EntityUtil.Update(System.Data.Entity.Strings.Update_WorkspaceMismatch, null);
}
}
private void LoadStateEntry(IEntityStateEntry stateEntry)
{
Debug.Assert(null != stateEntry, "state entry must exist");
// make sure the state entry doesn't contain invalid data and register it with the
// update pipeline
ValidateAndRegisterStateEntry(stateEntry);
// use data structure internal to the update pipeline instead of the raw state entry
ExtractedStateEntry extractedStateEntry = new ExtractedStateEntry(this, stateEntry);
// figure out if this state entry is being handled by a function (stored procedure) or
// through dynamic SQL
EntitySetBase extent = stateEntry.EntitySet;
if (null == m_viewLoader.GetFunctionMappingTranslator(extent))
{
// 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 List GetExtentFunctionModifications(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:
///
/// - 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)
///
///
internal partial class UpdateTranslator
{
#region Constructors
///
/// 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 IEqualityComparer KeyComparer;
#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 IEnumerable GetDynamicModifiedExtents()
{
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 IEnumerable GetFunctionModifiedExtents()
{
return m_functionChanges.Keys;
}
///
/// Produce dynamic store commands for this translator's changes.
///
/// Database commands in a safe order
private IEnumerable ProduceDynamicCommands()
{
// 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)
{
Set stateEntries = 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 (KeyValuePair required in m_requiredEntities)
{
EntityKey key = required.Key;
if (!m_knownEntityKeys.Contains(key))
{
// pull the value into the translator if we don't already it
IEntityStateEntry requiredEntry;
if (m_stateManager.TryGetEntityStateEntry(key, out requiredEntry))
{
// 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, Set keySet)
{
// Note: an entity is ancillary iff. it is unchanged (otherwise it is tracked as a "standard" changed entity)
IEntityStateEntry endEntry;
if (m_stateManager.TryGetEntityStateEntry(key, out endEntry) && // make sure the entity is tracked
!endEntry.IsKeyEntry && // make sure the entity is not a stub
endEntry.State == EntityState.Unchanged) // if the entity is being modified, it's already included anyways
{
keySet.Add(key);
}
}
private void ValidateRecord(EntitySetBase extent, IExtendedDataRecord record, IEntityStateEntry entry)
{
Debug.Assert(null != extent, "must be verified by caller");
DataRecordInfo recordInfo;
if ((null == record) ||
(null == (recordInfo = record.DataRecordInfo)) ||
(null == recordInfo.RecordType))
{
throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.InvalidStateEntry, 2);
}
VerifyExtent(MetadataWorkspace, extent);
// additional validation happens lazily as values are loaded from the record
}
// Verifies the given extent is present in the given workspace.
private static void VerifyExtent(MetadataWorkspace workspace, EntitySetBase extent)
{
// get the container to which the given extent belongs
EntityContainer actualContainer = extent.EntityContainer;
// try to retrieve the container in the given workspace
EntityContainer referenceContainer = null;
if (null != actualContainer)
{
workspace.TryGetEntityContainer(
actualContainer.Name, actualContainer.DataSpace, out referenceContainer);
}
// determine if the given extent lives in a container from the given workspace
// (the item collections for each container are reference equivalent when they are declared in the
// same item collection)
if (null == actualContainer || null == referenceContainer ||
!Object.ReferenceEquals(actualContainer, referenceContainer))
{
//
throw EntityUtil.Update(System.Data.Entity.Strings.Update_WorkspaceMismatch, null);
}
}
private void LoadStateEntry(IEntityStateEntry stateEntry)
{
Debug.Assert(null != stateEntry, "state entry must exist");
// make sure the state entry doesn't contain invalid data and register it with the
// update pipeline
ValidateAndRegisterStateEntry(stateEntry);
// use data structure internal to the update pipeline instead of the raw state entry
ExtractedStateEntry extractedStateEntry = new ExtractedStateEntry(this, stateEntry);
// figure out if this state entry is being handled by a function (stored procedure) or
// through dynamic SQL
EntitySetBase extent = stateEntry.EntitySet;
if (null == m_viewLoader.GetFunctionMappingTranslator(extent))
{
// 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 List GetExtentFunctionModifications(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
- CompilerError.cs
- StorageMappingFragment.cs
- AsymmetricAlgorithm.cs
- WeakReference.cs
- DockAndAnchorLayout.cs
- IgnoreFileBuildProvider.cs
- ExeConfigurationFileMap.cs
- Restrictions.cs
- CachedPathData.cs
- Semaphore.cs
- TextEndOfLine.cs
- util.cs
- VisualStateManager.cs
- LineServicesRun.cs
- FileController.cs
- URLIdentityPermission.cs
- JapaneseCalendar.cs
- GuidConverter.cs
- BoolExpressionVisitors.cs
- ValidationHelpers.cs
- ExceptionValidationRule.cs
- BezierSegment.cs
- InkPresenter.cs
- CodeTypeDeclarationCollection.cs
- CounterSetInstance.cs
- ConnectivityStatus.cs
- Graph.cs
- BulletedListEventArgs.cs
- DataPagerFieldItem.cs
- FixedSOMLineCollection.cs
- MessageSecurityOverTcpElement.cs
- ToolStripContentPanelRenderEventArgs.cs
- CultureInfo.cs
- StateItem.cs
- ActivationArguments.cs
- ScriptMethodAttribute.cs
- X509Extension.cs
- EpmHelper.cs
- DocumentEventArgs.cs
- DataExpression.cs
- ProxyHelper.cs
- LassoSelectionBehavior.cs
- SafeRightsManagementEnvironmentHandle.cs
- DataGridColumnFloatingHeader.cs
- SectionInformation.cs
- PublisherIdentityPermission.cs
- HasActivatableWorkflowEvent.cs
- ThreadAttributes.cs
- Wildcard.cs
- ExceptionTranslationTable.cs
- TraceListener.cs
- Attributes.cs
- Overlapped.cs
- ObjectCacheHost.cs
- TextBoxLine.cs
- _NetRes.cs
- PathFigureCollectionValueSerializer.cs
- WebPartHelpVerb.cs
- ToolStripItemEventArgs.cs
- LocationFactory.cs
- ListViewPagedDataSource.cs
- TemplatedMailWebEventProvider.cs
- DataStorage.cs
- DetailsViewRowCollection.cs
- TdsRecordBufferSetter.cs
- CanExecuteRoutedEventArgs.cs
- Latin1Encoding.cs
- ToolConsole.cs
- BrowserCapabilitiesCompiler.cs
- CheckPair.cs
- CustomLineCap.cs
- BitmapEffectState.cs
- Effect.cs
- XmlAggregates.cs
- WeakEventTable.cs
- AuthenticationSection.cs
- XmlWellformedWriter.cs
- ChangePassword.cs
- SqlBuilder.cs
- ReachPageContentSerializer.cs
- VisualStyleTypesAndProperties.cs
- SecurityMessageProperty.cs
- CommandEventArgs.cs
- AtomServiceDocumentSerializer.cs
- OdbcParameterCollection.cs
- DbCommandDefinition.cs
- Accessible.cs
- jithelpers.cs
- CodeDirectoryCompiler.cs
- WindowsListView.cs
- PageThemeCodeDomTreeGenerator.cs
- ComponentResourceKey.cs
- RegistrationServices.cs
- Baml6Assembly.cs
- AssemblyNameUtility.cs
- WindowsMenu.cs
- XsltLibrary.cs
- CacheEntry.cs
- _FtpControlStream.cs
- TiffBitmapDecoder.cs