// Determines key values for a list of changes. Side effect: populates <see cref="keys" /> which // includes an entry for every key involved in a change. private Dictionary <CompositeKey, PropagatorResult> ProcessKeys(UpdateCompiler compiler, List <PropagatorResult> changes, Set <CompositeKey> keys) { Dictionary <CompositeKey, PropagatorResult> map = new Dictionary <CompositeKey, PropagatorResult>( compiler.m_translator.KeyComparer); foreach (PropagatorResult change in changes) { // Reassign change to row since we cannot modify iteration variable PropagatorResult row = change; CompositeKey key = new CompositeKey(GetKeyConstants(row)); // Make sure we aren't inserting another row with the same key PropagatorResult other; if (map.TryGetValue(key, out other)) { DiagnoseKeyCollision(compiler, change, key, other); } map.Add(key, row); keys.Add(key); } return(map); }
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); } }
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); } }
// Determines key values for a list of changes. Side effect: populates <see cref="keys" /> which // includes an entry for every key involved in a change. private Dictionary<CompositeKey, PropagatorResult> ProcessKeys(UpdateCompiler compiler, List<PropagatorResult> changes, Set<CompositeKey> keys) { Dictionary<CompositeKey, PropagatorResult> map = new Dictionary<CompositeKey, PropagatorResult>( compiler.m_translator.KeyComparer); foreach (PropagatorResult change in changes) { // Reassign change to row since we cannot modify iteration variable PropagatorResult row = change; CompositeKey key = new CompositeKey(GetKeyConstants(row)); // Make sure we aren't inserting another row with the same key PropagatorResult other; if (map.TryGetValue(key, out other)) { DiagnoseKeyCollision(compiler, change, key, other); } map.Add(key, row); keys.Add(key); } return map; }
// Processes all insert and delete requests in the table's <see cref="ChangeNode" />. Inserts // and deletes with the same key are merged into updates. internal List<UpdateCommand> CompileCommands(ChangeNode changeNode, UpdateCompiler compiler) { Set<CompositeKey> keys = new Set<CompositeKey>(compiler.m_translator.KeyComparer); // Retrieve all delete results (original values) and insert results (current values) while // populating a set of all row keys. The set contains a single key per row. Dictionary<CompositeKey, PropagatorResult> deleteResults = ProcessKeys(compiler, changeNode.Deleted, keys); Dictionary<CompositeKey, PropagatorResult> insertResults = ProcessKeys(compiler, changeNode.Inserted, keys); List<UpdateCommand> commands = new List<UpdateCommand>(deleteResults.Count + insertResults.Count); // Examine each row key to see if the row is being deleted, inserted or updated foreach (CompositeKey key in keys) { PropagatorResult deleteResult; PropagatorResult insertResult; bool hasDelete = deleteResults.TryGetValue(key, out deleteResult); bool hasInsert = insertResults.TryGetValue(key, out insertResult); Debug.Assert(hasDelete || hasInsert, "(update/TableChangeProcessor) m_keys must not contain a value " + "if there is no corresponding insert or delete"); try { if (!hasDelete) { // this is an insert commands.Add(compiler.BuildInsertCommand(insertResult, this)); } else if (!hasInsert) { // this is a delete commands.Add(compiler.BuildDeleteCommand(deleteResult, this)); } else { // this is an update because it has both a delete result and an insert result UpdateCommand updateCommand = compiler.BuildUpdateCommand(deleteResult, insertResult, this); if (null != updateCommand) { // if null is returned, it means it is a no-op update commands.Add(updateCommand); } } } catch (Exception e) { if (UpdateTranslator.RequiresContext(e)) { // collect state entries in scope for the current compilation List<IEntityStateEntry> stateEntries = new List<IEntityStateEntry>(); if (null != deleteResult) { stateEntries.AddRange(SourceInterpreter.GetAllStateEntries( deleteResult, compiler.m_translator, m_table)); } if (null != insertResult) { stateEntries.AddRange(SourceInterpreter.GetAllStateEntries( insertResult, compiler.m_translator, m_table)); } throw EntityUtil.Update(System.Data.Entity.Strings.Update_GeneralExecutionException, e, stateEntries); } throw; } } return commands; }
/// <summary> /// Produce dynamic store commands for this translator's changes. /// </summary> /// <returns>Database commands in a safe order</returns> private IEnumerable<UpdateCommand> ProduceDynamicCommands() { // Initialize DBCommand update compiler UpdateCompiler updateCompiler = new UpdateCompiler(this); // Determine affected Set<EntitySet> tables = new Set<EntitySet>(); foreach (EntitySetBase extent in GetDynamicModifiedExtents()) { Set<EntitySet> affectedTables = m_viewLoader.GetAffectedTables(extent, m_metadataWorkspace); //Since these extents don't have Functions defined for update operations, //the affected tables should be provided via MSL. //If we dont find any throw an exception if (affectedTables.Count == 0) { throw EntityUtil.Update(System.Data.Entity.Strings.Update_MappingNotFound( extent.Name), null /*stateEntries*/); } foreach (EntitySet table in affectedTables) { tables.Add(table); } } // Determine changes to apply to each table foreach (EntitySet table in tables) { DbQueryCommandTree umView = m_connection.GetMetadataWorkspace().GetCqtView(table); // Propagate changes to root of tree (at which point they are S-Space changes) ChangeNode changeNode = Propagator.Propagate(this, table, umView); // Process changes for the table TableChangeProcessor change = new TableChangeProcessor(table); foreach (UpdateCommand command in change.CompileCommands(changeNode, updateCompiler)) { yield return command; } } }
// Processes all insert and delete requests in the table's <see cref="ChangeNode" />. Inserts // and deletes with the same key are merged into updates. internal List <UpdateCommand> CompileCommands(ChangeNode changeNode, UpdateCompiler compiler) { Set <CompositeKey> keys = new Set <CompositeKey>(compiler.m_translator.KeyComparer); // Retrieve all delete results (original values) and insert results (current values) while // populating a set of all row keys. The set contains a single key per row. Dictionary <CompositeKey, PropagatorResult> deleteResults = ProcessKeys(compiler, changeNode.Deleted, keys); Dictionary <CompositeKey, PropagatorResult> insertResults = ProcessKeys(compiler, changeNode.Inserted, keys); List <UpdateCommand> commands = new List <UpdateCommand>(deleteResults.Count + insertResults.Count); // Examine each row key to see if the row is being deleted, inserted or updated foreach (CompositeKey key in keys) { PropagatorResult deleteResult; PropagatorResult insertResult; bool hasDelete = deleteResults.TryGetValue(key, out deleteResult); bool hasInsert = insertResults.TryGetValue(key, out insertResult); Debug.Assert(hasDelete || hasInsert, "(update/TableChangeProcessor) m_keys must not contain a value " + "if there is no corresponding insert or delete"); try { if (!hasDelete) { // this is an insert commands.Add(compiler.BuildInsertCommand(insertResult, this)); } else if (!hasInsert) { // this is a delete commands.Add(compiler.BuildDeleteCommand(deleteResult, this)); } else { // this is an update because it has both a delete result and an insert result UpdateCommand updateCommand = compiler.BuildUpdateCommand(deleteResult, insertResult, this); if (null != updateCommand) { // if null is returned, it means it is a no-op update commands.Add(updateCommand); } } } catch (Exception e) { if (UpdateTranslator.RequiresContext(e)) { // collect state entries in scope for the current compilation List <IEntityStateEntry> stateEntries = new List <IEntityStateEntry>(); if (null != deleteResult) { stateEntries.AddRange(SourceInterpreter.GetAllStateEntries( deleteResult, compiler.m_translator, m_table)); } if (null != insertResult) { stateEntries.AddRange(SourceInterpreter.GetAllStateEntries( insertResult, compiler.m_translator, m_table)); } throw EntityUtil.Update(System.Data.Entity.Strings.Update_GeneralExecutionException, e, stateEntries); } throw; } } return(commands); }