internal ExtractedStateEntry(EntityState state, PropagatorResult original, PropagatorResult current, IEntityStateEntry source) { State = state; Original = original; Current = current; Source = source; }
internal DynamicUpdateCommand(TableChangeProcessor processor, UpdateTranslator translator, ModificationOperator op, PropagatorResult originalValues, PropagatorResult currentValues, DbModificationCommandTree tree, Dictionary<int, string> outputIdentifiers) : base(originalValues, currentValues) { m_processor = EntityUtil.CheckArgumentNull(processor, "processor"); m_operator = op; m_modificationCommandTree = EntityUtil.CheckArgumentNull(tree, "commandTree"); m_outputIdentifiers = outputIdentifiers; // may be null (not all commands have output identifiers) // initialize identifier information (supports lateral propagation of server gen values) if (ModificationOperator.Insert == op || ModificationOperator.Update == op) { const int capacity = 2; // "average" number of identifiers per row m_inputIdentifiers = new List<KeyValuePair<int ,DbSetClause>>(capacity); foreach (KeyValuePair<EdmMember, PropagatorResult> member in Helper.PairEnumerations(TypeHelpers.GetAllStructuralMembers(this.CurrentValues.StructuralType), this.CurrentValues.GetMemberValues())) { DbSetClause setter; int identifier = member.Value.Identifier; if (PropagatorResult.NullIdentifier != identifier && TryGetSetterExpression(tree, member.Key, op, out setter)) // can find corresponding setter { foreach (int principal in translator.KeyManager.GetPrincipals(identifier)) { m_inputIdentifiers.Add(new KeyValuePair<int, DbSetClause>(principal, setter)); } } } } }
/// <summary> /// Builds an update command. /// </summary> /// <param name="oldRow">Old value of the row being updated.</param> /// <param name="newRow">New value for the row being updated.</param> /// <param name="processor">Context for the table containing row.</param> /// <returns>Update command.</returns> internal UpdateCommand BuildUpdateCommand( PropagatorResult oldRow, PropagatorResult newRow, TableChangeProcessor processor) { // If we're updating a row, the row may not need to be touched (e.g., no concurrency validation required) var rowMustBeTouched = false; var target = GetTarget(processor); // Create set clauses and returning parameter Dictionary<int, string> outputIdentifiers; DbExpression returning; var setClauses = new List<DbModificationClause>(); foreach (var clause in BuildSetClauses( target, newRow, oldRow, processor, /* insertMode */ false, out outputIdentifiers, out returning, ref rowMustBeTouched)) { setClauses.Add(clause); } // Construct predicate identifying the row to modify var predicate = BuildPredicate(target, oldRow, newRow, processor, ref rowMustBeTouched); if (0 == setClauses.Count) { if (rowMustBeTouched) { var stateEntries = new List<IEntityStateEntry>(); stateEntries.AddRange( SourceInterpreter.GetAllStateEntries( oldRow, m_translator, processor.Table)); stateEntries.AddRange( SourceInterpreter.GetAllStateEntries( newRow, m_translator, processor.Table)); if (stateEntries.All(it => (it.State == EntityState.Unchanged))) { rowMustBeTouched = false; } } // Determine if there is nothing to do (i.e., no values to set, // no computed columns, and no concurrency validation required) if (!rowMustBeTouched) { return null; } } // Initialize DML command tree var commandTree = new DbUpdateCommandTree( m_translator.MetadataWorkspace, DataSpace.SSpace, target, predicate, setClauses.AsReadOnly(), returning); // Create command UpdateCommand command = new DynamicUpdateCommand( processor, m_translator, ModificationOperator.Update, oldRow, newRow, commandTree, outputIdentifiers); return command; }
/// <summary> /// Finds all markup associated with the given source. /// </summary> /// <param name="source">Source expression. Must not be null.</param> /// <param name="translator">Translator containing session information.</param> /// <param name="sourceTable">Table from which the exception was thrown (must not be null).</param> /// <returns>Markup.</returns> internal static ReadOnlyCollection<IEntityStateEntry> GetAllStateEntries(PropagatorResult source, UpdateTranslator translator, EntitySet sourceTable) { Debug.Assert(null != source); Debug.Assert(null != translator); Debug.Assert(null != sourceTable); SourceInterpreter interpreter = new SourceInterpreter(translator, sourceTable); interpreter.RetrieveResultMarkup(source); return new ReadOnlyCollection<IEntityStateEntry>(interpreter.m_stateEntries); }
private void RetrieveResultMarkup(PropagatorResult source) { Debug.Assert(null != source); if (source.Identifier != PropagatorResult.NullIdentifier) { // state entries travel with identifiers. several state entries may be merged // into a single identifier result via joins in the update mapping view do { if (null != source.StateEntry) { m_stateEntries.Add(source.StateEntry); if (source.Identifier != PropagatorResult.NullIdentifier) { // if this is an identifier, it may also be registered with an "owner". // Return the owner as well if the owner is also mapped to this table. PropagatorResult owner; if (m_translator.KeyManager.TryGetIdentifierOwner(source.Identifier, out owner) && null != owner.StateEntry && ExtentInScope(owner.StateEntry.EntitySet)) { m_stateEntries.Add(owner.StateEntry); } // Check if are any referential constraints. If so, the entity key // implies that the dependent relationship instance is also being // handled in this result. foreach (var stateEntry in m_translator.KeyManager.GetDependentStateEntries(source.Identifier)) { m_stateEntries.Add(stateEntry); } } } source = source.Next; } while (null != source); } else if (!source.IsSimple && !source.IsNull) { // walk children foreach (var child in source.GetMemberValues()) { RetrieveResultMarkup(child); } } }
/// <summary> /// Construct a new placeholder with the shape of the given placeholder. Key values are /// injected into the resulting place holder and default values are substituted with /// either propagator constants or progagator nulls depending on the mode established /// by the <paramref name="mode"/> flag. /// </summary> /// <remarks> /// The key is essentially an array of values. The key map indicates that for a particular /// placeholder an expression (keyMap.Keys) corresponds to some ordinal in the key array. /// </remarks> /// <param name="placeholder">Placeholder to clone</param> /// <param name="key">Key to substitute</param> /// <param name="placeholderKey">Key elements in the placeholder (ordinally aligned with 'key')</param> /// <param name="mode">Mode of operation.</param> /// <returns>Cloned placeholder with key values</returns> internal static PropagatorResult Populate( PropagatorResult placeholder, CompositeKey key, CompositeKey placeholderKey, PopulateMode mode) { //Contract.Requires(placeholder != null); //Contract.Requires(key != null); //Contract.Requires(placeholderKey != null); // Figure out which flags to apply to generated elements. var isNull = mode == PopulateMode.NullModified || mode == PopulateMode.NullPreserve; var preserve = mode == PopulateMode.NullPreserve || mode == PopulateMode.Unknown; var flags = PropagatorFlags.NoFlags; if (!isNull) { flags |= PropagatorFlags.Unknown; } // only null values are known if (preserve) { flags |= PropagatorFlags.Preserve; } var result = placeholder.Replace( node => { // See if this is a key element var keyIndex = -1; for (var i = 0; i < placeholderKey.KeyComponents.Length; i++) { if (placeholderKey.KeyComponents[i] == node) { keyIndex = i; break; } } if (keyIndex != -1) { // Key value. return key.KeyComponents[keyIndex]; } else { // for simple entries, just return using the markup context for this // populator var value = isNull ? null : node.GetSimpleValue(); return PropagatorResult.CreateSimpleValue(flags, value); } }); return result; }
/// <summary> /// Builds a delete command. /// </summary> /// <param name="oldRow">Value of the row being deleted.</param> /// <param name="processor">Context for the table containing row.</param> /// <returns>Delete command.</returns> internal UpdateCommand BuildDeleteCommand(PropagatorResult oldRow, TableChangeProcessor processor) { // If we're deleting a row, the row must always be touched bool rowMustBeTouched = true; // Initialize DML command tree DbExpressionBinding target = GetTarget(processor); // Create delete predicate DbExpression predicate = BuildPredicate(target, oldRow, null, processor, ref rowMustBeTouched); DbDeleteCommandTree commandTree = new DbDeleteCommandTree(m_translator.MetadataWorkspace, DataSpace.SSpace, target, predicate); // Set command // Initialize delete command UpdateCommand command = new DynamicUpdateCommand(processor, m_translator, ModificationOperator.Delete, oldRow, null, commandTree, null); return command; }
/// <summary> /// Construct a new placeholder with the shape of the given placeholder. Key values are /// injected into the resulting place holder and default values are substituted with /// either propagator constants or progagator nulls depending on the mode established /// by the <paramref name="mode"/> flag. /// </summary> /// <remarks> /// The key is essentially an array of values. The key map indicates that for a particular /// placeholder an expression (keyMap.Keys) corresponds to some ordinal in the key array. /// </remarks> /// <param name="placeholder">Placeholder to clone</param> /// <param name="key">Key to substitute</param> /// <param name="placeholderKey">Key elements in the placeholder (ordinally aligned with 'key')</param> /// <param name="mode">Mode of operation.</param> /// <param name="translator">Translator context.</param> /// <returns>Cloned placeholder with key values</returns> internal static PropagatorResult Populate(PropagatorResult placeholder, CompositeKey key, CompositeKey placeholderKey, PopulateMode mode, UpdateTranslator translator) { EntityUtil.CheckArgumentNull(placeholder, "placeholder"); EntityUtil.CheckArgumentNull(key, "key"); EntityUtil.CheckArgumentNull(placeholderKey, "placeholderKey"); EntityUtil.CheckArgumentNull(translator, "translator"); // Figure out which flags to apply to generated elements. bool isNull = mode == PopulateMode.NullModified || mode == PopulateMode.NullPreserve; bool preserve = mode == PopulateMode.NullPreserve || mode == PopulateMode.Unknown; PropagatorFlags flags = PropagatorFlags.NoFlags; if (!isNull) { flags |= PropagatorFlags.Unknown; } // only null values are known if (preserve) { flags |= PropagatorFlags.Preserve; } PropagatorResult result = placeholder.Replace(node => { // See if this is a key element int keyIndex = -1; for (int i = 0; i < placeholderKey.KeyComponents.Length; i++) { if (placeholderKey.KeyComponents[i] == node) { keyIndex = i; break; } } if (keyIndex != -1) { // Key value. return key.KeyComponents[keyIndex]; } else { // for simple entries, just return using the markup context for this // populator object value = isNull ? null : node.GetSimpleValue(); return PropagatorResult.CreateSimpleValue(flags, value); } }); return result; }
internal DynamicUpdateCommand( TableChangeProcessor processor, UpdateTranslator translator, ModificationOperator modificationOperator, PropagatorResult originalValues, PropagatorResult currentValues, DbModificationCommandTree tree, Dictionary<int, string> outputIdentifiers) : base(translator, originalValues, currentValues) { //Contract.Requires(processor != null); //Contract.Requires(translator != null); //Contract.Requires(tree != null); _processor = processor; _operator = modificationOperator; _modificationCommandTree = tree; _outputIdentifiers = outputIdentifiers; // may be null (not all commands have output identifiers) // initialize identifier information (supports lateral propagation of server gen values) if (ModificationOperator.Insert == modificationOperator || ModificationOperator.Update == modificationOperator) { const int capacity = 2; // "average" number of identifiers per row _inputIdentifiers = new List<KeyValuePair<int, DbSetClause>>(capacity); foreach (var member in Helper.PairEnumerations( TypeHelpers.GetAllStructuralMembers(CurrentValues.StructuralType), CurrentValues.GetMemberValues())) { DbSetClause setter; var identifier = member.Value.Identifier; if (PropagatorResult.NullIdentifier != identifier && TryGetSetterExpression(tree, member.Key, modificationOperator, out setter)) // can find corresponding setter { foreach (var principal in translator.KeyManager.GetPrincipals(identifier)) { _inputIdentifiers.Add(new KeyValuePair<int, DbSetClause>(principal, setter)); } } } } }
private void AddReferencedEntities(UpdateTranslator translator, PropagatorResult result, KeyToListMap <EntityKey, UpdateCommand> referencedEntities) { foreach (PropagatorResult property in result.GetMemberValues()) { if (property.IsSimple && property.Identifier != PropagatorResult.NullIdentifier && (PropagatorFlags.ForeignKey == (property.PropagatorFlags & PropagatorFlags.ForeignKey))) { foreach (int principal in translator.KeyManager.GetDirectReferences(property.Identifier)) { PropagatorResult owner; if (translator.KeyManager.TryGetIdentifierOwner(principal, out owner) && null != owner.StateEntry) { Debug.Assert(!owner.StateEntry.IsRelationship, "owner must not be a relationship"); referencedEntities.Add(owner.StateEntry.EntityKey, this); } } } } }
internal ExtractedStateEntry(UpdateTranslator translator, IEntityStateEntry stateEntry) { Debug.Assert(null != stateEntry, "stateEntry must not be null"); this.State = stateEntry.State; this.Source = stateEntry; switch (stateEntry.State) { case EntityState.Deleted: this.Original = translator.RecordConverter.ConvertOriginalValuesToPropagatorResult( stateEntry, ModifiedPropertiesBehavior.AllModified); this.Current = null; break; case EntityState.Unchanged: this.Original = translator.RecordConverter.ConvertOriginalValuesToPropagatorResult( stateEntry, ModifiedPropertiesBehavior.NoneModified); this.Current = translator.RecordConverter.ConvertCurrentValuesToPropagatorResult( stateEntry, ModifiedPropertiesBehavior.NoneModified); break; case EntityState.Modified: this.Original = translator.RecordConverter.ConvertOriginalValuesToPropagatorResult( stateEntry, ModifiedPropertiesBehavior.SomeModified); this.Current = translator.RecordConverter.ConvertCurrentValuesToPropagatorResult( stateEntry, ModifiedPropertiesBehavior.SomeModified); break; case EntityState.Added: this.Original = null; this.Current = translator.RecordConverter.ConvertCurrentValuesToPropagatorResult( stateEntry, ModifiedPropertiesBehavior.AllModified); break; default: Debug.Fail("unexpected IEntityStateEntry.State for entity " + stateEntry.State); this.Original = null; this.Current = null; break; } }
internal ExtractedStateEntry(UpdateTranslator translator, IEntityStateEntry stateEntry) { //Contract.Requires(translator != null); //Contract.Requires(stateEntry != null); State = stateEntry.State; Source = stateEntry; switch (stateEntry.State) { case EntityState.Deleted: Original = translator.RecordConverter.ConvertOriginalValuesToPropagatorResult( stateEntry, ModifiedPropertiesBehavior.AllModified); Current = null; break; case EntityState.Unchanged: Original = translator.RecordConverter.ConvertOriginalValuesToPropagatorResult( stateEntry, ModifiedPropertiesBehavior.NoneModified); Current = translator.RecordConverter.ConvertCurrentValuesToPropagatorResult( stateEntry, ModifiedPropertiesBehavior.NoneModified); break; case EntityState.Modified: Original = translator.RecordConverter.ConvertOriginalValuesToPropagatorResult( stateEntry, ModifiedPropertiesBehavior.SomeModified); Current = translator.RecordConverter.ConvertCurrentValuesToPropagatorResult( stateEntry, ModifiedPropertiesBehavior.SomeModified); break; case EntityState.Added: Original = null; Current = translator.RecordConverter.ConvertCurrentValuesToPropagatorResult( stateEntry, ModifiedPropertiesBehavior.AllModified); break; default: Contract.Assert(false, "Unexpected IEntityStateEntry.State for entity " + stateEntry.State); Original = null; Current = null; break; } }
/// <summary> /// Produce a tuple containing joined rows. /// </summary> /// <param name="left">Left row.</param> /// <param name="right">Right row.</param> /// <param name="leftKey">Key used to join left element.</param> /// <param name="rightKey">Key used to join right element.</param> /// <param name="result">Result change node; used for type information.</param> /// <returns>Result of joining the input rows.</returns> private PropagatorResult CreateResultTuple(Tuple <CompositeKey, PropagatorResult> left, Tuple <CompositeKey, PropagatorResult> right, ChangeNode result) { // using ref compare to avoid triggering value based CompositeKey leftKey = left.Item1; CompositeKey rightKey = right.Item1; Dictionary <PropagatorResult, PropagatorResult> map = null; if (!object.ReferenceEquals(null, leftKey) && !object.ReferenceEquals(null, rightKey) && !object.ReferenceEquals(leftKey, rightKey)) { // Merge key values from the left and the right (since they're equal, there's a possibility we'll // project values only from the left or the right hand side and lose important context.) CompositeKey mergedKey = leftKey.Merge(m_parent.m_updateTranslator.KeyManager, rightKey); // create a dictionary so that we can replace key values with merged key values (carrying context // from both sides) map = new Dictionary <PropagatorResult, PropagatorResult>(); for (int i = 0; i < leftKey.KeyComponents.Length; i++) { map[leftKey.KeyComponents[i]] = mergedKey.KeyComponents[i]; map[rightKey.KeyComponents[i]] = mergedKey.KeyComponents[i]; } } PropagatorResult[] joinRecordValues = new PropagatorResult[2]; joinRecordValues[0] = left.Item2; joinRecordValues[1] = right.Item2; PropagatorResult join = PropagatorResult.CreateStructuralValue(joinRecordValues, (StructuralType)result.ElementType.EdmType, false); // replace with merged key values as appropriate if (null != map) { PropagatorResult replacement; join = join.Replace(original => map.TryGetValue(original, out replacement) ? replacement : original); } return(join); }
internal override PropagatorResult Merge(KeyManager keyManager, PropagatorResult other) { KeyValue otherKey = other as KeyValue; if (null == otherKey) { EntityUtil.InternalError(EntityUtil.InternalErrorCode.UpdatePipelineResultRequestInvalid, 0, "KeyValue.Merge"); } // Determine which key (this or otherKey) is first in the chain. Principal keys take // precedence over dependent keys and entities take precedence over relationships. if (this.Identifier != otherKey.Identifier) { // Find principal (if any) if (keyManager.GetPrincipals(otherKey.Identifier).Contains(this.Identifier)) { return(this.ReplicateResultWithNewNext(otherKey)); } else { return(otherKey.ReplicateResultWithNewNext(this)); } } else { // Entity takes precedence of relationship if (null == m_stateEntry || m_stateEntry.IsRelationship) { return(otherKey.ReplicateResultWithNewNext(this)); } else { return(this.ReplicateResultWithNewNext(otherKey)); } } }
// extracts key values from row expression private static CompositeKey ExtractKey(PropagatorResult change, ReadOnlyCollection<DbExpression> keySelectors, Propagator parent) { Debug.Assert(null != change && null != keySelectors && null != parent); PropagatorResult[] keyValues = new PropagatorResult[keySelectors.Count]; for (int i = 0; i < keySelectors.Count; i++) { PropagatorResult constant = Evaluator.Evaluate(keySelectors[i], change, parent); keyValues[i] = constant; } return new CompositeKey(keyValues); }
// Find "sanctioned" default value private static void GetPropagatorResultForPrimitiveType(PrimitiveType primitiveType, out PropagatorResult result) { object value; PrimitiveTypeKind primitiveTypeKind = primitiveType.PrimitiveTypeKind; if (!s_typeDefaultMap.TryGetValue(primitiveTypeKind, out value)) { // If none exists, default to lowest common denominator for constants value = default(byte); } // Return a new constant expression flagged as unknown since the value is only there for // show. (Not entirely for show, because null constraints may require a value for a record, // whether that record is a placeholder or not). result = PropagatorResult.CreateSimpleValue(PropagatorFlags.NoFlags, value); }
/// <summary> /// Specialization of <see cref="CreatePlaceholder" /> for a relationship set extent. /// </summary> /// <param name="associationSet"></param> /// <returns></returns> private PropagatorResult CreateAssociationSetPlaceholder(AssociationSet associationSet) { Debug.Assert(null != associationSet, "Caller must verify parameters are not null"); var endMetadata = associationSet.ElementType.AssociationEndMembers; PropagatorResult[] endReferenceValues = new PropagatorResult[endMetadata.Count]; // Create a reference expression for each end in the relationship for (int endOrdinal = 0; endOrdinal < endMetadata.Count; endOrdinal++) { var end = endMetadata[endOrdinal]; EntityType entityType = (EntityType)((RefType)end.TypeUsage.EdmType).ElementType; // Retrieve key values for this end PropagatorResult[] keyValues = new PropagatorResult[entityType.KeyMembers.Count]; for (int memberOrdinal = 0; memberOrdinal < entityType.KeyMembers.Count; memberOrdinal++) { EdmMember keyMember = entityType.KeyMembers[memberOrdinal]; PropagatorResult keyValue = CreateMemberPlaceholder(keyMember); keyValues[memberOrdinal] = keyValue; } RowType endType = entityType.GetKeyRowType(m_parent.MetadataWorkspace); PropagatorResult refKeys = PropagatorResult.CreateStructuralValue(keyValues, endType, false); endReferenceValues[endOrdinal] = refKeys; } PropagatorResult result = PropagatorResult.CreateStructuralValue(endReferenceValues, associationSet.ElementType, false); return result; }
/// <summary> /// Checks if the given identifier has a registered 'owner' /// </summary> internal bool TryGetIdentifierOwner(int identifier, out PropagatorResult owner) { owner = _identifiers[identifier].Owner; return(null != owner); }
/// <summary> /// Determines column/value used to set values for a row. /// </summary> /// <remarks> /// The following columns are not included in the result: /// <list> /// <item>Keys in non-insert operations (keys are only set for inserts).</item> /// <item>Values flagged 'preserve' (these are values the propagator claims are untouched).</item> /// <item>Server generated values.</item> /// </list> /// </remarks> /// <param name="target">Expression binding representing the table.</param> /// <param name="row">Row containing values to set.</param> /// <param name="processor">Context for table.</param> /// <param name="insertMode">Determines whether key columns and 'preserve' columns are /// omitted from the list.</param> /// <param name="outputIdentifiers">Dictionary listing server generated identifiers.</param> /// <param name="returning">DbExpression describing result projection for server generated values.</param> /// <param name="rowMustBeTouched">Indicates whether the row must be touched /// because it produces a value (e.g. computed)</param> /// <returns>Column value pairs.</returns> private IEnumerable <DbModificationClause> BuildSetClauses(DbExpressionBinding target, PropagatorResult row, PropagatorResult originalRow, TableChangeProcessor processor, bool insertMode, out Dictionary <int, string> outputIdentifiers, out DbExpression returning, ref bool rowMustBeTouched) { Dictionary <EdmProperty, PropagatorResult> setClauses = new Dictionary <EdmProperty, PropagatorResult>(); List <KeyValuePair <string, DbExpression> > returningArguments = new List <KeyValuePair <string, DbExpression> >(); outputIdentifiers = new Dictionary <int, string>(); // Determine which flags indicate a property should be omitted from the set list. PropagatorFlags omitMask = insertMode ? PropagatorFlags.NoFlags : PropagatorFlags.Preserve | PropagatorFlags.Unknown; for (int propertyOrdinal = 0; propertyOrdinal < processor.Table.ElementType.Properties.Count; propertyOrdinal++) { EdmProperty property = processor.Table.ElementType.Properties[propertyOrdinal]; // Type members and result values are ordinally aligned PropagatorResult propertyResult = row.GetMemberValue(propertyOrdinal); if (PropagatorResult.NullIdentifier != propertyResult.Identifier) { // retrieve principal value propertyResult = propertyResult.ReplicateResultWithNewValue( m_translator.KeyManager.GetPrincipalValue(propertyResult)); } bool omitFromSetList = false; Debug.Assert(propertyResult.IsSimple); // Determine if this is a key value bool isKey = false; for (int i = 0; i < processor.KeyOrdinals.Length; i++) { if (processor.KeyOrdinals[i] == propertyOrdinal) { isKey = true; break; } } // check if this value should be omitted PropagatorFlags flags = PropagatorFlags.NoFlags; if (!insertMode && isKey) { // Keys are only set for inserts omitFromSetList = true; } else { // See if this value has been marked up with some context. If so, add the flag information // from the markup. Markup includes information about whether the property is a concurrency value, // whether it is known (it may be a property that is preserved across an update for instance) flags |= propertyResult.PropagatorFlags; } // Determine if this value is server-generated StoreGeneratedPattern genPattern = MetadataHelper.GetStoreGeneratedPattern(property); bool isServerGen = genPattern == StoreGeneratedPattern.Computed || (insertMode && genPattern == StoreGeneratedPattern.Identity); if (isServerGen) { DbPropertyExpression propertyExpression = target.Variable.Property(property); returningArguments.Add(new KeyValuePair <string, DbExpression>(property.Name, propertyExpression)); // check if this is a server generated identifier int identifier = propertyResult.Identifier; if (PropagatorResult.NullIdentifier != identifier) { if (m_translator.KeyManager.HasPrincipals(identifier)) { throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.Update_GeneratedDependent(property.Name)); } outputIdentifiers.Add(identifier, property.Name); // If this property maps an identifier (in the update pipeline) it may // also be a store key. If so, the pattern had better be "Identity" // since otherwise we're dealing with a mutable key. if (genPattern != StoreGeneratedPattern.Identity && processor.IsKeyProperty(propertyOrdinal)) { throw EntityUtil.NotSupported(System.Data.Entity.Strings.Update_NotSupportedComputedKeyColumn( EdmProviderManifest.StoreGeneratedPatternFacetName, XmlConstants.Computed, XmlConstants.Identity, property.Name, property.DeclaringType.FullName)); } } } if (PropagatorFlags.NoFlags != (flags & (omitMask))) { // column value matches "omit" pattern, therefore should not be set omitFromSetList = true; } else if (isServerGen) { // column value does not match "omit" pattern, but it is server generated // so it cannot be set omitFromSetList = true; // if the row has a modified value overridden by server gen, // it must still be touched in order to retrieve the value rowMustBeTouched = true; } // make the user is not updating an identity value if (!omitFromSetList && !insertMode && genPattern == StoreGeneratedPattern.Identity) { //throw the error only if the value actually changed Debug.Assert(originalRow != null, "Updated records should have a original row"); PropagatorResult originalPropertyResult = originalRow.GetMemberValue(propertyOrdinal); Debug.Assert(originalPropertyResult.IsSimple, "Server Gen property that is not primitive?"); Debug.Assert(propertyResult.IsSimple, "Server Gen property that is not primitive?"); if (!ByValueEqualityComparer.Default.Equals(originalPropertyResult.GetSimpleValue(), propertyResult.GetSimpleValue())) { throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.Update_ModifyingIdentityColumn( XmlConstants.Identity, property.Name, property.DeclaringType.FullName)); } else { omitFromSetList = true; } } if (!omitFromSetList) { setClauses.Add(property, propertyResult); } } // Construct returning projection if (0 < returningArguments.Count) { returning = DbExpressionBuilder.NewRow(returningArguments); } else { returning = null; } // Construct clauses corresponding to the set clauses List <DbModificationClause> result = new List <DbModificationClause>(setClauses.Count); foreach (KeyValuePair <EdmProperty, PropagatorResult> setClause in setClauses) { EdmProperty property = setClause.Key; result.Add(new DbSetClause( GeneratePropertyExpression(target, setClause.Key), GenerateValueExpression(setClause.Key, setClause.Value))); } return(result); }
// Effects: returns true iff. the input propagator result has some flag defined in "flags" // Requires: input is set private static bool HasFlag(PropagatorResult input, PropagatorFlags flags) { if (null == input) { return false; } return (PropagatorFlags.NoFlags != (flags & input.PropagatorFlags)); }
// Adds and register a DbParameter to the current command. internal void SetParameterValue(PropagatorResult result, StorageModificationFunctionParameterBinding parameterBinding, UpdateTranslator translator) { // retrieve DbParameter DbParameter parameter = this.m_dbCommand.Parameters[parameterBinding.Parameter.Name]; TypeUsage parameterType = parameterBinding.Parameter.TypeUsage; object parameterValue = translator.KeyManager.GetPrincipalValue(result); translator.SetParameterValue(parameter, parameterType, parameterValue); // if the parameter corresponds to an identifier (key component), remember this fact in case // it's important for dependency ordering (e.g., output the identifier before creating it) int identifier = result.Identifier; if (PropagatorResult.NullIdentifier != identifier) { const int initialSize = 2; // expect on average less than two input identifiers per command if (null == m_inputIdentifiers) { m_inputIdentifiers = new List<KeyValuePair<int, DbParameter>>(initialSize); } foreach (int principal in translator.KeyManager.GetPrincipals(identifier)) { m_inputIdentifiers.Add(new KeyValuePair<int, DbParameter>(principal, parameter)); } } }
// Effects: given a "clause" in the form of a property/value pair, produces an equality expression. If the // value is null, creates an IsNull expression // Requires: all arguments are set private DbExpression GenerateEqualityExpression(DbExpressionBinding target, EdmProperty property, PropagatorResult value) { Debug.Assert(null != target && null != property && null != value); DbExpression propertyExpression = GeneratePropertyExpression(target, property); DbExpression valueExpression = GenerateValueExpression(property, value); if (valueExpression.ExpressionKind == DbExpressionKind.Null) { return(propertyExpression.IsNull()); } return(propertyExpression.Equal(valueExpression)); }
private void DiagnoseKeyCollision(UpdateCompiler compiler, PropagatorResult change, CompositeKey key, PropagatorResult other) { KeyManager keyManager = compiler.m_translator.KeyManager; CompositeKey otherKey = new CompositeKey(GetKeyConstants(other)); // determine if the conflict is due to shared principal key values bool sharedPrincipal = true; for (int i = 0; sharedPrincipal && i < key.KeyComponents.Length; i++) { int identifier1 = key.KeyComponents[i].Identifier; int identifier2 = otherKey.KeyComponents[i].Identifier; if (!keyManager.GetPrincipals(identifier1).Intersect(keyManager.GetPrincipals(identifier2)).Any()) { sharedPrincipal = false; } } if (sharedPrincipal) { // if the duplication is due to shared principals, there is a duplicate key exception var stateEntries = SourceInterpreter.GetAllStateEntries(change, compiler.m_translator, m_table) .Concat(SourceInterpreter.GetAllStateEntries(other, compiler.m_translator, m_table)); throw EntityUtil.Update(Strings.Update_DuplicateKeys, null, stateEntries); } else { // if there are no shared principals, it implies that common dependents are the problem HashSet <IEntityStateEntry> commonDependents = null; foreach (PropagatorResult keyValue in key.KeyComponents.Concat(otherKey.KeyComponents)) { var dependents = new HashSet <IEntityStateEntry>(); foreach (int dependentId in keyManager.GetDependents(keyValue.Identifier)) { PropagatorResult dependentResult; if (keyManager.TryGetIdentifierOwner(dependentId, out dependentResult) && null != dependentResult.StateEntry) { dependents.Add(dependentResult.StateEntry); } } if (null == commonDependents) { commonDependents = new HashSet <IEntityStateEntry>(dependents); } else { commonDependents.IntersectWith(dependents); } } // to ensure the exception shape is consistent with constraint violations discovered while processing // commands (a more conventional scenario in which different tables are contributing principal values) // wrap a DataConstraintException in an UpdateException throw EntityUtil.Update(Strings.Update_GeneralExecutionException, EntityUtil.Constraint(Strings.Update_ReferentialConstraintIntegrityViolation), commonDependents); } }
/// <summary> /// Converts a record to a propagator result /// </summary> /// <param name="stateEntry">state manager entry containing the record</param> /// <param name="isModified">Indicates whether the root element is modified (i.e., whether the type has changed)</param> /// <param name="record">Record to convert</param> /// <param name="useCurrentValues">Indicates whether we are retrieving current or original values.</param> /// <param name="translator">Translator for session context; registers new metadata for the record type if none /// exists</param> /// <param name="modifiedPropertiesBehavior">Indicates how to determine whether a property is modified.</param> /// <returns>Result corresponding to the given record</returns> internal static PropagatorResult ExtractResultFromRecord(IEntityStateEntry stateEntry, bool isModified, IExtendedDataRecord record, bool useCurrentValues, UpdateTranslator translator, ModifiedPropertiesBehavior modifiedPropertiesBehavior) { StructuralType structuralType = (StructuralType)record.DataRecordInfo.RecordType.EdmType; ExtractorMetadata metadata = translator.GetExtractorMetadata(stateEntry.EntitySet, structuralType); EntityKey key = stateEntry.EntityKey; PropagatorResult[] nestedValues = new PropagatorResult[record.FieldCount]; for (int ordinal = 0; ordinal < nestedValues.Length; ordinal++) { nestedValues[ordinal] = metadata.RetrieveMember(stateEntry, record, useCurrentValues, key, ordinal, modifiedPropertiesBehavior); } return PropagatorResult.CreateStructuralValue(nestedValues, structuralType, isModified); }
// Adds a result column binding from a column name (from the result set for the function) to // a propagator result (which contains the context necessary to back-propagate the result). // If the result is an identifier, binds the internal void AddResultColumn(UpdateTranslator translator, String columnName, PropagatorResult result) { const int initializeSize = 2; // expect on average less than two result columns per command if (null == m_resultColumns) { m_resultColumns = new List <KeyValuePair <string, PropagatorResult> >(initializeSize); } m_resultColumns.Add(new KeyValuePair <string, PropagatorResult>(columnName, result)); int identifier = result.Identifier; if (PropagatorResult.NullIdentifier != identifier) { if (translator.KeyManager.HasPrincipals(identifier)) { throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.Update_GeneratedDependent(columnName)); } // register output identifier to enable fix-up and dependency tracking AddOutputIdentifier(columnName, identifier); } }
// Extracts key constants from the given row. private PropagatorResult[] GetKeyConstants(PropagatorResult row) { PropagatorResult[] keyConstants = new PropagatorResult[m_keyOrdinals.Length]; for (int i = 0; i < m_keyOrdinals.Length; i++) { PropagatorResult constant = row.GetMemberValue(m_keyOrdinals[i]); keyConstants[i] = constant; } return keyConstants; }
internal override FunctionUpdateCommand Translate( UpdateTranslator translator, ExtractedStateEntry stateEntry) { var mapping = GetFunctionMapping(stateEntry); StorageEntityTypeModificationFunctionMapping typeMapping = mapping.Item1; StorageModificationFunctionMapping functionMapping = mapping.Item2; EntityKey entityKey = stateEntry.Source.EntityKey; var stateEntries = new HashSet <IEntityStateEntry> { stateEntry.Source }; // gather all referenced association ends var collocatedEntries = // find all related entries corresponding to collocated association types from end in functionMapping.CollocatedAssociationSetEnds join candidateEntry in translator.GetRelationships(entityKey) on end.CorrespondingAssociationEndMember.DeclaringType equals candidateEntry.EntitySet.ElementType select Tuple.Create(end.CorrespondingAssociationEndMember, candidateEntry); var currentReferenceEnds = new Dictionary <AssociationEndMember, IEntityStateEntry>(); var originalReferenceEnds = new Dictionary <AssociationEndMember, IEntityStateEntry>(); foreach (var candidate in collocatedEntries) { ProcessReferenceCandidate(entityKey, stateEntries, currentReferenceEnds, originalReferenceEnds, candidate.Item1, candidate.Item2); } // create function object FunctionUpdateCommand command; // consider the following scenario, we need to loop through all the state entries that is correlated with entity2 and make sure it is not changed. // entity1 <-- Independent Association <-- entity2 <-- Fk association <-- entity 3 // | // entity4 <-- Fk association <-- if (stateEntries.All(e => e.State == EntityState.Unchanged)) { // we shouldn't update the entity if it is unchanged, only update when referenced association is changed. // if not, then this will trigger a fake update for principal end as describe in command = null; } else { command = new FunctionUpdateCommand(functionMapping, translator, stateEntries.ToList().AsReadOnly(), stateEntry); // bind all function parameters BindFunctionParameters(translator, stateEntry, functionMapping, command, currentReferenceEnds, originalReferenceEnds); // interpret all result bindings if (null != functionMapping.ResultBindings) { foreach (StorageModificationFunctionResultBinding resultBinding in functionMapping.ResultBindings) { PropagatorResult result = stateEntry.Current.GetMemberValue(resultBinding.Property); command.AddResultColumn(translator, resultBinding.ColumnName, result); } } } return(command); }
// Adds a result column binding from a column name (from the result set for the function) to // a propagator result (which contains the context necessary to back-propagate the result). // If the result is an identifier, binds the internal void AddResultColumn(UpdateTranslator translator, String columnName, PropagatorResult result) { const int initializeSize = 2; // expect on average less than two result columns per command if (null == m_resultColumns) { m_resultColumns = new List<KeyValuePair<string, PropagatorResult>>(initializeSize); } m_resultColumns.Add(new KeyValuePair<string, PropagatorResult>(columnName, result)); int identifier = result.Identifier; if (PropagatorResult.NullIdentifier != identifier) { if (translator.KeyManager.HasPrincipals(identifier)) { throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.Update_GeneratedDependent(columnName)); } // register output identifier to enable fix-up and dependency tracking AddOutputIdentifier(columnName, identifier); } }
/// <summary> /// Determines predicate used to identify a row in a table. /// </summary> /// <remarks> /// Columns are included in the list when: /// <list> /// <item>They are keys for the table</item> /// <item>They are concurrency values</item> /// </list> /// </remarks> /// <param name="target">Expression binding representing the table containing the row</param> /// <param name="referenceRow">Values for the row being located.</param> /// <param name="current">Values being updated (may be null).</param> /// <param name="processor">Context for the table containing the row.</param> /// <param name="rowMustBeTouched">Output parameter indicating whether a row must be touched /// (whether it's being modified or not) because it contains a concurrency value</param> /// <returns>Column/value pairs.</returns> private DbExpression BuildPredicate(DbExpressionBinding target, PropagatorResult referenceRow, PropagatorResult current, TableChangeProcessor processor, ref bool rowMustBeTouched) { Dictionary <EdmProperty, PropagatorResult> whereClauses = new Dictionary <EdmProperty, PropagatorResult>(); // add all concurrency tokens (note that keys are always concurrency tokens as well) int propertyOrdinal = 0; foreach (EdmProperty member in processor.Table.ElementType.Properties) { // members and result values are ordinally aligned PropagatorResult expectedValue = referenceRow.GetMemberValue(propertyOrdinal); PropagatorResult newValue = null == current ? null : current.GetMemberValue(propertyOrdinal); // check if the rowMustBeTouched value should be set to true (if it isn't already // true and we've come across a concurrency value) if (!rowMustBeTouched && (HasFlag(expectedValue, PropagatorFlags.ConcurrencyValue) || HasFlag(newValue, PropagatorFlags.ConcurrencyValue))) { rowMustBeTouched = true; } // determine if this is a concurrency value if (!whereClauses.ContainsKey(member) && // don't add to the set clause twice (HasFlag(expectedValue, PropagatorFlags.ConcurrencyValue | PropagatorFlags.Key) || HasFlag(newValue, PropagatorFlags.ConcurrencyValue | PropagatorFlags.Key))) // tagged as concurrency value { whereClauses.Add(member, expectedValue); } propertyOrdinal++; } // Build a binary AND expression tree from the clauses DbExpression predicate = null; foreach (KeyValuePair <EdmProperty, PropagatorResult> clause in whereClauses) { DbExpression clauseExpression = GenerateEqualityExpression(target, clause.Key, clause.Value); if (null == predicate) { predicate = clauseExpression; } else { predicate = predicate.And(clauseExpression); } } Debug.Assert(null != predicate, "some predicate term must exist"); return(predicate); }
/// <summary> /// A result is merged with another when it is merged as part of an equi-join. /// </summary> /// <remarks> /// In theory, this should only ever be called on two keys (since we only join on /// keys). We throw in the base implementation, and override in KeyResult. By convention /// the principal key is always the first result in the chain (in case of an RIC). In /// addition, entity entries always appear before relationship entries. /// </remarks> /// <param name="other">Result to merge with.</param> /// <returns>Merged result.</returns> internal virtual PropagatorResult Merge(KeyManager keyManager, PropagatorResult other) { throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.UpdatePipelineResultRequestInvalid, 0, "PropagatorResult.Merge"); }
protected UpdateCommand(PropagatorResult originalValues, PropagatorResult currentValues) { m_originalValues = originalValues; m_currentValues = currentValues; }
/// <summary> /// Aligns a value returned from the store with the expected type for the member. /// </summary> /// <param name="value">Value to convert.</param> /// <param name="member">Metadata for the member being set.</param> /// <param name="context">The context generating the return value.</param> /// <returns>Converted return value</returns> private object AlignReturnValue(object value, EdmMember member, PropagatorResult context) { if (DBNull.Value.Equals(value)) { // check if there is a nullability constraint on the value if (BuiltInTypeKind.EdmProperty == member.BuiltInTypeKind && !((EdmProperty)member).Nullable) { throw EntityUtil.Update(System.Data.Entity.Strings.Update_NullReturnValueForNonNullableMember( member.Name, member.DeclaringType.FullName), null); } } else if (!Helper.IsSpatialType(member.TypeUsage)) { Type clrType; Type clrEnumType = null; if (Helper.IsEnumType(member.TypeUsage.EdmType)) { PrimitiveType underlyingType = Helper.AsPrimitive(member.TypeUsage.EdmType); clrEnumType = context.Record.GetFieldType(context.RecordOrdinal); clrType = underlyingType.ClrEquivalentType; Debug.Assert(clrEnumType.IsEnum); } else { // convert the value to the appropriate CLR type Debug.Assert(BuiltInTypeKind.PrimitiveType == member.TypeUsage.EdmType.BuiltInTypeKind, "we only allow return values that are instances of EDM primitive or enum types"); PrimitiveType primitiveType = (PrimitiveType)member.TypeUsage.EdmType; clrType = primitiveType.ClrEquivalentType; } try { value = Convert.ChangeType(value, clrType, CultureInfo.InvariantCulture); if (clrEnumType != null) { value = Enum.ToObject(clrEnumType, value); } } catch (Exception e) { // we should not be wrapping all exceptions if (UpdateTranslator.RequiresContext(e)) { Type userClrType = clrEnumType ?? clrType; throw EntityUtil.Update(System.Data.Entity.Strings.Update_ReturnValueHasUnexpectedType( value.GetType().FullName, userClrType.FullName, member.Name, member.DeclaringType.FullName), e); } throw; } } // return the adjusted value return value; }
internal override long Execute(UpdateTranslator translator, EntityConnection connection, Dictionary <int, object> identifierValues, List <KeyValuePair <PropagatorResult, object> > generatedValues) { // Compile command using (DbCommand command = this.CreateCommand(translator, identifierValues)) { // configure command to use the connection and transaction for this session command.Transaction = ((null != connection.CurrentTransaction) ? connection.CurrentTransaction.StoreTransaction : null); command.Connection = connection.StoreConnection; if (translator.CommandTimeout.HasValue) { command.CommandTimeout = translator.CommandTimeout.Value; } // Execute the query int rowsAffected; if (m_modificationCommandTree.HasReader) { // retrieve server gen results rowsAffected = 0; using (DbDataReader reader = command.ExecuteReader(CommandBehavior.SequentialAccess)) { if (reader.Read()) { rowsAffected++; IBaseList <EdmMember> members = TypeHelpers.GetAllStructuralMembers(this.CurrentValues.StructuralType); for (int ordinal = 0; ordinal < reader.FieldCount; ordinal++) { // column name of result corresponds to column name of table string columnName = reader.GetName(ordinal); EdmMember member = members[columnName]; object value; if (Helper.IsSpatialType(member.TypeUsage) && !reader.IsDBNull(ordinal)) { value = SpatialHelpers.GetSpatialValue(translator.MetadataWorkspace, reader, member.TypeUsage, ordinal); } else { value = reader.GetValue(ordinal); } // retrieve result which includes the context for back-propagation int columnOrdinal = members.IndexOf(member); PropagatorResult result = this.CurrentValues.GetMemberValue(columnOrdinal); // register for back-propagation generatedValues.Add(new KeyValuePair <PropagatorResult, object>(result, value)); // register identifier if it exists int identifier = result.Identifier; if (PropagatorResult.NullIdentifier != identifier) { identifierValues.Add(identifier, value); } } } // Consume the current reader (and subsequent result sets) so that any errors // executing the command can be intercepted CommandHelper.ConsumeReader(reader); } } else { rowsAffected = command.ExecuteNonQuery(); } return(rowsAffected); } }
/// <summary> /// Builds insert command. /// </summary> /// <param name="newRow">Row to insert.</param> /// <param name="processor">Context for the table we're inserting into.</param> /// <returns>Insert command.</returns> internal UpdateCommand BuildInsertCommand(PropagatorResult newRow, TableChangeProcessor processor) { // Bind the insert target DbExpressionBinding target = GetTarget(processor); // Create set clauses and returning parameter Dictionary<int, string> outputIdentifiers; DbExpression returning; bool rowMustBeTouched = true; // for inserts, the row must always be touched List<DbModificationClause> setClauses = new List<DbModificationClause>(); foreach (DbModificationClause clause in BuildSetClauses(target, newRow, null, processor, /* insertMode */ true, out outputIdentifiers, out returning, ref rowMustBeTouched)) { setClauses.Add(clause); } // Initialize DML command tree DbInsertCommandTree commandTree = new DbInsertCommandTree(m_translator.MetadataWorkspace, DataSpace.SSpace, target, setClauses.AsReadOnly(), returning); // Create command UpdateCommand command = new DynamicUpdateCommand(processor, m_translator, ModificationOperator.Insert, null, newRow, commandTree, outputIdentifiers); return command; }
/// <summary> /// Specialization of <see cref="CreatePlaceholder" /> for an entity set extent. /// </summary> /// <param name="entitySet"></param> /// <returns></returns> private PropagatorResult CreateEntitySetPlaceholder(EntitySet entitySet) { EntityUtil.CheckArgumentNull(entitySet, "entitySet"); ReadOnlyMetadataCollection<EdmProperty> members = entitySet.ElementType.Properties; PropagatorResult[] memberValues = new PropagatorResult[members.Count]; for (int ordinal = 0; ordinal < members.Count; ordinal++) { PropagatorResult memberValue = CreateMemberPlaceholder(members[ordinal]); memberValues[ordinal] = memberValue; } PropagatorResult result = PropagatorResult.CreateStructuralValue(memberValues, entitySet.ElementType, false); return result; }
/// <summary> /// Determines column/value used to set values for a row. /// </summary> /// <remarks> /// The following columns are not included in the result: /// <list> /// <item>Keys in non-insert operations (keys are only set for inserts).</item> /// <item>Values flagged 'preserve' (these are values the propagator claims are untouched).</item> /// <item>Server generated values.</item> /// </list> /// </remarks> /// <param name="target">Expression binding representing the table.</param> /// <param name="row">Row containing values to set.</param> /// <param name="processor">Context for table.</param> /// <param name="insertMode">Determines whether key columns and 'preserve' columns are /// omitted from the list.</param> /// <param name="outputIdentifiers">Dictionary listing server generated identifiers.</param> /// <param name="returning">DbExpression describing result projection for server generated values.</param> /// <param name="rowMustBeTouched">Indicates whether the row must be touched /// because it produces a value (e.g. computed)</param> /// <returns>Column value pairs.</returns> private IEnumerable<DbModificationClause> BuildSetClauses(DbExpressionBinding target, PropagatorResult row, PropagatorResult originalRow, TableChangeProcessor processor, bool insertMode, out Dictionary<int, string> outputIdentifiers, out DbExpression returning, ref bool rowMustBeTouched) { Dictionary<EdmProperty, PropagatorResult> setClauses = new Dictionary<EdmProperty, PropagatorResult>(); List<KeyValuePair<string, DbExpression>> returningArguments = new List<KeyValuePair<string, DbExpression>>(); outputIdentifiers = new Dictionary<int, string>(); // Determine which flags indicate a property should be omitted from the set list. PropagatorFlags omitMask = insertMode ? PropagatorFlags.NoFlags : PropagatorFlags.Preserve | PropagatorFlags.Unknown; for (int propertyOrdinal = 0; propertyOrdinal < processor.Table.ElementType.Properties.Count; propertyOrdinal++) { EdmProperty property = processor.Table.ElementType.Properties[propertyOrdinal]; // Type members and result values are ordinally aligned PropagatorResult propertyResult = row.GetMemberValue(propertyOrdinal); if (PropagatorResult.NullIdentifier != propertyResult.Identifier) { // retrieve principal value propertyResult = propertyResult.ReplicateResultWithNewValue( m_translator.KeyManager.GetPrincipalValue(propertyResult)); } bool omitFromSetList = false; Debug.Assert(propertyResult.IsSimple); // Determine if this is a key value bool isKey = false; for (int i = 0; i < processor.KeyOrdinals.Length; i++) { if (processor.KeyOrdinals[i] == propertyOrdinal) { isKey = true; break; } } // check if this value should be omitted PropagatorFlags flags = PropagatorFlags.NoFlags; if (!insertMode && isKey) { // Keys are only set for inserts omitFromSetList = true; } else { // See if this value has been marked up with some context. If so, add the flag information // from the markup. Markup includes information about whether the property is a concurrency value, // whether it is known (it may be a property that is preserved across an update for instance) flags |= propertyResult.PropagatorFlags; } // Determine if this value is server-generated StoreGeneratedPattern genPattern = MetadataHelper.GetStoreGeneratedPattern(property); bool isServerGen = genPattern == StoreGeneratedPattern.Computed || (insertMode && genPattern == StoreGeneratedPattern.Identity); if (isServerGen) { DbPropertyExpression propertyExpression = target.Variable.Property(property); returningArguments.Add(new KeyValuePair<string, DbExpression>(property.Name, propertyExpression)); // check if this is a server generated identifier int identifier = propertyResult.Identifier; if (PropagatorResult.NullIdentifier != identifier) { if (m_translator.KeyManager.HasPrincipals(identifier)) { throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.Update_GeneratedDependent(property.Name)); } outputIdentifiers.Add(identifier, property.Name); // If this property maps an identifier (in the update pipeline) it may // also be a store key. If so, the pattern had better be "Identity" // since otherwise we're dealing with a mutable key. if (genPattern != StoreGeneratedPattern.Identity && processor.IsKeyProperty(propertyOrdinal)) { throw EntityUtil.NotSupported(System.Data.Entity.Strings.Update_NotSupportedComputedKeyColumn( EdmProviderManifest.StoreGeneratedPatternFacetName, XmlConstants.Computed, XmlConstants.Identity, property.Name, property.DeclaringType.FullName)); } } } if (PropagatorFlags.NoFlags != (flags & (omitMask))) { // column value matches "omit" pattern, therefore should not be set omitFromSetList = true; } else if (isServerGen) { // column value does not match "omit" pattern, but it is server generated // so it cannot be set omitFromSetList = true; // if the row has a modified value overridden by server gen, // it must still be touched in order to retrieve the value rowMustBeTouched = true; } // make the user is not updating an identity value if (!omitFromSetList && !insertMode && genPattern == StoreGeneratedPattern.Identity) { //throw the error only if the value actually changed Debug.Assert(originalRow != null, "Updated records should have a original row"); PropagatorResult originalPropertyResult = originalRow.GetMemberValue(propertyOrdinal); Debug.Assert(originalPropertyResult.IsSimple, "Server Gen property that is not primitive?"); Debug.Assert(propertyResult.IsSimple, "Server Gen property that is not primitive?"); if (!ByValueEqualityComparer.Default.Equals(originalPropertyResult.GetSimpleValue(), propertyResult.GetSimpleValue())) { throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.Update_ModifyingIdentityColumn( XmlConstants.Identity, property.Name, property.DeclaringType.FullName)); } else { omitFromSetList = true; } } if (!omitFromSetList) { setClauses.Add(property, propertyResult); } } // Construct returning projection if (0 < returningArguments.Count) { returning = DbExpressionBuilder.NewRow(returningArguments); } else { returning = null; } // Construct clauses corresponding to the set clauses List<DbModificationClause> result = new List<DbModificationClause>(setClauses.Count); foreach (KeyValuePair<EdmProperty, PropagatorResult> setClause in setClauses) { EdmProperty property = setClause.Key; result.Add(new DbSetClause( GeneratePropertyExpression(target, setClause.Key), GenerateValueExpression(setClause.Key, setClause.Value))); } return result; }
/// <summary> /// Given default values for children members, produces a new default expression for the requested (parent) member. /// </summary> /// <param name="node">Parent member</param> /// <returns>Default value for parent member</returns> internal PropagatorResult Visit(EdmMember node) { PropagatorResult result; TypeUsage nodeType = Helper.GetModelTypeUsage(node); if (Helper.IsScalarType(nodeType.EdmType)) { GetPropagatorResultForPrimitiveType(Helper.AsPrimitive(nodeType.EdmType), out result); } else { // Construct a new 'complex type' (really any structural type) member. StructuralType structuralType = (StructuralType)nodeType.EdmType; IBaseList<EdmMember> members = TypeHelpers.GetAllStructuralMembers(structuralType); PropagatorResult[] args = new PropagatorResult[members.Count]; for (int ordinal = 0; ordinal < members.Count; ordinal++) // foreach (EdmMember member in members) { args[ordinal] = Visit(members[ordinal]); } result = PropagatorResult.CreateStructuralValue(args, structuralType, false); } return result; }
/// <summary> /// Determines predicate used to identify a row in a table. /// </summary> /// <remarks> /// Columns are included in the list when: /// <list> /// <item>They are keys for the table</item> /// <item>They are concurrency values</item> /// </list> /// </remarks> /// <param name="target">Expression binding representing the table containing the row</param> /// <param name="referenceRow">Values for the row being located.</param> /// <param name="current">Values being updated (may be null).</param> /// <param name="processor">Context for the table containing the row.</param> /// <param name="rowMustBeTouched">Output parameter indicating whether a row must be touched /// (whether it's being modified or not) because it contains a concurrency value</param> /// <returns>Column/value pairs.</returns> private DbExpression BuildPredicate(DbExpressionBinding target, PropagatorResult referenceRow, PropagatorResult current, TableChangeProcessor processor, ref bool rowMustBeTouched) { Dictionary<EdmProperty, PropagatorResult> whereClauses = new Dictionary<EdmProperty, PropagatorResult>(); // add all concurrency tokens (note that keys are always concurrency tokens as well) int propertyOrdinal = 0; foreach (EdmProperty member in processor.Table.ElementType.Properties) { // members and result values are ordinally aligned PropagatorResult expectedValue = referenceRow.GetMemberValue(propertyOrdinal); PropagatorResult newValue = null == current ? null : current.GetMemberValue(propertyOrdinal); // check if the rowMustBeTouched value should be set to true (if it isn't already // true and we've come across a concurrency value) if (!rowMustBeTouched && (HasFlag(expectedValue, PropagatorFlags.ConcurrencyValue) || HasFlag(newValue, PropagatorFlags.ConcurrencyValue))) { rowMustBeTouched = true; } // determine if this is a concurrency value if (!whereClauses.ContainsKey(member) && // don't add to the set clause twice (HasFlag(expectedValue, PropagatorFlags.ConcurrencyValue | PropagatorFlags.Key) || HasFlag(newValue, PropagatorFlags.ConcurrencyValue | PropagatorFlags.Key))) // tagged as concurrency value { whereClauses.Add(member, expectedValue); } propertyOrdinal++; } // Build a binary AND expression tree from the clauses DbExpression predicate = null; foreach (KeyValuePair<EdmProperty, PropagatorResult> clause in whereClauses) { DbExpression clauseExpression = GenerateEqualityExpression(target, clause.Key, clause.Value); if (null == predicate) { predicate = clauseExpression; } else { predicate = predicate.And(clauseExpression); } } Debug.Assert(null != predicate, "some predicate term must exist"); return predicate; }
/// <summary> /// Produce a tuple containing joined rows. /// </summary> /// <param name="left">Left row.</param> /// <param name="right">Right row.</param> /// <param name="leftKey">Key used to join left element.</param> /// <param name="rightKey">Key used to join right element.</param> /// <param name="result">Result change node; used for type information.</param> /// <returns>Result of joining the input rows.</returns> private PropagatorResult CreateResultTuple(Tuple<CompositeKey, PropagatorResult> left, Tuple<CompositeKey, PropagatorResult> right, ChangeNode result) { // using ref compare to avoid triggering value based CompositeKey leftKey = left.Item1; CompositeKey rightKey = right.Item1; Dictionary<PropagatorResult, PropagatorResult> map = null; if (!object.ReferenceEquals(null, leftKey) && !object.ReferenceEquals(null, rightKey) && !object.ReferenceEquals(leftKey, rightKey)) { // Merge key values from the left and the right (since they're equal, there's a possibility we'll // project values only from the left or the right hand side and lose important context.) CompositeKey mergedKey = leftKey.Merge(m_parent.m_updateTranslator.KeyManager, rightKey); // create a dictionary so that we can replace key values with merged key values (carrying context // from both sides) map = new Dictionary<PropagatorResult, PropagatorResult>(); for (int i = 0; i < leftKey.KeyComponents.Length; i++) { map[leftKey.KeyComponents[i]] = mergedKey.KeyComponents[i]; map[rightKey.KeyComponents[i]] = mergedKey.KeyComponents[i]; } } PropagatorResult[] joinRecordValues = new PropagatorResult[2]; joinRecordValues[0] = left.Item2; joinRecordValues[1] = right.Item2; PropagatorResult join = PropagatorResult.CreateStructuralValue(joinRecordValues, (StructuralType)result.ElementType.EdmType, false); // replace with merged key values as appropriate if (null != map) { PropagatorResult replacement; join = join.Replace(original => map.TryGetValue(original, out replacement) ? replacement : original); } return join; }
// Effects: given a "clause" in the form of a property/value pair, produces an equality expression. If the // value is null, creates an IsNull expression // Requires: all arguments are set private DbExpression GenerateEqualityExpression(DbExpressionBinding target, EdmProperty property, PropagatorResult value) { Debug.Assert(null != target && null != property && null != value); DbExpression propertyExpression = GeneratePropertyExpression(target, property); DbExpression valueExpression = GenerateValueExpression(property, value); if (valueExpression.ExpressionKind == DbExpressionKind.Null) { return propertyExpression.IsNull(); } return propertyExpression.Equal(valueExpression); }
private void DiagnoseKeyCollision(UpdateCompiler compiler, PropagatorResult change, CompositeKey key, PropagatorResult other) { KeyManager keyManager = compiler.m_translator.KeyManager; CompositeKey otherKey = new CompositeKey(GetKeyConstants(other)); // determine if the conflict is due to shared principal key values bool sharedPrincipal = true; for (int i = 0; sharedPrincipal && i < key.KeyComponents.Length; i++) { int identifier1 = key.KeyComponents[i].Identifier; int identifier2 = otherKey.KeyComponents[i].Identifier; if (!keyManager.GetPrincipals(identifier1).Intersect(keyManager.GetPrincipals(identifier2)).Any()) { sharedPrincipal = false; } } if (sharedPrincipal) { // if the duplication is due to shared principals, there is a duplicate key exception var stateEntries = SourceInterpreter.GetAllStateEntries(change, compiler.m_translator, m_table) .Concat(SourceInterpreter.GetAllStateEntries(other, compiler.m_translator, m_table)); throw EntityUtil.Update(Strings.Update_DuplicateKeys, null, stateEntries); } else { // if there are no shared principals, it implies that common dependents are the problem HashSet<IEntityStateEntry> commonDependents = null; foreach (PropagatorResult keyValue in key.KeyComponents.Concat(otherKey.KeyComponents)) { var dependents = new HashSet<IEntityStateEntry>(); foreach (int dependentId in keyManager.GetDependents(keyValue.Identifier)) { PropagatorResult dependentResult; if (keyManager.TryGetIdentifierOwner(dependentId, out dependentResult) && null != dependentResult.StateEntry) { dependents.Add(dependentResult.StateEntry); } } if (null == commonDependents) { commonDependents = new HashSet<IEntityStateEntry>(dependents); } else { commonDependents.IntersectWith(dependents); } } // to ensure the exception shape is consistent with constraint violations discovered while processing // commands (a more conventional scenario in which different tables are contributing principal values) // wrap a DataConstraintException in an UpdateException throw EntityUtil.Update(Strings.Update_GeneralExecutionException, EntityUtil.Constraint(Strings.Update_ReferentialConstraintIntegrityViolation), commonDependents); } }
// Effects: given a propagator result, produces a constant expression describing that value. // Requires: all arguments are set, and the value must be simple (scalar) private DbExpression GenerateValueExpression(EdmProperty property, PropagatorResult value) { Debug.Assert(null != value && value.IsSimple && null != property); Debug.Assert(Helper.IsPrimitiveType(property.TypeUsage.EdmType), "Properties in SSpace should be primitive."); if (value.IsNull) { return DbExpressionBuilder.Null(Helper.GetModelTypeUsage(property)); } object principalValue = m_translator.KeyManager.GetPrincipalValue(value); if (Convert.IsDBNull(principalValue)) { // although the result may be marked non-null (because it is an identifier) it is possible // there is no corresponding real value for the property yet return DbExpressionBuilder.Null(Helper.GetModelTypeUsage(property)); } else { // At this point we have already done any needed type checking and we potentially translated the type // of the property to the SSpace (the property parameter is a property in the SSpace). However the value // is here is a CSpace value. As a result it does not have to match the type of the property in SSpace. // Two cases here are: // - the type in CSpace does not exactly match the type in the SSpace (but is promotable) // - the type in CSpace is enum type and in this case it never matches the type in SSpace where enum type // does not exist // Since the types have already been checked it is safe just to convert the value from CSpace to the type // from SSpace. Debug.Assert(Nullable.GetUnderlyingType(principalValue.GetType()) == null, "Unexpected nullable type."); TypeUsage propertyType = Helper.GetModelTypeUsage(property); Type principalType = principalValue.GetType(); if (principalType.IsEnum) { principalValue = Convert.ChangeType(principalValue, principalType.GetEnumUnderlyingType(), CultureInfo.InvariantCulture); } var columnClrEquivalentType = ((PrimitiveType)propertyType.EdmType).ClrEquivalentType; if (principalType != columnClrEquivalentType) { principalValue = Convert.ChangeType(principalValue, columnClrEquivalentType, CultureInfo.InvariantCulture); } return DbExpressionBuilder.Constant(propertyType, principalValue); } }
private void SetServerGenValue(PropagatorResult context, object value) { if (context.RecordOrdinal != PropagatorResult.NullOrdinal) { CurrentValueRecord targetRecord = context.Record; // determine if type compensation is required IExtendedDataRecord recordWithMetadata = (IExtendedDataRecord)targetRecord; EdmMember member = recordWithMetadata.DataRecordInfo.FieldMetadata[context.RecordOrdinal].FieldType; value = value ?? DBNull.Value; // records expect DBNull rather than null value = AlignReturnValue(value, member, context); targetRecord.SetValue(context.RecordOrdinal, value); } }
// efects: Executes the current function command in the given transaction and connection context. // All server-generated values are added to the generatedValues list. If those values are identifiers, they are // also added to the identifierValues dictionary, which associates proxy identifiers for keys in the session // with their actual values, permitting fix-up of identifiers across relationships. internal override long Execute(UpdateTranslator translator, EntityConnection connection, Dictionary <int, object> identifierValues, List <KeyValuePair <PropagatorResult, object> > generatedValues) { // configure command to use the connection and transaction for this session m_dbCommand.Transaction = ((null != connection.CurrentTransaction) ? connection.CurrentTransaction.StoreTransaction : null); m_dbCommand.Connection = connection.StoreConnection; if (translator.CommandTimeout.HasValue) { m_dbCommand.CommandTimeout = translator.CommandTimeout.Value; } // set all identifier inputs (to support propagation of identifier values across relationship // boundaries) if (null != m_inputIdentifiers) { foreach (KeyValuePair <int, DbParameter> inputIdentifier in m_inputIdentifiers) { object value; if (identifierValues.TryGetValue(inputIdentifier.Key, out value)) { // set the actual value for the identifier if it has been produced by some // other command inputIdentifier.Value.Value = value; } } } // Execute the query long rowsAffected; if (null != m_resultColumns) { // If there are result columns, read the server gen results rowsAffected = 0; IBaseList <EdmMember> members = TypeHelpers.GetAllStructuralMembers(this.CurrentValues.StructuralType); using (DbDataReader reader = m_dbCommand.ExecuteReader(CommandBehavior.SequentialAccess)) { // Retrieve only the first row from the first result set if (reader.Read()) { rowsAffected++; foreach (var resultColumn in m_resultColumns .Select(r => new KeyValuePair <int, PropagatorResult>(GetColumnOrdinal(translator, reader, r.Key), r.Value)) .OrderBy(r => r.Key)) // order by column ordinal to avoid breaking SequentialAccess readers { int columnOrdinal = resultColumn.Key; TypeUsage columnType = members[resultColumn.Value.RecordOrdinal].TypeUsage; object value; if (Helper.IsSpatialType(columnType) && !reader.IsDBNull(columnOrdinal)) { value = SpatialHelpers.GetSpatialValue(translator.MetadataWorkspace, reader, columnType, columnOrdinal); } else { value = reader.GetValue(columnOrdinal); } // register for back-propagation PropagatorResult result = resultColumn.Value; generatedValues.Add(new KeyValuePair <PropagatorResult, object>(result, value)); // register identifier if it exists int identifier = result.Identifier; if (PropagatorResult.NullIdentifier != identifier) { identifierValues.Add(identifier, value); } } } // Consume the current reader (and subsequent result sets) so that any errors // executing the function can be intercepted CommandHelper.ConsumeReader(reader); } } else { rowsAffected = m_dbCommand.ExecuteNonQuery(); } // if an explicit rows affected parameter exists, use this value instead if (null != m_rowsAffectedParameter) { // by design, negative row counts indicate failure iff. an explicit rows // affected parameter is used if (DBNull.Value.Equals(m_rowsAffectedParameter.Value)) { rowsAffected = 0; } else { try { rowsAffected = Convert.ToInt64(m_rowsAffectedParameter.Value, CultureInfo.InvariantCulture); } catch (Exception e) { if (UpdateTranslator.RequiresContext(e)) { // wrap the exception throw EntityUtil.Update(System.Data.Entity.Strings.Update_UnableToConvertRowsAffectedParameterToInt32( m_rowsAffectedParameter.ParameterName, typeof(int).FullName), e, this.GetStateEntries(translator)); } throw; } } } return(rowsAffected); }