1 //---------------------------------------------------------------------
2 // <copyright file="UpdateTranslator.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9 
10 using System.Collections.Generic;
11 using System.Data.Objects;
12 using System.Data.Common.Utils;
13 using System.Data.Common.CommandTrees;
14 using System.Data.Common;
15 using System.Threading;
16 using System.Collections.ObjectModel;
17 using System.Diagnostics;
18 using System.Data.Metadata.Edm;
19 using System.Data.EntityClient;
20 using System.Data.Spatial;
21 using System.Globalization;
22 using System.Data.Entity;
23 using System.Linq;
24 
25 namespace System.Data.Mapping.Update.Internal
26 {
27     /// <summary>
28     /// This class performs to following tasks to persist C-Space changes to the store:
29     /// <list>
30     /// <item>Extract changes from the entity state manager</item>
31     /// <item>Group changes by C-Space extent</item>
32     /// <item>For each affected S-Space table, perform propagation (get changes in S-Space terms)</item>
33     /// <item>Merge S-Space inserts and deletes into updates where appropriate</item>
34     /// <item>Produce S-Space commands implementating the modifications (insert, delete and update SQL statements)</item>
35     /// </list>
36     /// </summary>
37     internal partial class UpdateTranslator
38     {
39         #region Constructors
40         /// <summary>
41         /// Constructs a grouper based on the contents of the given entity state manager.
42         /// </summary>
43         /// <param name="stateManager">Entity state manager containing changes to be processed.</param>
44         /// <param name="metadataWorkspace">Metadata workspace.</param>
45         /// <param name="connection">Map connection</param>
46         /// <param name="commandTimeout">Timeout for update commands; null means 'use provider default'</param>
UpdateTranslator(IEntityStateManager stateManager, MetadataWorkspace metadataWorkspace, EntityConnection connection, int? commandTimeout)47         private UpdateTranslator(IEntityStateManager stateManager, MetadataWorkspace metadataWorkspace, EntityConnection connection, int? commandTimeout)
48         {
49             EntityUtil.CheckArgumentNull(stateManager, "stateManager");
50             EntityUtil.CheckArgumentNull(metadataWorkspace, "metadataWorkspace");
51             EntityUtil.CheckArgumentNull(connection, "connection");
52 
53             // propagation state
54             m_changes = new Dictionary<EntitySetBase, ChangeNode>();
55             m_functionChanges = new Dictionary<EntitySetBase, List<ExtractedStateEntry>>();
56             m_stateEntries = new List<IEntityStateEntry>();
57             m_knownEntityKeys = new Set<EntityKey>();
58             m_requiredEntities = new Dictionary<EntityKey, AssociationSet>();
59             m_optionalEntities = new Set<EntityKey>();
60             m_includedValueEntities = new Set<EntityKey>();
61 
62             // workspace state
63             m_metadataWorkspace = metadataWorkspace;
64             m_viewLoader = metadataWorkspace.GetUpdateViewLoader();
65             m_stateManager = stateManager;
66 
67             // ancillary propagation services
68             m_recordConverter = new RecordConverter(this);
69             m_constraintValidator = new RelationshipConstraintValidator(this);
70 
71             m_providerServices = DbProviderServices.GetProviderServices(connection.StoreProviderFactory);
72             m_connection = connection;
73             m_commandTimeout = commandTimeout;
74 
75             // metadata cache
76             m_extractorMetadata = new Dictionary<Tuple<EntitySetBase, StructuralType>, ExtractorMetadata>(); ;
77 
78             // key management
79             KeyManager = new KeyManager(this);
80             KeyComparer = CompositeKey.CreateComparer(KeyManager);
81         }
82 
83         #endregion
84 
85         #region Fields
86         // propagation state
87         private readonly Dictionary<EntitySetBase, ChangeNode> m_changes;
88         private readonly Dictionary<EntitySetBase, List<ExtractedStateEntry>> m_functionChanges;
89         private readonly List<IEntityStateEntry> m_stateEntries;
90         private readonly Set<EntityKey> m_knownEntityKeys;
91         private readonly Dictionary<EntityKey, AssociationSet> m_requiredEntities;
92         private readonly Set<EntityKey> m_optionalEntities;
93         private readonly Set<EntityKey> m_includedValueEntities;
94 
95         // workspace state
96         private readonly MetadataWorkspace m_metadataWorkspace;
97         private readonly ViewLoader m_viewLoader;
98         private readonly IEntityStateManager m_stateManager;
99 
100         // ancillary propagation services
101         private readonly RecordConverter m_recordConverter;
102         private readonly RelationshipConstraintValidator m_constraintValidator;
103 
104         // provider information
105         private readonly DbProviderServices m_providerServices;
106         private readonly EntityConnection m_connection;
107         private readonly int? m_commandTimeout;
108         private Dictionary<StorageModificationFunctionMapping, DbCommandDefinition> m_modificationFunctionCommandDefinitions;
109 
110         // metadata cache
111         private readonly Dictionary<Tuple<EntitySetBase, StructuralType>, ExtractorMetadata> m_extractorMetadata;
112 
113         // static members
114         private static readonly List<string> s_emptyMemberList = new List<string>();
115         #endregion
116 
117         #region Properties
118         /// <summary>
119         /// Gets workspace used in this session.
120         /// </summary>
121         internal MetadataWorkspace MetadataWorkspace
122         {
123             get { return m_metadataWorkspace; }
124         }
125 
126         /// <summary>
127         /// Gets key manager that handles interpretation of keys (including resolution of
128         /// referential-integrity/foreign key constraints)
129         /// </summary>
130         internal readonly KeyManager KeyManager;
131 
132         /// <summary>
133         /// Gets the view loader metadata wrapper for the current workspace.
134         /// </summary>
135         internal ViewLoader ViewLoader
136         {
137             get { return m_viewLoader; }
138         }
139 
140         /// <summary>
141         /// Gets record converter which translates state entry records into propagator results.
142         /// </summary>
143         internal RecordConverter RecordConverter
144         {
145             get { return m_recordConverter; }
146         }
147 
148         /// <summary>
149         /// Gets command timeout for update commands. If null, use default.
150         /// </summary>
151         internal int? CommandTimeout
152         {
153             get { return m_commandTimeout; }
154         }
155 
156         internal readonly IEqualityComparer<CompositeKey> KeyComparer;
157         #endregion
158 
159         #region Methods
160         /// <summary>
161         /// Registers any referential constraints contained in the state entry (so that
162         /// constrained members have the same identifier values). Only processes relationships
163         /// with referential constraints defined.
164         /// </summary>
165         /// <param name="stateEntry">State entry</param>
RegisterReferentialConstraints(IEntityStateEntry stateEntry)166         internal void RegisterReferentialConstraints(IEntityStateEntry stateEntry)
167         {
168             if (stateEntry.IsRelationship)
169             {
170                 AssociationSet associationSet = (AssociationSet)stateEntry.EntitySet;
171                 if (0 < associationSet.ElementType.ReferentialConstraints.Count)
172                 {
173                     DbDataRecord record = stateEntry.State == EntityState.Added ?
174                         (DbDataRecord)stateEntry.CurrentValues : stateEntry.OriginalValues;
175                     foreach (ReferentialConstraint constraint in associationSet.ElementType.ReferentialConstraints)
176                     {
177                         // retrieve keys at the ends
178                         EntityKey principalKey = (EntityKey)record[constraint.FromRole.Name];
179                         EntityKey dependentKey = (EntityKey)record[constraint.ToRole.Name];
180 
181                         // associate keys, where the from side 'owns' the to side
182                         using (ReadOnlyMetadataCollection<EdmProperty>.Enumerator principalPropertyEnum = constraint.FromProperties.GetEnumerator())
183                         using (ReadOnlyMetadataCollection<EdmProperty>.Enumerator dependentPropertyEnum = constraint.ToProperties.GetEnumerator())
184                         {
185                             while (principalPropertyEnum.MoveNext() && dependentPropertyEnum.MoveNext())
186                             {
187                                 int principalKeyMemberCount;
188                                 int dependentKeyMemberCount;
189 
190                                 // get offsets for from and to key properties
191                                 int principalOffset = GetKeyMemberOffset(constraint.FromRole, principalPropertyEnum.Current,
192                                     out principalKeyMemberCount);
193                                 int dependentOffset = GetKeyMemberOffset(constraint.ToRole, dependentPropertyEnum.Current,
194                                     out dependentKeyMemberCount);
195 
196                                 int principalIdentifier = this.KeyManager.GetKeyIdentifierForMemberOffset(principalKey, principalOffset, principalKeyMemberCount);
197                                 int dependentIdentifier = this.KeyManager.GetKeyIdentifierForMemberOffset(dependentKey, dependentOffset, dependentKeyMemberCount);
198 
199                                 // register equivalence of identifiers
200                                 this.KeyManager.AddReferentialConstraint(stateEntry, dependentIdentifier, principalIdentifier);
201                             }
202                         }
203                     }
204                 }
205             }
206             else if (!stateEntry.IsKeyEntry)
207             {
208                 if (stateEntry.State == EntityState.Added || stateEntry.State == EntityState.Modified)
209                 {
210                     RegisterEntityReferentialConstraints(stateEntry, true);
211                 }
212                 if (stateEntry.State == EntityState.Deleted || stateEntry.State == EntityState.Modified)
213                 {
214                     RegisterEntityReferentialConstraints(stateEntry, false);
215                 }
216             }
217         }
218 
RegisterEntityReferentialConstraints(IEntityStateEntry stateEntry, bool currentValues)219         private void RegisterEntityReferentialConstraints(IEntityStateEntry stateEntry, bool currentValues)
220         {
221             IExtendedDataRecord record = currentValues
222                 ? (IExtendedDataRecord)stateEntry.CurrentValues
223                 : (IExtendedDataRecord)stateEntry.OriginalValues;
224             EntitySet entitySet = (EntitySet)stateEntry.EntitySet;
225             EntityKey dependentKey = stateEntry.EntityKey;
226 
227             foreach (var foreignKey in entitySet.ForeignKeyDependents)
228             {
229                 AssociationSet associationSet = foreignKey.Item1;
230                 ReferentialConstraint constraint = foreignKey.Item2;
231                 EntityType dependentType = MetadataHelper.GetEntityTypeForEnd((AssociationEndMember)constraint.ToRole);
232                 if (dependentType.IsAssignableFrom(record.DataRecordInfo.RecordType.EdmType))
233                 {
234                     EntityKey principalKey = null;
235 
236                     // First, check for an explicit reference
237                     if (!currentValues || !m_stateManager.TryGetReferenceKey(dependentKey, (AssociationEndMember)constraint.FromRole, out principalKey))
238                     {
239                         // build a key based on the foreign key values
240                         EntityType principalType = MetadataHelper.GetEntityTypeForEnd((AssociationEndMember)constraint.FromRole);
241                         bool hasNullValue = false;
242                         object[] keyValues = new object[principalType.KeyMembers.Count];
243                         for (int i = 0, n = keyValues.Length; i < n; i++)
244                         {
245                             EdmProperty keyMember = (EdmProperty)principalType.KeyMembers[i];
246 
247                             // Find corresponding foreign key value
248                             int constraintOrdinal = constraint.FromProperties.IndexOf((EdmProperty)keyMember);
249                             int recordOrdinal = record.GetOrdinal(constraint.ToProperties[constraintOrdinal].Name);
250                             if (record.IsDBNull(recordOrdinal))
251                             {
252                                 hasNullValue = true;
253                                 break;
254                             }
255                             keyValues[i] = record.GetValue(recordOrdinal);
256                         }
257 
258                         if (!hasNullValue)
259                         {
260                             EntitySet principalSet = associationSet.AssociationSetEnds[constraint.FromRole.Name].EntitySet;
261                             if (1 == keyValues.Length)
262                             {
263                                 principalKey = new EntityKey(principalSet, keyValues[0]);
264                             }
265                             else
266                             {
267                                 principalKey = new EntityKey(principalSet, keyValues);
268                             }
269                         }
270                     }
271 
272                     if (null != principalKey)
273                     {
274                         // find the right principal key... (first, existing entities; then, added entities; finally, just the key)
275                         IEntityStateEntry existingPrincipal;
276                         EntityKey tempKey;
277                         if (m_stateManager.TryGetEntityStateEntry(principalKey, out existingPrincipal))
278                         {
279                             // nothing to do. the principal key will resolve to the existing entity
280                         }
281                         else if (currentValues && this.KeyManager.TryGetTempKey(principalKey, out tempKey))
282                         {
283                             // if we aren't dealing with current values, we cannot resolve to a temp key (original values
284                             // cannot indicate a relationship to an 'added' entity).
285                             if (null == tempKey)
286                             {
287                                 throw EntityUtil.Update(Strings.Update_AmbiguousForeignKey(constraint.ToRole.DeclaringType.FullName), null, stateEntry);
288                             }
289                             else
290                             {
291                                 principalKey = tempKey;
292                             }
293                         }
294 
295                         // pull the principal end into the update pipeline (supports value propagation)
296                         AddValidAncillaryKey(principalKey, m_optionalEntities);
297 
298                         // associate keys, where the from side 'owns' the to side
299                         for (int i = 0, n = constraint.FromProperties.Count; i < n; i++)
300                         {
301                             var principalProperty = constraint.FromProperties[i];
302                             var dependentProperty = constraint.ToProperties[i];
303 
304                             int principalKeyMemberCount;
305 
306                             // get offsets for from and to key properties
307                             int principalOffset = GetKeyMemberOffset(constraint.FromRole, principalProperty, out principalKeyMemberCount);
308                             int principalIdentifier = this.KeyManager.GetKeyIdentifierForMemberOffset(principalKey, principalOffset, principalKeyMemberCount);
309                             int dependentIdentifier;
310 
311                             if (entitySet.ElementType.KeyMembers.Contains(dependentProperty))
312                             {
313                                 int dependentKeyMemberCount;
314                                 int dependentOffset = GetKeyMemberOffset(constraint.ToRole, dependentProperty,
315                                     out dependentKeyMemberCount);
316                                 dependentIdentifier = this.KeyManager.GetKeyIdentifierForMemberOffset(dependentKey, dependentOffset, dependentKeyMemberCount);
317                             }
318                             else
319                             {
320                                 dependentIdentifier = this.KeyManager.GetKeyIdentifierForMember(dependentKey, dependentProperty.Name, currentValues);
321                             }
322 
323                             // don't allow the user to insert or update an entity that refers to a deleted principal
324                             if (currentValues && null != existingPrincipal && existingPrincipal.State == EntityState.Deleted &&
325                                 (stateEntry.State == EntityState.Added || stateEntry.State == EntityState.Modified))
326                             {
327                                 throw EntityUtil.Update(
328                                     Strings.Update_InsertingOrUpdatingReferenceToDeletedEntity(associationSet.ElementType.FullName),
329                                     null,
330                                     stateEntry,
331                                     existingPrincipal);
332                             }
333 
334                             // register equivalence of identifiers
335                             this.KeyManager.AddReferentialConstraint(stateEntry, dependentIdentifier, principalIdentifier);
336                         }
337                     }
338                 }
339             }
340         }
341 
342         // requires: role must not be null and property must be a key member for the role end
GetKeyMemberOffset(RelationshipEndMember role, EdmProperty property, out int keyMemberCount)343         private static int GetKeyMemberOffset(RelationshipEndMember role, EdmProperty property, out int keyMemberCount)
344         {
345             Debug.Assert(null != role);
346             Debug.Assert(null != property);
347             Debug.Assert(BuiltInTypeKind.RefType == role.TypeUsage.EdmType.BuiltInTypeKind,
348                 "relationship ends must be of RefType");
349             RefType endType = (RefType)role.TypeUsage.EdmType;
350             Debug.Assert(BuiltInTypeKind.EntityType == endType.ElementType.BuiltInTypeKind,
351                 "relationship ends must reference EntityType");
352             EntityType entityType = (EntityType)endType.ElementType;
353             keyMemberCount = entityType.KeyMembers.Count;
354             return entityType.KeyMembers.IndexOf(property);
355         }
356 
357         /// <summary>
358         /// Yields all relationship state entries with the given key as an end.
359         /// </summary>
360         /// <param name="entityKey"></param>
361         /// <returns></returns>
GetRelationships(EntityKey entityKey)362         internal IEnumerable<IEntityStateEntry> GetRelationships(EntityKey entityKey)
363         {
364             return m_stateManager.FindRelationshipsByKey(entityKey);
365         }
366 
367         /// <summary>
368         /// Persists stateManager changes to the store.
369         /// </summary>
370         /// <param name="stateManager">StateManager containing changes to persist.</param>
371         /// <param name="adapter">Map adapter requesting the changes.</param>
372         /// <returns>Total number of state entries affected</returns>
Update(IEntityStateManager stateManager, IEntityAdapter adapter)373         internal static Int32 Update(IEntityStateManager stateManager, IEntityAdapter adapter)
374         {
375             // provider/connection details
376             EntityConnection connection = (EntityConnection)adapter.Connection;
377             MetadataWorkspace metadataWorkspace = connection.GetMetadataWorkspace();
378             int? commandTimeout = adapter.CommandTimeout;
379 
380             UpdateTranslator translator = new UpdateTranslator(stateManager, metadataWorkspace, connection, commandTimeout);
381 
382             // tracks values for identifiers in this session
383             Dictionary<int, object> identifierValues = new Dictionary<int, object>();
384 
385             // tracks values for generated values in this session
386             List<KeyValuePair<PropagatorResult, object>> generatedValues = new List<KeyValuePair<PropagatorResult, object>>();
387 
388             IEnumerable<UpdateCommand> orderedCommands = translator.ProduceCommands();
389 
390             // used to track the source of commands being processed in case an exception is thrown
391             UpdateCommand source = null;
392             try
393             {
394                 foreach (UpdateCommand command in orderedCommands)
395                 {
396                     // Remember the data sources so that we can throw meaningful exception
397                     source = command;
398                     long rowsAffected = command.Execute(translator, connection, identifierValues, generatedValues);
399                     translator.ValidateRowsAffected(rowsAffected, source);
400                 }
401             }
402             catch (Exception e)
403             {
404                 // we should not be wrapping all exceptions
405                 if (UpdateTranslator.RequiresContext(e))
406                 {
407                     throw EntityUtil.Update(System.Data.Entity.Strings.Update_GeneralExecutionException, e, translator.DetermineStateEntriesFromSource(source));
408                 }
409                 throw;
410             }
411 
412             translator.BackPropagateServerGen(generatedValues);
413 
414             int totalStateEntries = translator.AcceptChanges(adapter);
415 
416             return totalStateEntries;
417         }
418 
ProduceCommands()419         private IEnumerable<UpdateCommand> ProduceCommands()
420         {
421             // load all modified state entries
422             PullModifiedEntriesFromStateManager();
423             PullUnchangedEntriesFromStateManager();
424 
425             // check constraints
426             m_constraintValidator.ValidateConstraints();
427             this.KeyManager.ValidateReferentialIntegrityGraphAcyclic();
428 
429             // gather all commands (aggregate in a dependency orderer to determine operation order
430             IEnumerable<UpdateCommand> dynamicCommands = this.ProduceDynamicCommands();
431             IEnumerable<UpdateCommand> functionCommands = this.ProduceFunctionCommands();
432             UpdateCommandOrderer orderer = new UpdateCommandOrderer(dynamicCommands.Concat(functionCommands), this);
433             IEnumerable<UpdateCommand> orderedCommands;
434             IEnumerable<UpdateCommand> remainder;
435             if (!orderer.TryTopologicalSort(out orderedCommands, out remainder))
436             {
437                 // throw an exception if it is not possible to perform dependency ordering
438                 throw DependencyOrderingError(remainder);
439             }
440 
441             return orderedCommands;
442         }
443 
444         // effects: given rows affected, throws if the count suggests a concurrency failure.
445         // Throws a concurrency exception based on the current command sources (which allow
446         // us to populated the EntityStateEntries on UpdateException)
ValidateRowsAffected(long rowsAffected, UpdateCommand source)447         private void ValidateRowsAffected(long rowsAffected, UpdateCommand source)
448         {
449             // 0 rows affected indicates a concurrency failure; negative values suggest rowcount is off;
450             // positive values suggest at least one row was affected (we generally expect exactly one,
451             // but triggers/view logic/logging may change this value)
452             if (0 == rowsAffected)
453             {
454                 var stateEntries = DetermineStateEntriesFromSource(source);
455                 throw EntityUtil.UpdateConcurrency(rowsAffected, null, stateEntries);
456             }
457         }
458 
DetermineStateEntriesFromSource(UpdateCommand source)459         private IEnumerable<IEntityStateEntry> DetermineStateEntriesFromSource(UpdateCommand source)
460         {
461             if (null == source)
462             {
463                 return Enumerable.Empty<IEntityStateEntry>();
464             }
465             return source.GetStateEntries(this);
466         }
467 
468         // effects: Given a list of pairs describing the contexts for server generated values and their actual
469         // values, backpropagates to the relevant state entries
BackPropagateServerGen(List<KeyValuePair<PropagatorResult, object>> generatedValues)470         private void BackPropagateServerGen(List<KeyValuePair<PropagatorResult, object>> generatedValues)
471         {
472             foreach (KeyValuePair<PropagatorResult, object> generatedValue in generatedValues)
473             {
474                 PropagatorResult context;
475 
476                 // check if a redirect to "owner" result is possible
477                 if (PropagatorResult.NullIdentifier == generatedValue.Key.Identifier ||
478                     !KeyManager.TryGetIdentifierOwner(generatedValue.Key.Identifier, out context))
479                 {
480                     // otherwise, just use the straightforward context
481                     context = generatedValue.Key;
482                 }
483 
484                 object value = generatedValue.Value;
485                 if (context.Identifier == PropagatorResult.NullIdentifier)
486                 {
487                     SetServerGenValue(context, value);
488                 }
489                 else
490                 {
491                     // check if we need to back propagate this value to any other positions (e.g. for foreign keys)
492                     foreach (int dependent in this.KeyManager.GetDependents(context.Identifier))
493                     {
494                         if (this.KeyManager.TryGetIdentifierOwner(dependent, out context))
495                         {
496                             SetServerGenValue(context, value);
497                         }
498                     }
499                 }
500             }
501         }
502 
SetServerGenValue(PropagatorResult context, object value)503         private void SetServerGenValue(PropagatorResult context, object value)
504         {
505             if (context.RecordOrdinal != PropagatorResult.NullOrdinal)
506             {
507                 CurrentValueRecord targetRecord = context.Record;
508 
509                 // determine if type compensation is required
510                 IExtendedDataRecord recordWithMetadata = (IExtendedDataRecord)targetRecord;
511                 EdmMember member = recordWithMetadata.DataRecordInfo.FieldMetadata[context.RecordOrdinal].FieldType;
512 
513                 value = value ?? DBNull.Value; // records expect DBNull rather than null
514                 value = AlignReturnValue(value, member, context);
515                 targetRecord.SetValue(context.RecordOrdinal, value);
516             }
517         }
518 
519         /// <summary>
520         /// Aligns a value returned from the store with the expected type for the member.
521         /// </summary>
522         /// <param name="value">Value to convert.</param>
523         /// <param name="member">Metadata for the member being set.</param>
524         /// <param name="context">The context generating the return value.</param>
525         /// <returns>Converted return value</returns>
AlignReturnValue(object value, EdmMember member, PropagatorResult context)526         private object AlignReturnValue(object value, EdmMember member, PropagatorResult context)
527         {
528             if (DBNull.Value.Equals(value))
529             {
530                 // check if there is a nullability constraint on the value
531                 if (BuiltInTypeKind.EdmProperty == member.BuiltInTypeKind &&
532                     !((EdmProperty)member).Nullable)
533                 {
534                     throw EntityUtil.Update(System.Data.Entity.Strings.Update_NullReturnValueForNonNullableMember(
535                         member.Name,
536                         member.DeclaringType.FullName), null);
537                 }
538             }
539             else if (!Helper.IsSpatialType(member.TypeUsage))
540             {
541                 Type clrType;
542                 Type clrEnumType = null;
543                 if (Helper.IsEnumType(member.TypeUsage.EdmType))
544                 {
545                     PrimitiveType underlyingType = Helper.AsPrimitive(member.TypeUsage.EdmType);
546                     clrEnumType = context.Record.GetFieldType(context.RecordOrdinal);
547                     clrType = underlyingType.ClrEquivalentType;
548                     Debug.Assert(clrEnumType.IsEnum);
549                 }
550                 else
551                 {
552                     // convert the value to the appropriate CLR type
553                     Debug.Assert(BuiltInTypeKind.PrimitiveType == member.TypeUsage.EdmType.BuiltInTypeKind,
554                         "we only allow return values that are instances of EDM primitive or enum types");
555                     PrimitiveType primitiveType = (PrimitiveType)member.TypeUsage.EdmType;
556                     clrType = primitiveType.ClrEquivalentType;
557                 }
558 
559                 try
560                 {
561                     value = Convert.ChangeType(value, clrType, CultureInfo.InvariantCulture);
562                     if (clrEnumType != null)
563                     {
564                         value = Enum.ToObject(clrEnumType, value);
565                     }
566                 }
567                 catch (Exception e)
568                 {
569                     // we should not be wrapping all exceptions
570                     if (UpdateTranslator.RequiresContext(e))
571                     {
572                         Type userClrType = clrEnumType ?? clrType;
573                         throw EntityUtil.Update(System.Data.Entity.Strings.Update_ReturnValueHasUnexpectedType(
574                             value.GetType().FullName,
575                             userClrType.FullName,
576                             member.Name,
577                             member.DeclaringType.FullName), e);
578                     }
579                     throw;
580                 }
581             }
582 
583             // return the adjusted value
584             return value;
585         }
586 
587         /// <summary>
588         /// Accept changes to entities and relationships processed by this translator instance.
589         /// </summary>
590         /// <param name="adapter">Data adapter</param>
591         /// <returns>Number of state entries affected.</returns>
AcceptChanges(IEntityAdapter adapter)592         private int AcceptChanges(IEntityAdapter adapter)
593         {
594             int affectedCount = 0;
595             foreach (IEntityStateEntry stateEntry in m_stateEntries)
596             {
597                 // only count and accept changes for state entries that are being explicitly modified
598                 if (EntityState.Unchanged != stateEntry.State)
599                 {
600                     if (adapter.AcceptChangesDuringUpdate)
601                     {
602                         stateEntry.AcceptChanges();
603                     }
604                     affectedCount++;
605                 }
606             }
607             return affectedCount;
608         }
609 
610         /// <summary>
611         /// Gets extents for which this translator has identified changes to be handled
612         /// by the standard update pipeline.
613         /// </summary>
614         /// <returns>Enumeration of modified C-Space extents.</returns>
GetDynamicModifiedExtents()615         private IEnumerable<EntitySetBase> GetDynamicModifiedExtents()
616         {
617             return m_changes.Keys;
618         }
619 
620         /// <summary>
621         /// Gets extents for which this translator has identified changes to be handled
622         /// by function mappings.
623         /// </summary>
624         /// <returns>Enumreation of modified C-Space extents.</returns>
GetFunctionModifiedExtents()625         private IEnumerable<EntitySetBase> GetFunctionModifiedExtents()
626         {
627             return m_functionChanges.Keys;
628         }
629 
630         /// <summary>
631         /// Produce dynamic store commands for this translator's changes.
632         /// </summary>
633         /// <returns>Database commands in a safe order</returns>
ProduceDynamicCommands()634         private IEnumerable<UpdateCommand> ProduceDynamicCommands()
635         {
636             // Initialize DBCommand update compiler
637             UpdateCompiler updateCompiler = new UpdateCompiler(this);
638 
639             // Determine affected
640             Set<EntitySet> tables = new Set<EntitySet>();
641 
642             foreach (EntitySetBase extent in GetDynamicModifiedExtents())
643             {
644                 Set<EntitySet> affectedTables = m_viewLoader.GetAffectedTables(extent, m_metadataWorkspace);
645                 //Since these extents don't have Functions defined for update operations,
646                 //the affected tables should be provided via MSL.
647                 //If we dont find any throw an exception
648                 if (affectedTables.Count == 0)
649                 {
650                     throw EntityUtil.Update(System.Data.Entity.Strings.Update_MappingNotFound(
651                         extent.Name), null /*stateEntries*/);
652                 }
653 
654                 foreach (EntitySet table in affectedTables)
655                 {
656                     tables.Add(table);
657                 }
658             }
659 
660             // Determine changes to apply to each table
661             foreach (EntitySet table in tables)
662             {
663                 DbQueryCommandTree umView = m_connection.GetMetadataWorkspace().GetCqtView(table);
664 
665                 // Propagate changes to root of tree (at which point they are S-Space changes)
666                 ChangeNode changeNode = Propagator.Propagate(this, table, umView);
667 
668                 // Process changes for the table
669                 TableChangeProcessor change = new TableChangeProcessor(table);
670                 foreach (UpdateCommand command in change.CompileCommands(changeNode, updateCompiler))
671                 {
672                     yield return command;
673                 }
674             }
675         }
676 
677         // Generates and caches a command definition for the given function
GenerateCommandDefinition(StorageModificationFunctionMapping functionMapping)678         internal DbCommandDefinition GenerateCommandDefinition(StorageModificationFunctionMapping functionMapping)
679         {
680             if (null == m_modificationFunctionCommandDefinitions)
681             {
682                 m_modificationFunctionCommandDefinitions = new Dictionary<StorageModificationFunctionMapping,DbCommandDefinition>();
683             }
684             DbCommandDefinition commandDefinition;
685             if (!m_modificationFunctionCommandDefinitions.TryGetValue(functionMapping, out commandDefinition))
686             {
687                 // synthesize a RowType for this mapping
688                 TypeUsage resultType = null;
689                 if (null != functionMapping.ResultBindings && 0 < functionMapping.ResultBindings.Count)
690                 {
691                     List<EdmProperty> properties = new List<EdmProperty>(functionMapping.ResultBindings.Count);
692                     foreach (StorageModificationFunctionResultBinding resultBinding in functionMapping.ResultBindings)
693                     {
694                         properties.Add(new EdmProperty(resultBinding.ColumnName, resultBinding.Property.TypeUsage));
695                     }
696                     RowType rowType = new RowType(properties);
697                     CollectionType collectionType = new CollectionType(rowType);
698                     resultType = TypeUsage.Create(collectionType);
699                 }
700 
701                 // add function parameters
702                 IEnumerable<KeyValuePair<string, TypeUsage>> functionParams =
703                     functionMapping.Function.Parameters.Select(paramInfo => new KeyValuePair<string, TypeUsage>(paramInfo.Name, paramInfo.TypeUsage));
704 
705                 // construct DbFunctionCommandTree including implict return type
706                 DbFunctionCommandTree tree = new DbFunctionCommandTree(m_metadataWorkspace, DataSpace.SSpace,
707                     functionMapping.Function, resultType, functionParams);
708 
709                 commandDefinition = m_providerServices.CreateCommandDefinition(tree);
710             }
711             return commandDefinition;
712         }
713 
714         // Produces all function commands in a safe order
ProduceFunctionCommands()715         private IEnumerable<UpdateCommand> ProduceFunctionCommands()
716         {
717             foreach (EntitySetBase extent in GetFunctionModifiedExtents())
718             {
719                 // Get a handle on the appropriate translator
720                 ModificationFunctionMappingTranslator translator = m_viewLoader.GetFunctionMappingTranslator(extent, m_metadataWorkspace);
721 
722                 if (null != translator)
723                 {
724                     // Compile commands
725                     foreach (ExtractedStateEntry stateEntry in GetExtentFunctionModifications(extent))
726                     {
727                         FunctionUpdateCommand command = translator.Translate(this, stateEntry);
728                         if (null != command)
729                         {
730                             yield return command;
731                         }
732                     }
733                 }
734             }
735         }
736 
737         /// <summary>
738         /// Gets a metadata wrapper for the given type. The wrapper makes
739         /// certain tasks in the update pipeline more efficient.
740         /// </summary>
741         /// <param name="type">Structural type</param>
742         /// <returns>Metadata wrapper</returns>
GetExtractorMetadata(EntitySetBase entitySetBase, StructuralType type)743         internal ExtractorMetadata GetExtractorMetadata(EntitySetBase entitySetBase, StructuralType type)
744         {
745             ExtractorMetadata metadata;
746             var key = Tuple.Create(entitySetBase, type);
747             if (!m_extractorMetadata.TryGetValue(key, out metadata))
748             {
749                 metadata = new ExtractorMetadata(entitySetBase, type, this);
750                 m_extractorMetadata.Add(key, metadata);
751             }
752             return metadata;
753         }
754 
755         /// <summary>
756         /// Returns error when it is not possible to order update commands. Argument is the 'remainder', or commands
757         /// that could not be ordered due to a cycle.
758         /// </summary>
DependencyOrderingError(IEnumerable<UpdateCommand> remainder)759         private UpdateException DependencyOrderingError(IEnumerable<UpdateCommand> remainder)
760         {
761             Debug.Assert(null != remainder && remainder.Count() > 0, "must provide non-empty remainder");
762 
763             HashSet<IEntityStateEntry> stateEntries = new HashSet<IEntityStateEntry>();
764 
765             foreach (UpdateCommand command in remainder)
766             {
767                 stateEntries.UnionWith(command.GetStateEntries(this));
768             }
769 
770             // throw exception containing all related state entries
771             throw EntityUtil.Update(System.Data.Entity.Strings.Update_ConstraintCycle, null, stateEntries);
772         }
773 
774         /// <summary>
775         /// Creates a command in the current context.
776         /// </summary>
777         /// <param name="commandTree">DbCommand tree</param>
778         /// <returns>DbCommand produced by the current provider.</returns>
CreateCommand(DbModificationCommandTree commandTree)779         internal DbCommand CreateCommand(DbModificationCommandTree commandTree)
780         {
781             DbCommand command;
782             Debug.Assert(null != m_providerServices, "constructor ensures either the command definition " +
783                     "builder or provider service is available");
784             Debug.Assert(null != m_connection.StoreConnection, "EntityAdapter.Update ensures the store connection is set");
785             try
786             {
787                 command = m_providerServices.CreateCommand(commandTree);
788             }
789             catch (Exception e)
790             {
791                 // we should not be wrapping all exceptions
792                 if (UpdateTranslator.RequiresContext(e))
793                 {
794                     // we don't wan't folks to have to know all the various types of exceptions that can
795                     // occur, so we just rethrow a CommandDefinitionException and make whatever we caught
796                     // the inner exception of it.
797                     throw EntityUtil.CommandCompilation(System.Data.Entity.Strings.EntityClient_CommandDefinitionPreparationFailed, e);
798                 }
799                 throw;
800             }
801             return command;
802         }
803 
804         /// <summary>
805         /// Helper method to allow the setting of parameter values to update stored procedures.
806         /// Allows the DbProvider an opportunity to rewrite the parameter to suit provider specific needs.
807         /// </summary>
808         /// <param name="parameter">Parameter to set.</param>
809         /// <param name="typeUsage">The type of the parameter.</param>
810         /// <param name="value">The value to which to set the parameter.</param>
SetParameterValue(DbParameter parameter, TypeUsage typeUsage, object value)811         internal void SetParameterValue(DbParameter parameter, TypeUsage typeUsage, object value)
812         {
813             m_providerServices.SetParameterValue(parameter, typeUsage, value);
814         }
815 
816         /// <summary>
817         /// Determines whether the given exception requires additional context from the update pipeline (in other
818         /// words, whether the exception should be wrapped in an UpdateException).
819         /// </summary>
820         /// <param name="e">Exception to test.</param>
821         /// <returns>true if exception should be wrapped; false otherwise</returns>
RequiresContext(Exception e)822         internal static bool RequiresContext(Exception e)
823         {
824             // if the exception isn't catchable, never wrap
825             if (!EntityUtil.IsCatchableExceptionType(e)) { return false; }
826 
827             // update and incompatible provider exceptions already contain the necessary context
828             return !(e is UpdateException) && !(e is ProviderIncompatibleException);
829         }
830 
831         #region Private initialization methods
832         /// <summary>
833         /// Retrieve all modified entries from the state manager.
834         /// </summary>
PullModifiedEntriesFromStateManager()835         private void PullModifiedEntriesFromStateManager()
836         {
837             // do a first pass over added entries to register 'by value' entity key targets that may be resolved as
838             // via a foreign key
839             foreach (IEntityStateEntry addedEntry in m_stateManager.GetEntityStateEntries(EntityState.Added))
840             {
841                 if (!addedEntry.IsRelationship && !addedEntry.IsKeyEntry)
842                 {
843                     this.KeyManager.RegisterKeyValueForAddedEntity(addedEntry);
844                 }
845             }
846 
847             // do a second pass over entries to register referential integrity constraints
848             // for server-generation
849             foreach (IEntityStateEntry modifiedEntry in m_stateManager.GetEntityStateEntries(EntityState.Modified | EntityState.Added | EntityState.Deleted))
850             {
851                 RegisterReferentialConstraints(modifiedEntry);
852             }
853 
854             foreach (IEntityStateEntry modifiedEntry in m_stateManager.GetEntityStateEntries(EntityState.Modified | EntityState.Added | EntityState.Deleted))
855             {
856                 LoadStateEntry(modifiedEntry);
857             }
858         }
859 
860 
861         /// <summary>
862         /// Retrieve all required/optional/value entries into the state manager. These are entries that --
863         /// although unmodified -- affect or are affected by updates.
864         /// </summary>
PullUnchangedEntriesFromStateManager()865         private void PullUnchangedEntriesFromStateManager()
866         {
867             foreach (KeyValuePair<EntityKey, AssociationSet> required in m_requiredEntities)
868             {
869                 EntityKey key = required.Key;
870 
871                 if (!m_knownEntityKeys.Contains(key))
872                 {
873                     // pull the value into the translator if we don't already it
874                     IEntityStateEntry requiredEntry;
875 
876                     if (m_stateManager.TryGetEntityStateEntry(key, out requiredEntry) && !requiredEntry.IsKeyEntry)
877                     {
878                         // load the object as a no-op update
879                         LoadStateEntry(requiredEntry);
880                     }
881                     else
882                     {
883                         // throw an exception
884                         throw EntityUtil.UpdateMissingEntity(required.Value.Name, TypeHelpers.GetFullName(key.EntityContainerName, key.EntitySetName));
885                     }
886                 }
887             }
888 
889             foreach (EntityKey key in m_optionalEntities)
890             {
891                 if (!m_knownEntityKeys.Contains(key))
892                 {
893                     IEntityStateEntry optionalEntry;
894 
895                     if (m_stateManager.TryGetEntityStateEntry(key, out optionalEntry) && !optionalEntry.IsKeyEntry)
896                     {
897                         // load the object as a no-op update
898                         LoadStateEntry(optionalEntry);
899                     }
900                 }
901             }
902 
903             foreach (EntityKey key in m_includedValueEntities)
904             {
905                 if (!m_knownEntityKeys.Contains(key))
906                 {
907                     IEntityStateEntry valueEntry;
908 
909                     if (m_stateManager.TryGetEntityStateEntry(key, out valueEntry))
910                     {
911                         // Convert state entry so that its values are known to the update pipeline.
912                         var result = m_recordConverter.ConvertCurrentValuesToPropagatorResult(valueEntry, ModifiedPropertiesBehavior.NoneModified);
913                     }
914                 }
915             }
916         }
917 
918         /// <summary>
919         /// Validates and tracks a state entry being processed by this translator.
920         /// </summary>
921         /// <param name="stateEntry"></param>
ValidateAndRegisterStateEntry(IEntityStateEntry stateEntry)922         private void ValidateAndRegisterStateEntry(IEntityStateEntry stateEntry)
923         {
924             EntityUtil.CheckArgumentNull(stateEntry, "stateEntry");
925 
926             EntitySetBase extent = stateEntry.EntitySet;
927             if (null == extent)
928             {
929                 throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.InvalidStateEntry, 1);
930             }
931 
932             // Determine the key. May be null if the state entry does not represent an entity.
933             EntityKey entityKey = stateEntry.EntityKey;
934             IExtendedDataRecord record = null;
935 
936             // verify the structure of the entry values
937             if (0 != ((EntityState.Added | EntityState.Modified | EntityState.Unchanged) & stateEntry.State))
938             {
939                 // added, modified and unchanged entries have current values
940                 record = (IExtendedDataRecord)stateEntry.CurrentValues;
941                 ValidateRecord(extent, record, stateEntry);
942             }
943             if (0 != ((EntityState.Modified | EntityState.Deleted | EntityState.Unchanged) & stateEntry.State))
944             {
945                 // deleted, modified and unchanged entries have original values
946                 record = (IExtendedDataRecord)stateEntry.OriginalValues;
947                 ValidateRecord(extent, record, stateEntry);
948             }
949             Debug.Assert(null != record, "every state entry must contain a record");
950 
951             // check for required ends of relationships
952             AssociationSet associationSet = extent as AssociationSet;
953             if (null != associationSet)
954             {
955                 AssociationSetMetadata associationSetMetadata = m_viewLoader.GetAssociationSetMetadata(associationSet, m_metadataWorkspace);
956 
957                 if (associationSetMetadata.HasEnds)
958                 {
959                     foreach (FieldMetadata field in record.DataRecordInfo.FieldMetadata)
960                     {
961                         // ends of relationship record must be EntityKeys
962                         EntityKey end = (EntityKey)record.GetValue(field.Ordinal);
963 
964                         // ends of relationships must have AssociationEndMember metadata
965                         AssociationEndMember endMetadata = (AssociationEndMember)field.FieldType;
966 
967                         if (associationSetMetadata.RequiredEnds.Contains(endMetadata))
968                         {
969                             if (!m_requiredEntities.ContainsKey(end))
970                             {
971                                 m_requiredEntities.Add(end, associationSet);
972                             }
973                         }
974 
975                         else if (associationSetMetadata.OptionalEnds.Contains(endMetadata))
976                         {
977                             AddValidAncillaryKey(end, m_optionalEntities);
978                         }
979 
980                         else if (associationSetMetadata.IncludedValueEnds.Contains(endMetadata))
981                         {
982                             AddValidAncillaryKey(end, m_includedValueEntities);
983                         }
984                     }
985                 }
986 
987                 // register relationship with validator
988                 m_constraintValidator.RegisterAssociation(associationSet, record, stateEntry);
989             }
990             else
991             {
992                 // register entity with validator
993                 m_constraintValidator.RegisterEntity(stateEntry);
994             }
995 
996             // add to the list of entries being tracked
997             m_stateEntries.Add(stateEntry);
998             if (null != (object)entityKey) { m_knownEntityKeys.Add(entityKey); }
999         }
1000 
1001         /// <summary>
1002         /// effects: given an entity key and a set, adds key to the set iff. the corresponding entity
1003         /// is:
1004         ///
1005         ///     not a stub (or 'key') entry, and;
1006         ///     not a core element in the update pipeline (it's not being directly modified)
1007         /// </summary>
AddValidAncillaryKey(EntityKey key, Set<EntityKey> keySet)1008         private void AddValidAncillaryKey(EntityKey key, Set<EntityKey> keySet)
1009         {
1010             // Note: an entity is ancillary iff. it is unchanged (otherwise it is tracked as a "standard" changed entity)
1011             IEntityStateEntry endEntry;
1012             if (m_stateManager.TryGetEntityStateEntry(key, out endEntry) && // make sure the entity is tracked
1013                 !endEntry.IsKeyEntry && // make sure the entity is not a stub
1014                 endEntry.State == EntityState.Unchanged) // if the entity is being modified, it's already included anyways
1015             {
1016                 keySet.Add(key);
1017             }
1018         }
1019 
ValidateRecord(EntitySetBase extent, IExtendedDataRecord record, IEntityStateEntry entry)1020         private void ValidateRecord(EntitySetBase extent, IExtendedDataRecord record, IEntityStateEntry entry)
1021         {
1022             Debug.Assert(null != extent, "must be verified by caller");
1023 
1024             DataRecordInfo recordInfo;
1025             if ((null == record) ||
1026                 (null == (recordInfo = record.DataRecordInfo)) ||
1027                 (null == recordInfo.RecordType))
1028             {
1029                 throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.InvalidStateEntry, 2);
1030             }
1031 
1032             VerifyExtent(MetadataWorkspace, extent);
1033 
1034             // additional validation happens lazily as values are loaded from the record
1035         }
1036 
1037         // Verifies the given extent is present in the given workspace.
VerifyExtent(MetadataWorkspace workspace, EntitySetBase extent)1038         private static void VerifyExtent(MetadataWorkspace workspace, EntitySetBase extent)
1039         {
1040             // get the container to which the given extent belongs
1041             EntityContainer actualContainer = extent.EntityContainer;
1042 
1043             // try to retrieve the container in the given workspace
1044             EntityContainer referenceContainer = null;
1045             if (null != actualContainer)
1046             {
1047                 workspace.TryGetEntityContainer(
1048                     actualContainer.Name, actualContainer.DataSpace, out referenceContainer);
1049             }
1050 
1051             // determine if the given extent lives in a container from the given workspace
1052             // (the item collections for each container are reference equivalent when they are declared in the
1053             // same item collection)
1054             if (null == actualContainer || null == referenceContainer ||
1055                 !Object.ReferenceEquals(actualContainer, referenceContainer))
1056             {
1057                 //
1058 
1059 
1060 
1061                 throw EntityUtil.Update(System.Data.Entity.Strings.Update_WorkspaceMismatch, null);
1062             }
1063         }
1064 
LoadStateEntry(IEntityStateEntry stateEntry)1065         private void LoadStateEntry(IEntityStateEntry stateEntry)
1066         {
1067             Debug.Assert(null != stateEntry, "state entry must exist");
1068 
1069             // make sure the state entry doesn't contain invalid data and register it with the
1070             // update pipeline
1071             ValidateAndRegisterStateEntry(stateEntry);
1072 
1073             // use data structure internal to the update pipeline instead of the raw state entry
1074             ExtractedStateEntry extractedStateEntry = new ExtractedStateEntry(this, stateEntry);
1075 
1076             // figure out if this state entry is being handled by a function (stored procedure) or
1077             // through dynamic SQL
1078             EntitySetBase extent = stateEntry.EntitySet;
1079             if (null == m_viewLoader.GetFunctionMappingTranslator(extent, m_metadataWorkspace))
1080             {
1081                 // if there is no function mapping, register a ChangeNode (used for update
1082                 // propagation and dynamic SQL generation)
1083                 ChangeNode changeNode = GetExtentModifications(extent);
1084                 if (null != extractedStateEntry.Original)
1085                 {
1086                     changeNode.Deleted.Add(extractedStateEntry.Original);
1087                 }
1088                 if (null != extractedStateEntry.Current)
1089                 {
1090                     changeNode.Inserted.Add(extractedStateEntry.Current);
1091                 }
1092             }
1093             else
1094             {
1095                 // for function updates, store off the extracted state entry in its entirety
1096                 // (used when producing FunctionUpdateCommands)
1097                 List<ExtractedStateEntry> functionEntries = GetExtentFunctionModifications(extent);
1098                 functionEntries.Add(extractedStateEntry);
1099             }
1100         }
1101 
1102 
1103 
1104         /// <summary>
1105         /// Retrieve a change node for an extent. If none exists, creates and registers a new one.
1106         /// </summary>
1107         /// <param name="extent">Extent for which to return a change node.</param>
1108         /// <returns>Change node for requested extent.</returns>
GetExtentModifications(EntitySetBase extent)1109         internal ChangeNode GetExtentModifications(EntitySetBase extent)
1110         {
1111             EntityUtil.CheckArgumentNull(extent, "extent");
1112             Debug.Assert(null != m_changes, "(UpdateTranslator/GetChangeNodeForExtent) method called before translator initialized");
1113 
1114             ChangeNode changeNode;
1115 
1116             if (!m_changes.TryGetValue(extent, out changeNode))
1117             {
1118                 changeNode = new ChangeNode(TypeUsage.Create(extent.ElementType));
1119                 m_changes.Add(extent, changeNode);
1120             }
1121 
1122             return changeNode;
1123         }
1124 
1125         /// <summary>
1126         /// Retrieve a list of state entries being processed by custom user functions.
1127         /// </summary>
1128         /// <param name="extent">Extent for which to return entries.</param>
1129         /// <returns>List storing the entries.</returns>
GetExtentFunctionModifications(EntitySetBase extent)1130         internal List<ExtractedStateEntry> GetExtentFunctionModifications(EntitySetBase extent)
1131         {
1132             EntityUtil.CheckArgumentNull(extent, "extent");
1133             Debug.Assert(null != m_functionChanges, "method called before translator initialized");
1134 
1135             List<ExtractedStateEntry> entries;
1136 
1137             if (!m_functionChanges.TryGetValue(extent, out entries))
1138             {
1139                 entries = new List<ExtractedStateEntry>();
1140                 m_functionChanges.Add(extent, entries);
1141             }
1142 
1143             return entries;
1144         }
1145         #endregion
1146         #endregion
1147     }
1148 
1149     /// <summary>
1150     /// Enumeration of possible operators.
1151     /// </summary>
1152     /// <remarks>
1153     /// The values are used to determine the order of operations (in the absence of any strong dependencies).
1154     /// The chosen order is based on the observation that hidden dependencies (e.g. due to temporary keys in
1155     /// the state manager or unknown FKs) favor deletes before inserts and updates before deletes. For instance,
1156     /// a deleted entity may have the same real key value as an inserted entity. Similarly, a self-reference
1157     /// may require a new dependent row to be updated before the prinpical row is inserted. Obviously, the actual
1158     /// constraints are required to make reliable decisions so this ordering is merely a heuristic.
1159     /// </remarks>
1160     internal enum ModificationOperator : byte
1161     {
1162         Update = 0,
1163         Delete = 1,
1164         Insert = 2,
1165     }
1166 }
1167