261 lines
11 KiB
C#
261 lines
11 KiB
C#
//---------------------------------------------------------------------
|
|
// <copyright file="UpdateCommand.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//
|
|
// @owner [....]
|
|
// @backupOwner [....]
|
|
//---------------------------------------------------------------------
|
|
|
|
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
|
|
}
|
|
}
|