1 //---------------------------------------------------------------------
2 // <copyright file="DynamicUpdateCommand.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
11 using System.Collections.Generic;
12 using System.Data.Common.CommandTrees;
13 using System.Data.Metadata.Edm;
14 using System.Data.Common;
15 using System.Data.EntityClient;
16 using System.Diagnostics;
17 using System.Data.Common.Utils;
19 using System.Data.Common.CommandTrees.ExpressionBuilder;
20 using System.Data.Spatial;
22 namespace System.Data.Mapping.Update.Internal
24 internal sealed class DynamicUpdateCommand : UpdateCommand
26 private readonly ModificationOperator m_operator;
27 private readonly TableChangeProcessor m_processor;
28 private readonly List<KeyValuePair<int, DbSetClause>> m_inputIdentifiers;
29 private readonly Dictionary<int, string> m_outputIdentifiers;
30 private readonly DbModificationCommandTree m_modificationCommandTree;
33 internal DynamicUpdateCommand(TableChangeProcessor processor, UpdateTranslator translator, ModificationOperator op,
34 PropagatorResult originalValues, PropagatorResult currentValues, DbModificationCommandTree tree,
35 Dictionary<int, string> outputIdentifiers)
36 : base(originalValues, currentValues)
38 m_processor = EntityUtil.CheckArgumentNull(processor, "processor");
40 m_modificationCommandTree = EntityUtil.CheckArgumentNull(tree, "commandTree");
41 m_outputIdentifiers = outputIdentifiers; // may be null (not all commands have output identifiers)
43 // initialize identifier information (supports lateral propagation of server gen values)
44 if (ModificationOperator.Insert == op || ModificationOperator.Update == op)
46 const int capacity = 2; // "average" number of identifiers per row
47 m_inputIdentifiers = new List<KeyValuePair<int ,DbSetClause>>(capacity);
49 foreach (KeyValuePair<EdmMember, PropagatorResult> member in
50 Helper.PairEnumerations(TypeHelpers.GetAllStructuralMembers(this.CurrentValues.StructuralType),
51 this.CurrentValues.GetMemberValues()))
54 int identifier = member.Value.Identifier;
56 if (PropagatorResult.NullIdentifier != identifier &&
57 TryGetSetterExpression(tree, member.Key, op, out setter)) // can find corresponding setter
59 foreach (int principal in translator.KeyManager.GetPrincipals(identifier))
61 m_inputIdentifiers.Add(new KeyValuePair<int, DbSetClause>(principal, setter));
68 // effects: try to find setter expression for the given member
69 // requires: command tree must be an insert or update tree (since other DML trees hnabve
70 private static bool TryGetSetterExpression(DbModificationCommandTree tree, EdmMember member, ModificationOperator op, out DbSetClause setter)
72 Debug.Assert(op == ModificationOperator.Insert || op == ModificationOperator.Update, "only inserts and updates have setters");
73 IEnumerable<DbModificationClause> clauses;
74 if (ModificationOperator.Insert == op)
76 clauses = ((DbInsertCommandTree)tree).SetClauses;
80 clauses = ((DbUpdateCommandTree)tree).SetClauses;
82 foreach (DbSetClause setClause in clauses)
84 // check if this is the correct setter
85 if (((DbPropertyExpression)setClause.Property).Property.EdmEquals(member))
97 internal override long Execute(UpdateTranslator translator, EntityConnection connection, Dictionary<int, object> identifierValues, List<KeyValuePair<PropagatorResult, object>> generatedValues)
100 using (DbCommand command = this.CreateCommand(translator, identifierValues))
102 // configure command to use the connection and transaction for this session
103 command.Transaction = ((null != connection.CurrentTransaction) ? connection.CurrentTransaction.StoreTransaction : null);
104 command.Connection = connection.StoreConnection;
105 if (translator.CommandTimeout.HasValue)
107 command.CommandTimeout = translator.CommandTimeout.Value;
112 if (m_modificationCommandTree.HasReader)
114 // retrieve server gen results
116 using (DbDataReader reader = command.ExecuteReader(CommandBehavior.SequentialAccess))
122 IBaseList<EdmMember> members = TypeHelpers.GetAllStructuralMembers(this.CurrentValues.StructuralType);
124 for (int ordinal = 0; ordinal < reader.FieldCount; ordinal++)
126 // column name of result corresponds to column name of table
127 string columnName = reader.GetName(ordinal);
128 EdmMember member = members[columnName];
130 if (Helper.IsSpatialType(member.TypeUsage) && !reader.IsDBNull(ordinal))
132 value = SpatialHelpers.GetSpatialValue(translator.MetadataWorkspace, reader, member.TypeUsage, ordinal);
136 value = reader.GetValue(ordinal);
139 // retrieve result which includes the context for back-propagation
140 int columnOrdinal = members.IndexOf(member);
141 PropagatorResult result = this.CurrentValues.GetMemberValue(columnOrdinal);
143 // register for back-propagation
144 generatedValues.Add(new KeyValuePair<PropagatorResult, object>(result, value));
146 // register identifier if it exists
147 int identifier = result.Identifier;
148 if (PropagatorResult.NullIdentifier != identifier)
150 identifierValues.Add(identifier, value);
155 // Consume the current reader (and subsequent result sets) so that any errors
156 // executing the command can be intercepted
157 CommandHelper.ConsumeReader(reader);
162 rowsAffected = command.ExecuteNonQuery();
170 /// Gets DB command definition encapsulating store logic for this command.
172 private DbCommand CreateCommand(UpdateTranslator translator, Dictionary<int, object> identifierValues)
174 DbModificationCommandTree commandTree = m_modificationCommandTree;
176 // check if any server gen identifiers need to be set
177 if (null != m_inputIdentifiers)
179 Dictionary<DbSetClause, DbSetClause> modifiedClauses = new Dictionary<DbSetClause, DbSetClause>();
180 for (int idx = 0; idx < m_inputIdentifiers.Count; idx++)
182 KeyValuePair<int, DbSetClause> inputIdentifier = m_inputIdentifiers[idx];
185 if (identifierValues.TryGetValue(inputIdentifier.Key, out value))
187 // reset the value of the identifier
188 DbSetClause newClause = new DbSetClause(inputIdentifier.Value.Property, DbExpressionBuilder.Constant(value));
189 modifiedClauses[inputIdentifier.Value] = newClause;
190 m_inputIdentifiers[idx] = new KeyValuePair<int, DbSetClause>(inputIdentifier.Key, newClause);
193 commandTree = RebuildCommandTree(commandTree, modifiedClauses);
196 return translator.CreateCommand(commandTree);
199 private DbModificationCommandTree RebuildCommandTree(DbModificationCommandTree originalTree, Dictionary<DbSetClause, DbSetClause> clauseMappings)
201 if (clauseMappings.Count == 0)
206 DbModificationCommandTree result;
207 Debug.Assert(originalTree.CommandTreeKind == DbCommandTreeKind.Insert || originalTree.CommandTreeKind == DbCommandTreeKind.Update, "Set clauses specified for a modification tree that is not an update or insert tree?");
208 if (originalTree.CommandTreeKind == DbCommandTreeKind.Insert)
210 DbInsertCommandTree insertTree = (DbInsertCommandTree)originalTree;
211 result = new DbInsertCommandTree(insertTree.MetadataWorkspace, insertTree.DataSpace,
212 insertTree.Target, ReplaceClauses(insertTree.SetClauses, clauseMappings).AsReadOnly(), insertTree.Returning);
216 DbUpdateCommandTree updateTree = (DbUpdateCommandTree)originalTree;
217 result = new DbUpdateCommandTree(updateTree.MetadataWorkspace, updateTree.DataSpace,
218 updateTree.Target, updateTree.Predicate, ReplaceClauses(updateTree.SetClauses, clauseMappings).AsReadOnly(), updateTree.Returning);
225 /// Creates a new list of modification clauses with the specified remapped clauses replaced.
227 private List<DbModificationClause> ReplaceClauses(IList<DbModificationClause> originalClauses, Dictionary<DbSetClause, DbSetClause> mappings)
229 List<DbModificationClause> result = new List<DbModificationClause>(originalClauses.Count);
230 for (int idx = 0; idx < originalClauses.Count; idx++)
232 DbSetClause replacementClause;
233 if (mappings.TryGetValue((DbSetClause)originalClauses[idx], out replacementClause))
235 result.Add(replacementClause);
239 result.Add(originalClauses[idx]);
245 internal ModificationOperator Operator { get { return m_operator; } }
247 internal override EntitySet Table { get { return this.m_processor.Table; } }
249 internal override IEnumerable<int> InputIdentifiers
253 if (null == m_inputIdentifiers)
259 foreach (KeyValuePair<int, DbSetClause> inputIdentifier in m_inputIdentifiers)
261 yield return inputIdentifier.Key;
267 internal override IEnumerable<int> OutputIdentifiers
271 if (null == m_outputIdentifiers)
273 return Enumerable.Empty<int>();
275 return m_outputIdentifiers.Keys;
279 internal override UpdateCommandKind Kind
281 get { return UpdateCommandKind.Dynamic; }
284 internal override IList<IEntityStateEntry> GetStateEntries(UpdateTranslator translator)
286 List<IEntityStateEntry> stateEntries = new List<IEntityStateEntry>(2);
287 if (null != this.OriginalValues)
289 foreach (IEntityStateEntry stateEntry in SourceInterpreter.GetAllStateEntries(
290 this.OriginalValues, translator, this.Table))
292 stateEntries.Add(stateEntry);
296 if (null != this.CurrentValues)
298 foreach (IEntityStateEntry stateEntry in SourceInterpreter.GetAllStateEntries(
299 this.CurrentValues, translator, this.Table))
301 stateEntries.Add(stateEntry);
307 internal override int CompareToType(UpdateCommand otherCommand)
309 Debug.Assert(!object.ReferenceEquals(this, otherCommand), "caller is supposed to ensure otherCommand is different reference");
311 DynamicUpdateCommand other = (DynamicUpdateCommand)otherCommand;
313 // order by operation type
314 int result = (int)this.Operator - (int)other.Operator;
315 if (0 != result) { return result; }
317 // order by Container.Table
318 result = StringComparer.Ordinal.Compare(this.m_processor.Table.Name, other.m_processor.Table.Name);
319 if (0 != result) { return result; }
320 result = StringComparer.Ordinal.Compare(this.m_processor.Table.EntityContainer.Name, other.m_processor.Table.EntityContainer.Name);
321 if (0 != result) { return result; }
323 // order by table key
324 PropagatorResult thisResult = (this.Operator == ModificationOperator.Delete ? this.OriginalValues : this.CurrentValues);
325 PropagatorResult otherResult = (other.Operator == ModificationOperator.Delete ? other.OriginalValues : other.CurrentValues);
326 for (int i = 0; i < m_processor.KeyOrdinals.Length; i++)
328 int keyOrdinal = m_processor.KeyOrdinals[i];
329 object thisValue = thisResult.GetMemberValue(keyOrdinal).GetSimpleValue();
330 object otherValue = otherResult.GetMemberValue(keyOrdinal).GetSimpleValue();
331 result = ByValueComparer.Default.Compare(thisValue, otherValue);
332 if (0 != result) { return result; }
335 // If the result is still zero, it means key values are all the same. Switch to synthetic identifiers
337 for (int i = 0; i < m_processor.KeyOrdinals.Length; i++)
339 int keyOrdinal = m_processor.KeyOrdinals[i];
340 int thisValue = thisResult.GetMemberValue(keyOrdinal).Identifier;
341 int otherValue = otherResult.GetMemberValue(keyOrdinal).Identifier;
342 result = thisValue - otherValue;
343 if (0 != result) { return result; }