1 //---------------------------------------------------------------------
2 // <copyright file="UpdateCommand.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 //---------------------------------------------------------------------
10 using System.Data.Metadata.Edm;
11 using System.Data.Common;
12 using System.Collections.Generic;
14 using System.Diagnostics;
15 using System.Globalization;
16 using System.Data.Common.Utils;
17 using System.Data.Common.CommandTrees;
18 using System.Data.Objects;
20 using System.Data.EntityClient;
21 using System.Threading;
22 namespace System.Data.Mapping.Update.Internal
24 internal enum UpdateCommandKind
31 /// Class storing the result of compiling an instance DML command.
33 internal abstract class UpdateCommand : IComparable<UpdateCommand>, IEquatable<UpdateCommand>
35 protected UpdateCommand(PropagatorResult originalValues, PropagatorResult currentValues)
37 m_originalValues = originalValues;
38 m_currentValues = currentValues;
41 private readonly PropagatorResult m_originalValues;
42 private readonly PropagatorResult m_currentValues;
44 // When it is not possible to order two commands based on their contents, we assign an 'ordering identifier'
45 // so that one will consistently precede the other.
46 private static int s_orderingIdentifierCounter;
47 private int m_orderingIdentifier;
50 /// Gets all identifiers (key values basically) generated by this command. For instance,
51 /// @@IDENTITY values.
53 internal abstract IEnumerable<int> OutputIdentifiers { get; }
56 /// Gets all identifiers required by this command.
58 internal abstract IEnumerable<int> InputIdentifiers { get; }
61 /// Gets table (if any) associated with the current command. FunctionUpdateCommand has no table.
63 internal virtual EntitySet Table
72 /// Gets type of command.
74 internal abstract UpdateCommandKind Kind
80 /// Gets original values of row/entity handled by this command.
82 internal PropagatorResult OriginalValues { get { return m_originalValues; } }
85 /// Gets current values of row/entity handled by this command.
87 internal PropagatorResult CurrentValues { get { return m_currentValues; } }
90 /// Yields all state entries contributing to this command. Used for error reporting.
92 /// <param name="translator">Translator context.</param>
93 /// <returns>Related state entries.</returns>
94 internal abstract IList<IEntityStateEntry> GetStateEntries(UpdateTranslator translator);
97 /// Determines model level dependencies for the current command. Dependencies are based
98 /// on the model operations performed by the command (adding or deleting entities or relationships).
100 internal void GetRequiredAndProducedEntities(UpdateTranslator translator,
101 KeyToListMap<EntityKey, UpdateCommand> addedEntities,
102 KeyToListMap<EntityKey, UpdateCommand> deletedEntities,
103 KeyToListMap<EntityKey, UpdateCommand> addedRelationships,
104 KeyToListMap<EntityKey, UpdateCommand> deletedRelationships)
106 IList<IEntityStateEntry> stateEntries = GetStateEntries(translator);
108 foreach (IEntityStateEntry stateEntry in stateEntries)
110 if (!stateEntry.IsRelationship)
112 if (stateEntry.State == EntityState.Added)
114 addedEntities.Add(stateEntry.EntityKey, this);
116 else if (stateEntry.State == EntityState.Deleted)
118 deletedEntities.Add(stateEntry.EntityKey, this);
123 // process foreign keys
124 if (null != this.OriginalValues)
126 // if a foreign key being deleted, it 'frees' or 'produces' the referenced key
127 AddReferencedEntities(translator, this.OriginalValues, deletedRelationships);
129 if (null != this.CurrentValues)
131 // if a foreign key is being added, if requires the referenced key
132 AddReferencedEntities(translator, this.CurrentValues, addedRelationships);
135 // process relationships
136 foreach (IEntityStateEntry stateEntry in stateEntries)
138 if (stateEntry.IsRelationship)
140 // only worry about the relationship if it is being added or deleted
141 bool isAdded = stateEntry.State == EntityState.Added;
142 if (isAdded || stateEntry.State == EntityState.Deleted)
144 DbDataRecord record = isAdded ? (DbDataRecord)stateEntry.CurrentValues : stateEntry.OriginalValues;
145 Debug.Assert(2 == record.FieldCount, "non-binary relationship?");
146 EntityKey end1 = (EntityKey)record[0];
147 EntityKey end2 = (EntityKey)record[1];
149 // relationships require the entity when they're added and free the entity when they're deleted...
150 KeyToListMap<EntityKey, UpdateCommand> affected = isAdded ? addedRelationships : deletedRelationships;
152 // both ends are being modified by the relationship
153 affected.Add(end1, this);
154 affected.Add(end2, this);
160 private void AddReferencedEntities(UpdateTranslator translator, PropagatorResult result, KeyToListMap<EntityKey, UpdateCommand> referencedEntities)
162 foreach (PropagatorResult property in result.GetMemberValues())
164 if (property.IsSimple && property.Identifier != PropagatorResult.NullIdentifier &&
165 (PropagatorFlags.ForeignKey == (property.PropagatorFlags & PropagatorFlags.ForeignKey)))
167 foreach (int principal in translator.KeyManager.GetDirectReferences(property.Identifier))
169 PropagatorResult owner;
170 if (translator.KeyManager.TryGetIdentifierOwner(principal, out owner) &&
171 null != owner.StateEntry)
173 Debug.Assert(!owner.StateEntry.IsRelationship, "owner must not be a relationship");
174 referencedEntities.Add(owner.StateEntry.EntityKey, this);
182 /// Executes the current update command.
184 /// <param name="translator">Translator context.</param>
185 /// <param name="connection">EntityConnection to use (and implicitly, the EntityTransaction to use).</param>
186 /// <param name="identifierValues">Aggregator for identifier values (read for InputIdentifiers; write for
187 /// OutputIdentifiers</param>
188 /// <param name="generatedValues">Aggregator for server generated values.</param>
189 /// <returns>Number of rows affected by the command.</returns>
190 internal abstract long Execute(UpdateTranslator translator, EntityConnection connection, Dictionary<int, object> identifierValues,
191 List<KeyValuePair<PropagatorResult, object>> generatedValues);
194 /// Implementation of CompareTo for concrete subclass of UpdateCommand.
196 internal abstract int CompareToType(UpdateCommand other);
199 /// Provides a suggested ordering between two commands. Ensuring a consistent ordering is important to avoid deadlocks
200 /// between two clients because it means locks are acquired in the same order where possible. The ordering criteria are as
201 /// follows (and are partly implemented in the CompareToType method). In some cases there are specific secondary
202 /// reasons for the order (e.g. operator kind), but for the most case we just care that a consistent ordering
205 /// - The kind of command (dynamic or function). This is an arbitrary criteria.
206 /// - The kind of operator (insert, update, delete). See <see cref="ModificationOperator"/> for details of the ordering.
207 /// - The target of the modification (table for dynamic, set for function).
208 /// - Primary key for the modification (table key for dynamic, entity keys for function).
210 /// If it is not possible to differentiate between two commands (e.g., where the user is inserting entities with server-generated
211 /// primary keys and has not given explicit values), arbitrary ordering identifiers are assigned to the commands to
212 /// ensure CompareTo is well-behaved (doesn't return 0 for different commands and suggests consistent ordering).
214 public int CompareTo(UpdateCommand other)
216 // If the commands are the same (by reference), return 0 immediately. Otherwise, we try to find (and eventually
217 // force) an ordering between them by returning a value that is non-zero.
218 if (this.Equals(other)) { return 0; }
219 Debug.Assert(null != other, "comparing to null UpdateCommand");
220 int result = (int)this.Kind - (int)other.Kind;
221 if (0 != result) { return result; }
223 // defer to specific type for other comparisons...
224 result = CompareToType(other);
225 if (0 != result) { return result; }
227 // if the commands are indistinguishable, assign arbitrary identifiers to them to ensure consistent ordering
230 if (this.m_orderingIdentifier == 0)
232 this.m_orderingIdentifier = Interlocked.Increment(ref s_orderingIdentifierCounter);
234 if (other.m_orderingIdentifier == 0)
236 other.m_orderingIdentifier = Interlocked.Increment(ref s_orderingIdentifierCounter);
239 return this.m_orderingIdentifier - other.m_orderingIdentifier;
243 #region IEquatable: note that we use reference equality
244 public bool Equals(UpdateCommand other)
246 return base.Equals(other);
249 public override bool Equals(object obj)
251 return base.Equals(obj);
254 public override int GetHashCode()
256 return base.GetHashCode();