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