//---------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
namespace System.Data.Mapping.Update.Internal
{
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Common.Utils;
using System.Data.EntityClient;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Data.Spatial;
///
/// Aggregates information about a modification command delegated to a store function.
///
internal sealed class FunctionUpdateCommand : UpdateCommand
{
#region Constructors
///
/// Initialize a new function command. Initializes the command object.
///
/// Function mapping metadata
/// Translator
/// State entries handled by this operation.
/// 'Root' state entry being handled by this function.
internal FunctionUpdateCommand(StorageModificationFunctionMapping functionMapping, UpdateTranslator translator,
System.Collections.ObjectModel.ReadOnlyCollection stateEntries,
ExtractedStateEntry stateEntry)
: base(stateEntry.Original, stateEntry.Current)
{
EntityUtil.CheckArgumentNull(functionMapping, "functionMapping");
EntityUtil.CheckArgumentNull(translator, "translator");
EntityUtil.CheckArgumentNull(stateEntries, "stateEntries");
// populate the main state entry for error reporting
m_stateEntries = stateEntries;
// create a command
DbCommandDefinition commandDefinition = translator.GenerateCommandDefinition(functionMapping);
m_dbCommand = commandDefinition.CreateCommand();
}
#endregion
#region Fields
private readonly System.Collections.ObjectModel.ReadOnlyCollection m_stateEntries;
///
/// Gets the store command wrapped by this command.
///
private readonly DbCommand m_dbCommand;
///
/// Gets pairs for column names and propagator results (so that we can associate reader results with
/// the source records for server generated values).
///
private List> m_resultColumns;
///
/// Gets map from identifiers (key component proxies) to parameters holding the actual
/// key values. Supports propagation of identifier values (fixup for server-gen keys)
///
private List> m_inputIdentifiers;
///
/// Gets map from identifiers (key component proxies) to column names producing the actual
/// key values. Supports propagation of identifier values (fixup for server-gen keys)
///
private Dictionary m_outputIdentifiers;
///
/// Gets a reference to the rows affected output parameter for the stored procedure. May be null.
///
private DbParameter m_rowsAffectedParameter;
#endregion
#region Properties
internal override IEnumerable InputIdentifiers
{
get
{
if (null == m_inputIdentifiers)
{
yield break;
}
else
{
foreach (KeyValuePair inputIdentifier in m_inputIdentifiers)
{
yield return inputIdentifier.Key;
}
}
}
}
internal override IEnumerable OutputIdentifiers
{
get
{
if (null == m_outputIdentifiers)
{
return Enumerable.Empty();
}
return m_outputIdentifiers.Keys;
}
}
internal override UpdateCommandKind Kind
{
get { return UpdateCommandKind.Function; }
}
#endregion
#region Methods
///
/// Gets state entries contributing to this function. Supports error reporting.
///
internal override IList GetStateEntries(UpdateTranslator translator)
{
return m_stateEntries;
}
// Adds and register a DbParameter to the current command.
internal void SetParameterValue(PropagatorResult result, StorageModificationFunctionParameterBinding parameterBinding, UpdateTranslator translator)
{
// retrieve DbParameter
DbParameter parameter = this.m_dbCommand.Parameters[parameterBinding.Parameter.Name];
TypeUsage parameterType = parameterBinding.Parameter.TypeUsage;
object parameterValue = translator.KeyManager.GetPrincipalValue(result);
translator.SetParameterValue(parameter, parameterType, parameterValue);
// if the parameter corresponds to an identifier (key component), remember this fact in case
// it's important for dependency ordering (e.g., output the identifier before creating it)
int identifier = result.Identifier;
if (PropagatorResult.NullIdentifier != identifier)
{
const int initialSize = 2; // expect on average less than two input identifiers per command
if (null == m_inputIdentifiers)
{
m_inputIdentifiers = new List>(initialSize);
}
foreach (int principal in translator.KeyManager.GetPrincipals(identifier))
{
m_inputIdentifiers.Add(new KeyValuePair(principal, parameter));
}
}
}
// Adds and registers a DbParameter taking the number of rows affected
internal void RegisterRowsAffectedParameter(FunctionParameter rowsAffectedParameter)
{
if (null != rowsAffectedParameter)
{
Debug.Assert(rowsAffectedParameter.Mode == ParameterMode.Out || rowsAffectedParameter.Mode == ParameterMode.InOut,
"when loading mapping metadata, we check that the parameter is an out parameter");
m_rowsAffectedParameter = m_dbCommand.Parameters[rowsAffectedParameter.Name];
}
}
// Adds a result column binding from a column name (from the result set for the function) to
// a propagator result (which contains the context necessary to back-propagate the result).
// If the result is an identifier, binds the
internal void AddResultColumn(UpdateTranslator translator, String columnName, PropagatorResult result)
{
const int initializeSize = 2; // expect on average less than two result columns per command
if (null == m_resultColumns)
{
m_resultColumns = new List>(initializeSize);
}
m_resultColumns.Add(new KeyValuePair(columnName, result));
int identifier = result.Identifier;
if (PropagatorResult.NullIdentifier != identifier)
{
if (translator.KeyManager.HasPrincipals(identifier))
{
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.Update_GeneratedDependent(columnName));
}
// register output identifier to enable fix-up and dependency tracking
AddOutputIdentifier(columnName, identifier);
}
}
// Indicate that a column in the command result set (specified by 'columnName') produces the
// value for a key component (specified by 'identifier')
private void AddOutputIdentifier(String columnName, int identifier)
{
const int initialSize = 2; // expect on average less than two identifier output per command
if (null == m_outputIdentifiers)
{
m_outputIdentifiers = new Dictionary(initialSize);
}
m_outputIdentifiers[identifier] = columnName;
}
// efects: Executes the current function command in the given transaction and connection context.
// All server-generated values are added to the generatedValues list. If those values are identifiers, they are
// also added to the identifierValues dictionary, which associates proxy identifiers for keys in the session
// with their actual values, permitting fix-up of identifiers across relationships.
internal override long Execute(UpdateTranslator translator, EntityConnection connection, Dictionary identifierValues,
List> generatedValues)
{
// configure command to use the connection and transaction for this session
m_dbCommand.Transaction = ((null != connection.CurrentTransaction) ? connection.CurrentTransaction.StoreTransaction : null);
m_dbCommand.Connection = connection.StoreConnection;
if (translator.CommandTimeout.HasValue)
{
m_dbCommand.CommandTimeout = translator.CommandTimeout.Value;
}
// set all identifier inputs (to support propagation of identifier values across relationship
// boundaries)
if (null != m_inputIdentifiers)
{
foreach (KeyValuePair inputIdentifier in m_inputIdentifiers)
{
object value;
if (identifierValues.TryGetValue(inputIdentifier.Key, out value))
{
// set the actual value for the identifier if it has been produced by some
// other command
inputIdentifier.Value.Value = value;
}
}
}
// Execute the query
long rowsAffected;
if (null != m_resultColumns)
{
// If there are result columns, read the server gen results
rowsAffected = 0;
IBaseList members = TypeHelpers.GetAllStructuralMembers(this.CurrentValues.StructuralType);
using (DbDataReader reader = m_dbCommand.ExecuteReader(CommandBehavior.SequentialAccess))
{
// Retrieve only the first row from the first result set
if (reader.Read())
{
rowsAffected++;
foreach (var resultColumn in m_resultColumns
.Select(r => new KeyValuePair(GetColumnOrdinal(translator, reader, r.Key), r.Value))
.OrderBy(r => r.Key)) // order by column ordinal to avoid breaking SequentialAccess readers
{
int columnOrdinal = resultColumn.Key;
TypeUsage columnType = members[resultColumn.Value.RecordOrdinal].TypeUsage;
object value;
if (Helper.IsSpatialType(columnType) && !reader.IsDBNull(columnOrdinal))
{
value = SpatialHelpers.GetSpatialValue(translator.MetadataWorkspace, reader, columnType, columnOrdinal);
}
else
{
value = reader.GetValue(columnOrdinal);
}
// register for back-propagation
PropagatorResult result = resultColumn.Value;
generatedValues.Add(new KeyValuePair(result, value));
// register identifier if it exists
int identifier = result.Identifier;
if (PropagatorResult.NullIdentifier != identifier)
{
identifierValues.Add(identifier, value);
}
}
}
// Consume the current reader (and subsequent result sets) so that any errors
// executing the function can be intercepted
CommandHelper.ConsumeReader(reader);
}
}
else
{
rowsAffected = m_dbCommand.ExecuteNonQuery();
}
// if an explicit rows affected parameter exists, use this value instead
if (null != m_rowsAffectedParameter)
{
// by design, negative row counts indicate failure iff. an explicit rows
// affected parameter is used
if (DBNull.Value.Equals(m_rowsAffectedParameter.Value))
{
rowsAffected = 0;
}
else
{
try
{
rowsAffected = Convert.ToInt64(m_rowsAffectedParameter.Value, CultureInfo.InvariantCulture);
}
catch (Exception e)
{
if (UpdateTranslator.RequiresContext(e))
{
// wrap the exception
throw EntityUtil.Update(System.Data.Entity.Strings.Update_UnableToConvertRowsAffectedParameterToInt32(
m_rowsAffectedParameter.ParameterName, typeof(int).FullName), e, this.GetStateEntries(translator));
}
throw;
}
}
}
return rowsAffected;
}
private int GetColumnOrdinal(UpdateTranslator translator, DbDataReader reader, string columnName)
{
int columnOrdinal;
try
{
columnOrdinal = reader.GetOrdinal(columnName);
}
catch (IndexOutOfRangeException)
{
throw EntityUtil.Update(System.Data.Entity.Strings.Update_MissingResultColumn(columnName), null,
this.GetStateEntries(translator));
}
return columnOrdinal;
}
///
/// Gets modification operator corresponding to the given entity state.
///
private static ModificationOperator GetModificationOperator(EntityState state)
{
switch (state)
{
case EntityState.Modified:
case EntityState.Unchanged:
// unchanged entities correspond to updates (consider the case where
// the entity is not being modified but a collocated relationship is)
return ModificationOperator.Update;
case EntityState.Added:
return ModificationOperator.Insert;
case EntityState.Deleted:
return ModificationOperator.Delete;
default:
Debug.Fail("unexpected entity state " + state);
return default(ModificationOperator);
}
}
internal override int CompareToType(UpdateCommand otherCommand)
{
Debug.Assert(!object.ReferenceEquals(this, otherCommand), "caller should ensure other command is different");
FunctionUpdateCommand other = (FunctionUpdateCommand)otherCommand;
// first state entry is the 'main' state entry for the command (see ctor)
IEntityStateEntry thisParent = this.m_stateEntries[0];
IEntityStateEntry otherParent = other.m_stateEntries[0];
// order by operator
int result = (int)GetModificationOperator(thisParent.State) -
(int)GetModificationOperator(otherParent.State);
if (0 != result) { return result; }
// order by entity set
result = StringComparer.Ordinal.Compare(thisParent.EntitySet.Name, otherParent.EntitySet.Name);
if (0 != result) { return result; }
result = StringComparer.Ordinal.Compare(thisParent.EntitySet.EntityContainer.Name, otherParent.EntitySet.EntityContainer.Name);
if (0 != result) { return result; }
// order by key values
int thisInputIdentifierCount = (null == this.m_inputIdentifiers ? 0 : this.m_inputIdentifiers.Count);
int otherInputIdentifierCount = (null == other.m_inputIdentifiers ? 0 : other.m_inputIdentifiers.Count);
result = thisInputIdentifierCount - otherInputIdentifierCount;
if (0 != result) { return result; }
for (int i = 0; i < thisInputIdentifierCount; i++)
{
DbParameter thisParameter = this.m_inputIdentifiers[i].Value;
DbParameter otherParameter = other.m_inputIdentifiers[i].Value;
result = ByValueComparer.Default.Compare(thisParameter.Value, otherParameter.Value);
if (0 != result) { return result; }
}
// If the result is still zero, it means key values are all the same. Switch to synthetic identifiers
// to differentiate.
for (int i = 0; i < thisInputIdentifierCount; i++)
{
int thisIdentifier = this.m_inputIdentifiers[i].Key;
int otherIdentifier = other.m_inputIdentifiers[i].Key;
result = thisIdentifier - otherIdentifier;
if (0 != result) { return result; }
}
return result;
}
#endregion
}
}