1 //---------------------------------------------------------------------
2 // <copyright file="FunctionUpdateCommand.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 namespace System
.Data
.Mapping
.Update
.Internal
12 using System
.Collections
.Generic
;
13 using System
.Data
.Common
;
14 using System
.Data
.Common
.Utils
;
15 using System
.Data
.EntityClient
;
16 using System
.Data
.Metadata
.Edm
;
17 using System
.Diagnostics
;
18 using System
.Globalization
;
20 using System
.Data
.Spatial
;
23 /// Aggregates information about a modification command delegated to a store function.
25 internal sealed class FunctionUpdateCommand
: UpdateCommand
29 /// Initialize a new function command. Initializes the command object.
31 /// <param name="functionMapping">Function mapping metadata</param>
32 /// <param name="translator">Translator</param>
33 /// <param name="stateEntries">State entries handled by this operation.</param>
34 /// <param name="stateEntry">'Root' state entry being handled by this function.</param>
35 internal FunctionUpdateCommand(StorageModificationFunctionMapping functionMapping
, UpdateTranslator translator
,
36 System
.Collections
.ObjectModel
.ReadOnlyCollection
<IEntityStateEntry
> stateEntries
,
37 ExtractedStateEntry stateEntry
)
38 : base(stateEntry
.Original
, stateEntry
.Current
)
40 EntityUtil
.CheckArgumentNull(functionMapping
, "functionMapping");
41 EntityUtil
.CheckArgumentNull(translator
, "translator");
42 EntityUtil
.CheckArgumentNull(stateEntries
, "stateEntries");
44 // populate the main state entry for error reporting
45 m_stateEntries
= stateEntries
;
48 DbCommandDefinition commandDefinition
= translator
.GenerateCommandDefinition(functionMapping
);
49 m_dbCommand
= commandDefinition
.CreateCommand();
54 private readonly System
.Collections
.ObjectModel
.ReadOnlyCollection
<IEntityStateEntry
> m_stateEntries
;
57 /// Gets the store command wrapped by this command.
59 private readonly DbCommand m_dbCommand
;
62 /// Gets pairs for column names and propagator results (so that we can associate reader results with
63 /// the source records for server generated values).
65 private List
<KeyValuePair
<string, PropagatorResult
>> m_resultColumns
;
68 /// Gets map from identifiers (key component proxies) to parameters holding the actual
69 /// key values. Supports propagation of identifier values (fixup for server-gen keys)
71 private List
<KeyValuePair
<int, DbParameter
>> m_inputIdentifiers
;
74 /// Gets map from identifiers (key component proxies) to column names producing the actual
75 /// key values. Supports propagation of identifier values (fixup for server-gen keys)
77 private Dictionary
<int, string> m_outputIdentifiers
;
80 /// Gets a reference to the rows affected output parameter for the stored procedure. May be null.
82 private DbParameter m_rowsAffectedParameter
;
86 internal override IEnumerable
<int> InputIdentifiers
90 if (null == m_inputIdentifiers
)
96 foreach (KeyValuePair
<int, DbParameter
> inputIdentifier
in m_inputIdentifiers
)
98 yield return inputIdentifier
.Key
;
104 internal override IEnumerable
<int> OutputIdentifiers
108 if (null == m_outputIdentifiers
)
110 return Enumerable
.Empty
<int>();
112 return m_outputIdentifiers
.Keys
;
116 internal override UpdateCommandKind Kind
118 get { return UpdateCommandKind.Function; }
124 /// Gets state entries contributing to this function. Supports error reporting.
126 internal override IList
<IEntityStateEntry
> GetStateEntries(UpdateTranslator translator
)
128 return m_stateEntries
;
131 // Adds and register a DbParameter to the current command.
132 internal void SetParameterValue(PropagatorResult result
, StorageModificationFunctionParameterBinding parameterBinding
, UpdateTranslator translator
)
134 // retrieve DbParameter
135 DbParameter parameter
= this.m_dbCommand
.Parameters
[parameterBinding
.Parameter
.Name
];
136 TypeUsage parameterType
= parameterBinding
.Parameter
.TypeUsage
;
137 object parameterValue
= translator
.KeyManager
.GetPrincipalValue(result
);
138 translator
.SetParameterValue(parameter
, parameterType
, parameterValue
);
140 // if the parameter corresponds to an identifier (key component), remember this fact in case
141 // it's important for dependency ordering (e.g., output the identifier before creating it)
142 int identifier
= result
.Identifier
;
143 if (PropagatorResult
.NullIdentifier
!= identifier
)
145 const int initialSize
= 2; // expect on average less than two input identifiers per command
146 if (null == m_inputIdentifiers
)
148 m_inputIdentifiers
= new List
<KeyValuePair
<int, DbParameter
>>(initialSize
);
150 foreach (int principal
in translator
.KeyManager
.GetPrincipals(identifier
))
152 m_inputIdentifiers
.Add(new KeyValuePair
<int, DbParameter
>(principal
, parameter
));
157 // Adds and registers a DbParameter taking the number of rows affected
158 internal void RegisterRowsAffectedParameter(FunctionParameter rowsAffectedParameter
)
160 if (null != rowsAffectedParameter
)
162 Debug
.Assert(rowsAffectedParameter
.Mode
== ParameterMode
.Out
|| rowsAffectedParameter
.Mode
== ParameterMode
.InOut
,
163 "when loading mapping metadata, we check that the parameter is an out parameter");
164 m_rowsAffectedParameter
= m_dbCommand
.Parameters
[rowsAffectedParameter
.Name
];
168 // Adds a result column binding from a column name (from the result set for the function) to
169 // a propagator result (which contains the context necessary to back-propagate the result).
170 // If the result is an identifier, binds the
171 internal void AddResultColumn(UpdateTranslator translator
, String columnName
, PropagatorResult result
)
173 const int initializeSize
= 2; // expect on average less than two result columns per command
174 if (null == m_resultColumns
)
176 m_resultColumns
= new List
<KeyValuePair
<string, PropagatorResult
>>(initializeSize
);
178 m_resultColumns
.Add(new KeyValuePair
<string, PropagatorResult
>(columnName
, result
));
180 int identifier
= result
.Identifier
;
181 if (PropagatorResult
.NullIdentifier
!= identifier
)
183 if (translator
.KeyManager
.HasPrincipals(identifier
))
185 throw EntityUtil
.InvalidOperation(System
.Data
.Entity
.Strings
.Update_GeneratedDependent(columnName
));
188 // register output identifier to enable fix-up and dependency tracking
189 AddOutputIdentifier(columnName
, identifier
);
193 // Indicate that a column in the command result set (specified by 'columnName') produces the
194 // value for a key component (specified by 'identifier')
195 private void AddOutputIdentifier(String columnName
, int identifier
)
197 const int initialSize
= 2; // expect on average less than two identifier output per command
198 if (null == m_outputIdentifiers
)
200 m_outputIdentifiers
= new Dictionary
<int, string>(initialSize
);
202 m_outputIdentifiers
[identifier
] = columnName
;
205 // efects: Executes the current function command in the given transaction and connection context.
206 // All server-generated values are added to the generatedValues list. If those values are identifiers, they are
207 // also added to the identifierValues dictionary, which associates proxy identifiers for keys in the session
208 // with their actual values, permitting fix-up of identifiers across relationships.
209 internal override long Execute(UpdateTranslator translator
, EntityConnection connection
, Dictionary
<int, object> identifierValues
,
210 List
<KeyValuePair
<PropagatorResult
, object>> generatedValues
)
212 // configure command to use the connection and transaction for this session
213 m_dbCommand
.Transaction
= ((null != connection
.CurrentTransaction
) ? connection
.CurrentTransaction
.StoreTransaction
: null);
214 m_dbCommand
.Connection
= connection
.StoreConnection
;
215 if (translator
.CommandTimeout
.HasValue
)
217 m_dbCommand
.CommandTimeout
= translator
.CommandTimeout
.Value
;
220 // set all identifier inputs (to support propagation of identifier values across relationship
222 if (null != m_inputIdentifiers
)
224 foreach (KeyValuePair
<int, DbParameter
> inputIdentifier
in m_inputIdentifiers
)
227 if (identifierValues
.TryGetValue(inputIdentifier
.Key
, out value))
229 // set the actual value for the identifier if it has been produced by some
231 inputIdentifier
.Value
.Value
= value;
238 if (null != m_resultColumns
)
240 // If there are result columns, read the server gen results
242 IBaseList
<EdmMember
> members
= TypeHelpers
.GetAllStructuralMembers(this.CurrentValues
.StructuralType
);
243 using (DbDataReader reader
= m_dbCommand
.ExecuteReader(CommandBehavior
.SequentialAccess
))
245 // Retrieve only the first row from the first result set
250 foreach (var resultColumn
in m_resultColumns
251 .Select(r
=> new KeyValuePair
<int, PropagatorResult
>(GetColumnOrdinal(translator
, reader
, r
.Key
), r
.Value
))
252 .OrderBy(r
=> r
.Key
)) // order by column ordinal to avoid breaking SequentialAccess readers
254 int columnOrdinal
= resultColumn
.Key
;
255 TypeUsage columnType
= members
[resultColumn
.Value
.RecordOrdinal
].TypeUsage
;
258 if (Helper
.IsSpatialType(columnType
) && !reader
.IsDBNull(columnOrdinal
))
260 value = SpatialHelpers
.GetSpatialValue(translator
.MetadataWorkspace
, reader
, columnType
, columnOrdinal
);
264 value = reader
.GetValue(columnOrdinal
);
267 // register for back-propagation
268 PropagatorResult result
= resultColumn
.Value
;
269 generatedValues
.Add(new KeyValuePair
<PropagatorResult
, object>(result
, value));
271 // register identifier if it exists
272 int identifier
= result
.Identifier
;
273 if (PropagatorResult
.NullIdentifier
!= identifier
)
275 identifierValues
.Add(identifier
, value);
280 // Consume the current reader (and subsequent result sets) so that any errors
281 // executing the function can be intercepted
282 CommandHelper
.ConsumeReader(reader
);
287 rowsAffected
= m_dbCommand
.ExecuteNonQuery();
290 // if an explicit rows affected parameter exists, use this value instead
291 if (null != m_rowsAffectedParameter
)
293 // by design, negative row counts indicate failure iff. an explicit rows
294 // affected parameter is used
295 if (DBNull
.Value
.Equals(m_rowsAffectedParameter
.Value
))
303 rowsAffected
= Convert
.ToInt64(m_rowsAffectedParameter
.Value
, CultureInfo
.InvariantCulture
);
307 if (UpdateTranslator
.RequiresContext(e
))
309 // wrap the exception
310 throw EntityUtil
.Update(System
.Data
.Entity
.Strings
.Update_UnableToConvertRowsAffectedParameterToInt32(
311 m_rowsAffectedParameter
.ParameterName
, typeof(int).FullName
), e
, this.GetStateEntries(translator
));
321 private int GetColumnOrdinal(UpdateTranslator translator
, DbDataReader reader
, string columnName
)
326 columnOrdinal
= reader
.GetOrdinal(columnName
);
328 catch (IndexOutOfRangeException
)
330 throw EntityUtil
.Update(System
.Data
.Entity
.Strings
.Update_MissingResultColumn(columnName
), null,
331 this.GetStateEntries(translator
));
333 return columnOrdinal
;
337 /// Gets modification operator corresponding to the given entity state.
339 private static ModificationOperator
GetModificationOperator(EntityState state
)
343 case EntityState
.Modified
:
344 case EntityState
.Unchanged
:
345 // unchanged entities correspond to updates (consider the case where
346 // the entity is not being modified but a collocated relationship is)
347 return ModificationOperator
.Update
;
349 case EntityState
.Added
:
350 return ModificationOperator
.Insert
;
352 case EntityState
.Deleted
:
353 return ModificationOperator
.Delete
;
356 Debug
.Fail("unexpected entity state " + state
);
357 return default(ModificationOperator
);
361 internal override int CompareToType(UpdateCommand otherCommand
)
363 Debug
.Assert(!object.ReferenceEquals(this, otherCommand
), "caller should ensure other command is different");
365 FunctionUpdateCommand other
= (FunctionUpdateCommand
)otherCommand
;
367 // first state entry is the 'main' state entry for the command (see ctor)
368 IEntityStateEntry thisParent
= this.m_stateEntries
[0];
369 IEntityStateEntry otherParent
= other
.m_stateEntries
[0];
372 int result
= (int)GetModificationOperator(thisParent
.State
) -
373 (int)GetModificationOperator(otherParent
.State
);
374 if (0 != result
) { return result; }
376 // order by entity set
377 result
= StringComparer
.Ordinal
.Compare(thisParent
.EntitySet
.Name
, otherParent
.EntitySet
.Name
);
378 if (0 != result
) { return result; }
379 result
= StringComparer
.Ordinal
.Compare(thisParent
.EntitySet
.EntityContainer
.Name
, otherParent
.EntitySet
.EntityContainer
.Name
);
380 if (0 != result
) { return result; }
382 // order by key values
383 int thisInputIdentifierCount
= (null == this.m_inputIdentifiers
? 0 : this.m_inputIdentifiers
.Count
);
384 int otherInputIdentifierCount
= (null == other
.m_inputIdentifiers
? 0 : other
.m_inputIdentifiers
.Count
);
385 result
= thisInputIdentifierCount
- otherInputIdentifierCount
;
386 if (0 != result
) { return result; }
387 for (int i
= 0; i
< thisInputIdentifierCount
; i
++)
389 DbParameter thisParameter
= this.m_inputIdentifiers
[i
].Value
;
390 DbParameter otherParameter
= other
.m_inputIdentifiers
[i
].Value
;
391 result
= ByValueComparer
.Default
.Compare(thisParameter
.Value
, otherParameter
.Value
);
392 if (0 != result
) { return result; }
395 // If the result is still zero, it means key values are all the same. Switch to synthetic identifiers
397 for (int i
= 0; i
< thisInputIdentifierCount
; i
++)
399 int thisIdentifier
= this.m_inputIdentifiers
[i
].Key
;
400 int otherIdentifier
= other
.m_inputIdentifiers
[i
].Key
;
401 result
= thisIdentifier
- otherIdentifier
;
402 if (0 != result
) { return result; }