1 //--------------------------------------------------------------------- 2 // <copyright file="UpdateTranslator.TableChangeProcessor.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 // 6 // @owner Microsoft 7 // @backupOwner Microsoft 8 //--------------------------------------------------------------------- 9 10 namespace System.Data.Mapping.Update.Internal 11 { 12 using System.Collections.Generic; 13 using System.Data.Common; 14 using System.Data.Common.Utils; 15 using System.Data.Entity; 16 using System.Data.Metadata.Edm; 17 using System.Diagnostics; 18 using System.Linq; 19 20 /// <summary> 21 /// Processes changes applying to a table by merging inserts and deletes into updates 22 /// where appropriate. 23 /// </summary> 24 /// <remarks> 25 /// This class is essentially responsible for identifying inserts, deletes 26 /// and updates in a particular table based on the <see cref="ChangeNode" /> 27 /// produced by value propagation w.r.t. the update mapping view for that table. 28 /// Assumes the change node includes at most a single insert and at most a single delete 29 /// for a given key (where we have both, the change is treated as an update). 30 /// </remarks> 31 internal class TableChangeProcessor 32 { 33 #region Constructors 34 /// <summary> 35 /// Constructs processor based on the contents of a change node. 36 /// </summary> 37 /// <param name="table">Table for which changes are being processed.</param> TableChangeProcessor(EntitySet table)38 internal TableChangeProcessor(EntitySet table) 39 { 40 EntityUtil.CheckArgumentNull(table, "table"); 41 42 m_table = table; 43 44 // cache information about table key 45 m_keyOrdinals = InitializeKeyOrdinals(table); 46 } 47 #endregion 48 49 #region Fields 50 private readonly EntitySet m_table; 51 private readonly int[] m_keyOrdinals; 52 #endregion 53 54 #region Properties 55 /// <summary> 56 /// Gets metadata for the table being modified. 57 /// </summary> 58 internal EntitySet Table 59 { 60 get { return m_table; } 61 } 62 63 /// <summary> 64 /// Gets a map from column ordinal to property descriptions for columns that are components of the table's 65 /// primary key. 66 /// </summary> 67 internal int[] KeyOrdinals { get { return m_keyOrdinals; } } 68 #endregion 69 70 #region Methods 71 // Determines whether the given ordinal position in the property list 72 // for this table is a key value. IsKeyProperty(int propertyOrdinal)73 internal bool IsKeyProperty(int propertyOrdinal) 74 { 75 foreach (int keyOrdinal in m_keyOrdinals) 76 { 77 if (propertyOrdinal == keyOrdinal) { return true; } 78 } 79 return false; 80 } 81 82 // Determines which column ordinals in the table are part of the key. InitializeKeyOrdinals(EntitySet table)83 private static int[] InitializeKeyOrdinals(EntitySet table) 84 { 85 EntityType tableType = table.ElementType; 86 IList<EdmMember> keyMembers = tableType.KeyMembers; 87 IBaseList<EdmMember> members = TypeHelpers.GetAllStructuralMembers(tableType); 88 int[] keyOrdinals = new int[keyMembers.Count]; 89 90 for (int keyMemberIndex = 0; keyMemberIndex < keyMembers.Count; keyMemberIndex++) 91 { 92 EdmMember keyMember = keyMembers[keyMemberIndex]; 93 keyOrdinals[keyMemberIndex] = members.IndexOf(keyMember); 94 95 Debug.Assert(keyOrdinals[keyMemberIndex] >= 0 && keyOrdinals[keyMemberIndex] < members.Count, 96 "an EntityType key member must also be a member of the entity type"); 97 } 98 99 return keyOrdinals; 100 } 101 102 // Processes all insert and delete requests in the table's <see cref="ChangeNode" />. Inserts 103 // and deletes with the same key are merged into updates. CompileCommands(ChangeNode changeNode, UpdateCompiler compiler)104 internal List<UpdateCommand> CompileCommands(ChangeNode changeNode, UpdateCompiler compiler) 105 { 106 Set<CompositeKey> keys = new Set<CompositeKey>(compiler.m_translator.KeyComparer); 107 108 // Retrieve all delete results (original values) and insert results (current values) while 109 // populating a set of all row keys. The set contains a single key per row. 110 Dictionary<CompositeKey, PropagatorResult> deleteResults = ProcessKeys(compiler, changeNode.Deleted, keys); 111 Dictionary<CompositeKey, PropagatorResult> insertResults = ProcessKeys(compiler, changeNode.Inserted, keys); 112 113 List<UpdateCommand> commands = new List<UpdateCommand>(deleteResults.Count + insertResults.Count); 114 115 // Examine each row key to see if the row is being deleted, inserted or updated 116 foreach (CompositeKey key in keys) 117 { 118 PropagatorResult deleteResult; 119 PropagatorResult insertResult; 120 121 bool hasDelete = deleteResults.TryGetValue(key, out deleteResult); 122 bool hasInsert = insertResults.TryGetValue(key, out insertResult); 123 124 Debug.Assert(hasDelete || hasInsert, "(update/TableChangeProcessor) m_keys must not contain a value " + 125 "if there is no corresponding insert or delete"); 126 127 try 128 { 129 if (!hasDelete) 130 { 131 // this is an insert 132 commands.Add(compiler.BuildInsertCommand(insertResult, this)); 133 } 134 else if (!hasInsert) 135 { 136 // this is a delete 137 commands.Add(compiler.BuildDeleteCommand(deleteResult, this)); 138 } 139 else 140 { 141 // this is an update because it has both a delete result and an insert result 142 UpdateCommand updateCommand = compiler.BuildUpdateCommand(deleteResult, insertResult, this); 143 if (null != updateCommand) 144 { 145 // if null is returned, it means it is a no-op update 146 commands.Add(updateCommand); 147 } 148 } 149 } 150 catch (Exception e) 151 { 152 if (UpdateTranslator.RequiresContext(e)) 153 { 154 // collect state entries in scope for the current compilation 155 List<IEntityStateEntry> stateEntries = new List<IEntityStateEntry>(); 156 if (null != deleteResult) 157 { 158 stateEntries.AddRange(SourceInterpreter.GetAllStateEntries( 159 deleteResult, compiler.m_translator, m_table)); 160 } 161 if (null != insertResult) 162 { 163 stateEntries.AddRange(SourceInterpreter.GetAllStateEntries( 164 insertResult, compiler.m_translator, m_table)); 165 } 166 167 throw EntityUtil.Update(System.Data.Entity.Strings.Update_GeneralExecutionException, 168 e, stateEntries); 169 } 170 throw; 171 } 172 } 173 174 return commands; 175 } 176 177 // Determines key values for a list of changes. Side effect: populates <see cref="keys" /> which 178 // includes an entry for every key involved in a change. ProcessKeys(UpdateCompiler compiler, List<PropagatorResult> changes, Set<CompositeKey> keys)179 private Dictionary<CompositeKey, PropagatorResult> ProcessKeys(UpdateCompiler compiler, List<PropagatorResult> changes, Set<CompositeKey> keys) 180 { 181 Dictionary<CompositeKey, PropagatorResult> map = new Dictionary<CompositeKey, PropagatorResult>( 182 compiler.m_translator.KeyComparer); 183 184 foreach (PropagatorResult change in changes) 185 { 186 // Reassign change to row since we cannot modify iteration variable 187 PropagatorResult row = change; 188 189 CompositeKey key = new CompositeKey(GetKeyConstants(row)); 190 191 // Make sure we aren't inserting another row with the same key 192 PropagatorResult other; 193 if (map.TryGetValue(key, out other)) 194 { 195 DiagnoseKeyCollision(compiler, change, key, other); 196 } 197 198 map.Add(key, row); 199 keys.Add(key); 200 } 201 202 return map; 203 } 204 205 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2140:TransparentMethodsMustNotReferenceCriticalCode", Justification = "Based on Bug VSTS Pioneer #433188: IsVisibleOutsideAssembly is wrong on generic instantiations.")] DiagnoseKeyCollision(UpdateCompiler compiler, PropagatorResult change, CompositeKey key, PropagatorResult other)206 private void DiagnoseKeyCollision(UpdateCompiler compiler, PropagatorResult change, CompositeKey key, PropagatorResult other) 207 { 208 KeyManager keyManager = compiler.m_translator.KeyManager; 209 CompositeKey otherKey = new CompositeKey(GetKeyConstants(other)); 210 211 // determine if the conflict is due to shared principal key values 212 bool sharedPrincipal = true; 213 for (int i = 0; sharedPrincipal && i < key.KeyComponents.Length; i++) 214 { 215 int identifier1 = key.KeyComponents[i].Identifier; 216 int identifier2 = otherKey.KeyComponents[i].Identifier; 217 218 if (!keyManager.GetPrincipals(identifier1).Intersect(keyManager.GetPrincipals(identifier2)).Any()) 219 { 220 sharedPrincipal = false; 221 } 222 } 223 224 if (sharedPrincipal) 225 { 226 // if the duplication is due to shared principals, there is a duplicate key exception 227 var stateEntries = SourceInterpreter.GetAllStateEntries(change, compiler.m_translator, m_table) 228 .Concat(SourceInterpreter.GetAllStateEntries(other, compiler.m_translator, m_table)); 229 throw EntityUtil.Update(Strings.Update_DuplicateKeys, null, stateEntries); 230 } 231 else 232 { 233 // if there are no shared principals, it implies that common dependents are the problem 234 HashSet<IEntityStateEntry> commonDependents = null; 235 foreach (PropagatorResult keyValue in key.KeyComponents.Concat(otherKey.KeyComponents)) 236 { 237 var dependents = new HashSet<IEntityStateEntry>(); 238 foreach (int dependentId in keyManager.GetDependents(keyValue.Identifier)) 239 { 240 PropagatorResult dependentResult; 241 if (keyManager.TryGetIdentifierOwner(dependentId, out dependentResult) && 242 null != dependentResult.StateEntry) 243 { 244 dependents.Add(dependentResult.StateEntry); 245 } 246 } 247 if (null == commonDependents) 248 { 249 commonDependents = new HashSet<IEntityStateEntry>(dependents); 250 } 251 else 252 { 253 commonDependents.IntersectWith(dependents); 254 } 255 } 256 257 // to ensure the exception shape is consistent with constraint violations discovered while processing 258 // commands (a more conventional scenario in which different tables are contributing principal values) 259 // wrap a DataConstraintException in an UpdateException 260 throw EntityUtil.Update(Strings.Update_GeneralExecutionException, 261 EntityUtil.Constraint(Strings.Update_ReferentialConstraintIntegrityViolation), commonDependents); 262 } 263 } 264 265 // Extracts key constants from the given row. GetKeyConstants(PropagatorResult row)266 private PropagatorResult[] GetKeyConstants(PropagatorResult row) 267 { 268 PropagatorResult[] keyConstants = new PropagatorResult[m_keyOrdinals.Length]; 269 for (int i = 0; i < m_keyOrdinals.Length; i++) 270 { 271 PropagatorResult constant = row.GetMemberValue(m_keyOrdinals[i]); 272 273 keyConstants[i] = constant; 274 } 275 return keyConstants; 276 } 277 #endregion 278 } 279 } 280