|
//---------------------------------------------------------------------
// <copyright file="UpdateCommand.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
using System.Data.Metadata.Edm;
using System.Data.Common;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Globalization;
using System.Data.Common.Utils;
using System.Data.Common.CommandTrees;
using System.Data.Objects;
using System.Linq;
using System.Data.EntityClient;
using System.Threading;
namespace System.Data.Mapping.Update.Internal
{
internal enum UpdateCommandKind
{
Dynamic,
Function,
}
/// <summary>
/// Class storing the result of compiling an instance DML command.
/// </summary>
internal abstract class UpdateCommand : IComparable<UpdateCommand>, IEquatable<UpdateCommand>
{
protected UpdateCommand(PropagatorResult originalValues, PropagatorResult currentValues)
{
m_originalValues = originalValues;
m_currentValues = currentValues;
}
private readonly PropagatorResult m_originalValues;
private readonly PropagatorResult m_currentValues;
// When it is not possible to order two commands based on their contents, we assign an 'ordering identifier'
// so that one will consistently precede the other.
private static int s_orderingIdentifierCounter;
private int m_orderingIdentifier;
/// <summary>
/// Gets all identifiers (key values basically) generated by this command. For instance,
/// @@IDENTITY values.
/// </summary>
internal abstract IEnumerable<int> OutputIdentifiers { get; }
/// <summary>
/// Gets all identifiers required by this command.
/// </summary>
internal abstract IEnumerable<int> InputIdentifiers { get; }
/// <summary>
/// Gets table (if any) associated with the current command. FunctionUpdateCommand has no table.
/// </summary>
internal virtual EntitySet Table
{
get
{
return null;
}
}
/// <summary>
/// Gets type of command.
/// </summary>
internal abstract UpdateCommandKind Kind
{
get;
}
/// <summary>
/// Gets original values of row/entity handled by this command.
/// </summary>
internal PropagatorResult OriginalValues { get { return m_originalValues; } }
/// <summary>
/// Gets current values of row/entity handled by this command.
/// </summary>
internal PropagatorResult CurrentValues { get { return m_currentValues; } }
/// <summary>
/// Yields all state entries contributing to this command. Used for error reporting.
/// </summary>
/// <param name="translator">Translator context.</param>
/// <returns>Related state entries.</returns>
internal abstract IList<IEntityStateEntry> GetStateEntries(UpdateTranslator translator);
/// <summary>
/// Determines model level dependencies for the current command. Dependencies are based
/// on the model operations performed by the command (adding or deleting entities or relationships).
/// </summary>
internal void GetRequiredAndProducedEntities(UpdateTranslator translator,
KeyToListMap<EntityKey, UpdateCommand> addedEntities,
KeyToListMap<EntityKey, UpdateCommand> deletedEntities,
KeyToListMap<EntityKey, UpdateCommand> addedRelationships,
KeyToListMap<EntityKey, UpdateCommand> deletedRelationships)
{
IList<IEntityStateEntry> stateEntries = GetStateEntries(translator);
foreach (IEntityStateEntry stateEntry in stateEntries)
{
if (!stateEntry.IsRelationship)
{
if (stateEntry.State == EntityState.Added)
{
addedEntities.Add(stateEntry.EntityKey, this);
}
else if (stateEntry.State == EntityState.Deleted)
{
deletedEntities.Add(stateEntry.EntityKey, this);
}
}
}
// process foreign keys
if (null != this.OriginalValues)
{
// if a foreign key being deleted, it 'frees' or 'produces' the referenced key
AddReferencedEntities(translator, this.OriginalValues, deletedRelationships);
}
if (null != this.CurrentValues)
{
// if a foreign key is being added, if requires the referenced key
AddReferencedEntities(translator, this.CurrentValues, addedRelationships);
}
// process relationships
foreach (IEntityStateEntry stateEntry in stateEntries)
{
if (stateEntry.IsRelationship)
{
// only worry about the relationship if it is being added or deleted
bool isAdded = stateEntry.State == EntityState.Added;
if (isAdded || stateEntry.State == EntityState.Deleted)
{
DbDataRecord record = isAdded ? (DbDataRecord)stateEntry.CurrentValues : stateEntry.OriginalValues;
Debug.Assert(2 == record.FieldCount, "non-binary relationship?");
EntityKey end1 = (EntityKey)record[0];
EntityKey end2 = (EntityKey)record[1];
// relationships require the entity when they're added and free the entity when they're deleted...
KeyToListMap<EntityKey, UpdateCommand> affected = isAdded ? addedRelationships : deletedRelationships;
// both ends are being modified by the relationship
affected.Add(end1, this);
affected.Add(end2, this);
}
}
}
}
private void AddReferencedEntities(UpdateTranslator translator, PropagatorResult result, KeyToListMap<EntityKey, UpdateCommand> referencedEntities)
{
foreach (PropagatorResult property in result.GetMemberValues())
{
if (property.IsSimple && property.Identifier != PropagatorResult.NullIdentifier &&
(PropagatorFlags.ForeignKey == (property.PropagatorFlags & PropagatorFlags.ForeignKey)))
{
foreach (int principal in translator.KeyManager.GetDirectReferences(property.Identifier))
{
PropagatorResult owner;
if (translator.KeyManager.TryGetIdentifierOwner(principal, out owner) &&
null != owner.StateEntry)
{
Debug.Assert(!owner.StateEntry.IsRelationship, "owner must not be a relationship");
referencedEntities.Add(owner.StateEntry.EntityKey, this);
}
}
}
}
}
/// <summary>
/// Executes the current update command.
/// </summary>
/// <param name="translator">Translator context.</param>
/// <param name="connection">EntityConnection to use (and implicitly, the EntityTransaction to use).</param>
/// <param name="identifierValues">Aggregator for identifier values (read for InputIdentifiers; write for
/// OutputIdentifiers</param>
/// <param name="generatedValues">Aggregator for server generated values.</param>
/// <returns>Number of rows affected by the command.</returns>
internal abstract long Execute(UpdateTranslator translator, EntityConnection connection, Dictionary<int, object> identifierValues,
List<KeyValuePair<PropagatorResult, object>> generatedValues);
/// <summary>
/// Implementation of CompareTo for concrete subclass of UpdateCommand.
/// </summary>
internal abstract int CompareToType(UpdateCommand other);
/// <summary>
/// Provides a suggested ordering between two commands. Ensuring a consistent ordering is important to avoid deadlocks
/// between two clients because it means locks are acquired in the same order where possible. The ordering criteria are as
/// follows (and are partly implemented in the CompareToType method). In some cases there are specific secondary
/// reasons for the order (e.g. operator kind), but for the most case we just care that a consistent ordering
/// is applied:
///
/// - The kind of command (dynamic or function). This is an arbitrary criteria.
/// - The kind of operator (insert, update, delete). See <see cref="ModificationOperator"/> for details of the ordering.
/// - The target of the modification (table for dynamic, set for function).
/// - Primary key for the modification (table key for dynamic, entity keys for function).
///
/// If it is not possible to differentiate between two commands (e.g., where the user is inserting entities with server-generated
/// primary keys and has not given explicit values), arbitrary ordering identifiers are assigned to the commands to
/// ensure CompareTo is well-behaved (doesn't return 0 for different commands and suggests consistent ordering).
/// </summary>
public int CompareTo(UpdateCommand other)
{
// If the commands are the same (by reference), return 0 immediately. Otherwise, we try to find (and eventually
// force) an ordering between them by returning a value that is non-zero.
if (this.Equals(other)) { return 0; }
Debug.Assert(null != other, "comparing to null UpdateCommand");
int result = (int)this.Kind - (int)other.Kind;
if (0 != result) { return result; }
// defer to specific type for other comparisons...
result = CompareToType(other);
if (0 != result) { return result; }
// if the commands are indistinguishable, assign arbitrary identifiers to them to ensure consistent ordering
unchecked
{
if (this.m_orderingIdentifier == 0)
{
this.m_orderingIdentifier = Interlocked.Increment(ref s_orderingIdentifierCounter);
}
if (other.m_orderingIdentifier == 0)
{
other.m_orderingIdentifier = Interlocked.Increment(ref s_orderingIdentifierCounter);
}
return this.m_orderingIdentifier - other.m_orderingIdentifier;
}
}
#region IEquatable: note that we use reference equality
public bool Equals(UpdateCommand other)
{
return base.Equals(other);
}
public override bool Equals(object obj)
{
return base.Equals(obj);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
#endregion
}
}
|