/// <summary> /// Performs join propagation. /// </summary> /// <returns>Changes propagated to the current join node in the update mapping view.</returns> internal ChangeNode Propagate() { // Construct an empty change node for the result ChangeNode result = Propagator.BuildChangeNode(m_joinExpression); // Gather all keys involved in the join JoinDictionary leftDeletes = ProcessKeys(m_left.Deleted, m_leftKeySelectors); JoinDictionary leftInserts = ProcessKeys(m_left.Inserted, m_leftKeySelectors); JoinDictionary rightDeletes = ProcessKeys(m_right.Deleted, m_rightKeySelectors); JoinDictionary rightInserts = ProcessKeys(m_right.Inserted, m_rightKeySelectors); var allKeys = leftDeletes.Keys .Concat(leftInserts.Keys) .Concat(rightDeletes.Keys) .Concat(rightInserts.Keys) .Distinct(m_parent.UpdateTranslator.KeyComparer); // Perform propagation one key at a time foreach (CompositeKey key in allKeys) { Propagate(key, result, leftDeletes, leftInserts, rightDeletes, rightInserts); } // Construct a new placeholder (see ChangeNode.Placeholder) for the join result node. result.Placeholder = CreateResultTuple(Tuple.Create((CompositeKey)null, m_left.Placeholder), Tuple.Create((CompositeKey)null, m_right.Placeholder), result); return(result); }
/// <summary> /// Produces a hash table of all instances and processes join keys, adding them to the list /// of keys handled by this node. /// </summary> /// <param name="instances">List of instances (whether delete or insert) for this node.</param> /// <param name="keySelectors">Selectors for key components.</param> /// <returns>A map from join keys to instances.</returns> private JoinDictionary ProcessKeys(IEnumerable <PropagatorResult> instances, ReadOnlyCollection <DbExpression> keySelectors) { // Dictionary uses the composite key on both sides. This is because the composite key, in addition // to supporting comparison, maintains some context information (e.g., source of a value in the // state manager). var hash = new JoinDictionary(m_parent.UpdateTranslator.KeyComparer); foreach (PropagatorResult instance in instances) { CompositeKey key = ExtractKey(instance, keySelectors, m_parent); hash[key] = Tuple.Create(key, instance); } return(hash); }
/// <summary> /// Propagate all changes associated with a particular join key. /// </summary> /// <param name="key">Key.</param> /// <param name="result">Resulting changes are added to this result.</param> private void Propagate(CompositeKey key, ChangeNode result, JoinDictionary leftDeletes, JoinDictionary leftInserts, JoinDictionary rightDeletes, JoinDictionary rightInserts) { // Retrieve changes associates with this join key Tuple <CompositeKey, PropagatorResult> leftInsert = null; Tuple <CompositeKey, PropagatorResult> leftDelete = null; Tuple <CompositeKey, PropagatorResult> rightInsert = null; Tuple <CompositeKey, PropagatorResult> rightDelete = null; Ops input = Ops.Nothing; if (leftInserts.TryGetValue(key, out leftInsert)) { input |= Ops.LeftInsert; } if (leftDeletes.TryGetValue(key, out leftDelete)) { input |= Ops.LeftDelete; } if (rightInserts.TryGetValue(key, out rightInsert)) { input |= Ops.RightInsert; } if (rightDeletes.TryGetValue(key, out rightDelete)) { input |= Ops.RightDelete; } // Get propagation rules for the changes Ops insertRule = m_insertRules[input]; Ops deleteRule = m_deleteRules[input]; if (Ops.Unsupported == insertRule || Ops.Unsupported == deleteRule) { // If no propagation rules are defined, it suggests an invalid workload (e.g. // a required entity or relationship is missing). In general, such exceptions // should be caught by the RelationshipConstraintValidator, but we defensively // check for problems here regardless. For instance, a 0..1:1..1 self-assocation // implied a stronger constraint that cannot be checked by RelationshipConstraintValidator. // First gather state entries contributing to the problem List <IEntityStateEntry> stateEntries = new List <IEntityStateEntry>(); Action <Tuple <CompositeKey, PropagatorResult> > addStateEntries = (r) => { if (r != null) { stateEntries.AddRange(SourceInterpreter.GetAllStateEntries(r.Item2, this.m_parent.m_updateTranslator, this.m_parent.m_table)); } }; addStateEntries(leftInsert); addStateEntries(leftDelete); addStateEntries(rightInsert); addStateEntries(rightDelete); throw EntityUtil.Update(Strings.Update_InvalidChanges, null, stateEntries); } // Where needed, substitute null/unknown placeholders. In some of the join propagation // rules, we handle the case where a side of the join is 'unknown', or where one side // of a join is comprised of an record containing only nulls. For instance, we may update // only one extent appearing in a row of a table (unknown), or; we may insert only // the left hand side of a left outer join, in which case the right hand side is 'null'. if (0 != (Ops.LeftUnknown & insertRule)) { leftInsert = Tuple.Create(key, LeftPlaceholder(key, PopulateMode.Unknown)); } if (0 != (Ops.LeftUnknown & deleteRule)) { leftDelete = Tuple.Create(key, LeftPlaceholder(key, PopulateMode.Unknown)); } if (0 != (Ops.RightNullModified & insertRule)) { rightInsert = Tuple.Create(key, RightPlaceholder(key, PopulateMode.NullModified)); } else if (0 != (Ops.RightNullPreserve & insertRule)) { rightInsert = Tuple.Create(key, RightPlaceholder(key, PopulateMode.NullPreserve)); } else if (0 != (Ops.RightUnknown & insertRule)) { rightInsert = Tuple.Create(key, RightPlaceholder(key, PopulateMode.Unknown)); } if (0 != (Ops.RightNullModified & deleteRule)) { rightDelete = Tuple.Create(key, RightPlaceholder(key, PopulateMode.NullModified)); } else if (0 != (Ops.RightNullPreserve & deleteRule)) { rightDelete = Tuple.Create(key, RightPlaceholder(key, PopulateMode.NullPreserve)); } else if (0 != (Ops.RightUnknown & deleteRule)) { rightDelete = Tuple.Create(key, RightPlaceholder(key, PopulateMode.Unknown)); } // Populate elements in join output if (null != leftInsert && null != rightInsert) { result.Inserted.Add(CreateResultTuple(leftInsert, rightInsert, result)); } if (null != leftDelete && null != rightDelete) { result.Deleted.Add(CreateResultTuple(leftDelete, rightDelete, result)); } }
/// <summary> /// Produces a hash table of all instances and processes join keys, adding them to the list /// of keys handled by this node. /// </summary> /// <param name="instances">List of instances (whether delete or insert) for this node.</param> /// <param name="keySelectors">Selectors for key components.</param> /// <returns>A map from join keys to instances.</returns> private JoinDictionary ProcessKeys(IEnumerable<PropagatorResult> instances, ReadOnlyCollection<DbExpression> keySelectors) { // Dictionary uses the composite key on both sides. This is because the composite key, in addition // to supporting comparison, maintains some context information (e.g., source of a value in the // state manager). var hash = new JoinDictionary(m_parent.UpdateTranslator.KeyComparer); foreach (PropagatorResult instance in instances) { CompositeKey key = ExtractKey(instance, keySelectors, m_parent); hash[key] = Tuple.Create(key, instance); } return hash; }
/// <summary> /// Propagate all changes associated with a particular join key. /// </summary> /// <param name="key">Key.</param> /// <param name="result">Resulting changes are added to this result.</param> private void Propagate(CompositeKey key, ChangeNode result, JoinDictionary leftDeletes, JoinDictionary leftInserts, JoinDictionary rightDeletes, JoinDictionary rightInserts) { // Retrieve changes associates with this join key Tuple<CompositeKey, PropagatorResult> leftInsert = null; Tuple<CompositeKey, PropagatorResult> leftDelete = null; Tuple<CompositeKey, PropagatorResult> rightInsert = null; Tuple<CompositeKey, PropagatorResult> rightDelete = null; Ops input = Ops.Nothing; if (leftInserts.TryGetValue(key, out leftInsert)) { input |= Ops.LeftInsert; } if (leftDeletes.TryGetValue(key, out leftDelete)) { input |= Ops.LeftDelete; } if (rightInserts.TryGetValue(key, out rightInsert)) { input |= Ops.RightInsert; } if (rightDeletes.TryGetValue(key, out rightDelete)) { input |= Ops.RightDelete; } // Get propagation rules for the changes Ops insertRule = m_insertRules[input]; Ops deleteRule = m_deleteRules[input]; if (Ops.Unsupported == insertRule || Ops.Unsupported == deleteRule) { // If no propagation rules are defined, it suggests an invalid workload (e.g. // a required entity or relationship is missing). In general, such exceptions // should be caught by the RelationshipConstraintValidator, but we defensively // check for problems here regardless. For instance, a 0..1:1..1 self-assocation // implied a stronger constraint that cannot be checked by RelationshipConstraintValidator. // First gather state entries contributing to the problem List<IEntityStateEntry> stateEntries = new List<IEntityStateEntry>(); Action<Tuple<CompositeKey, PropagatorResult>> addStateEntries = (r) => { if (r != null) { stateEntries.AddRange(SourceInterpreter.GetAllStateEntries(r.Item2, this.m_parent.m_updateTranslator, this.m_parent.m_table)); } }; addStateEntries(leftInsert); addStateEntries(leftDelete); addStateEntries(rightInsert); addStateEntries(rightDelete); throw EntityUtil.Update(Strings.Update_InvalidChanges, null, stateEntries); } // Where needed, substitute null/unknown placeholders. In some of the join propagation // rules, we handle the case where a side of the join is 'unknown', or where one side // of a join is comprised of an record containing only nulls. For instance, we may update // only one extent appearing in a row of a table (unknown), or; we may insert only // the left hand side of a left outer join, in which case the right hand side is 'null'. if (0 != (Ops.LeftUnknown & insertRule)) { leftInsert = Tuple.Create(key, LeftPlaceholder(key, PopulateMode.Unknown)); } if (0 != (Ops.LeftUnknown & deleteRule)) { leftDelete = Tuple.Create(key, LeftPlaceholder(key, PopulateMode.Unknown)); } if (0 != (Ops.RightNullModified & insertRule)) { rightInsert = Tuple.Create(key, RightPlaceholder(key, PopulateMode.NullModified)); } else if (0 != (Ops.RightNullPreserve & insertRule)) { rightInsert = Tuple.Create(key, RightPlaceholder(key, PopulateMode.NullPreserve)); } else if (0 != (Ops.RightUnknown & insertRule)) { rightInsert = Tuple.Create(key, RightPlaceholder(key, PopulateMode.Unknown)); } if (0 != (Ops.RightNullModified & deleteRule)) { rightDelete = Tuple.Create(key, RightPlaceholder(key, PopulateMode.NullModified)); } else if (0 != (Ops.RightNullPreserve & deleteRule)) { rightDelete = Tuple.Create(key, RightPlaceholder(key, PopulateMode.NullPreserve)); } else if (0 != (Ops.RightUnknown & deleteRule)) { rightDelete = Tuple.Create(key, RightPlaceholder(key, PopulateMode.Unknown)); } // Populate elements in join output if (null != leftInsert && null != rightInsert) { result.Inserted.Add(CreateResultTuple(leftInsert, rightInsert, result)); } if (null != leftDelete && null != rightDelete) { result.Deleted.Add(CreateResultTuple(leftDelete, rightDelete, result)); } }