Imported Upstream version 4.0.0~alpha1

Former-commit-id: 806294f5ded97629b74c85c09952f2a74fe182d9
This commit is contained in:
Jo Shields
2015-04-07 09:35:12 +01:00
parent 283343f570
commit 3c1f479b9d
22469 changed files with 2931443 additions and 869343 deletions

View File

@@ -0,0 +1,143 @@
//---------------------------------------------------------------------
// <copyright file="AssociationSetMetadata.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Metadata.Edm;
using System.Data.Common.Utils;
using System.Data.Common.CommandTrees;
using System.Collections.Generic;
using System.Linq;
namespace System.Data.Mapping.Update.Internal
{
/// <summary>
/// Encapsulates information about ends of an association set needed to correctly
/// interpret updates.
/// </summary>
internal sealed class AssociationSetMetadata
{
/// <summary>
/// Gets association ends that must be modified if the association
/// is changed (e.g. the mapping of the association is conditioned
/// on some property of the end)
/// </summary>
internal readonly Set<AssociationEndMember> RequiredEnds;
/// <summary>
/// Gets association ends that may be implicitly modified as a result
/// of changes to the association (e.g. collocated entity with server
/// generated value)
/// </summary>
internal readonly Set<AssociationEndMember> OptionalEnds;
/// <summary>
/// Gets association ends whose values may influence the association
/// (e.g. where there is a ReferentialIntegrity or "foreign key" constraint)
/// </summary>
internal readonly Set<AssociationEndMember> IncludedValueEnds;
/// <summary>
/// true iff. there are interesting ends for this association set.
/// </summary>
internal bool HasEnds
{
get { return 0 < RequiredEnds.Count || 0 < OptionalEnds.Count || 0 < IncludedValueEnds.Count; }
}
/// <summary>
/// Initialize Metadata for an AssociationSet
/// </summary>
internal AssociationSetMetadata(Set<EntitySet> affectedTables, AssociationSet associationSet, MetadataWorkspace workspace)
{
// If there is only 1 table, there can be no ambiguity about the "destination" of a relationship, so such
// sets are not typically required.
bool isRequired = 1 < affectedTables.Count;
// determine the ends of the relationship
var ends = associationSet.AssociationSetEnds;
// find collocated entities
foreach (EntitySet table in affectedTables)
{
// Find extents influencing the table
var influencingExtents = MetadataHelper.GetInfluencingEntitySetsForTable(table, workspace);
foreach (EntitySet influencingExtent in influencingExtents)
{
foreach (var end in ends)
{
// If the extent is an end of the relationship and we haven't already added it to the
// required set...
if (end.EntitySet.EdmEquals(influencingExtent))
{
if (isRequired)
{
AddEnd(ref RequiredEnds, end.CorrespondingAssociationEndMember);
}
else if (null == RequiredEnds || !RequiredEnds.Contains(end.CorrespondingAssociationEndMember))
{
AddEnd(ref OptionalEnds, end.CorrespondingAssociationEndMember);
}
}
}
}
}
// fix Required and Optional sets
FixSet(ref RequiredEnds);
FixSet(ref OptionalEnds);
// for associations with referential constraints, the principal end is always interesting
// since its key values may take precedence over the key values of the dependent end
foreach (ReferentialConstraint constraint in associationSet.ElementType.ReferentialConstraints)
{
// FromRole is the principal end in the referential constraint
AssociationEndMember principalEnd = (AssociationEndMember)constraint.FromRole;
if (!RequiredEnds.Contains(principalEnd) &&
!OptionalEnds.Contains(principalEnd))
{
AddEnd(ref IncludedValueEnds, principalEnd);
}
}
FixSet(ref IncludedValueEnds);
}
/// <summary>
/// Initialize given required ends.
/// </summary>
internal AssociationSetMetadata(IEnumerable<AssociationEndMember> requiredEnds)
{
if (requiredEnds.Any())
{
RequiredEnds = new Set<AssociationEndMember>(requiredEnds);
}
FixSet(ref RequiredEnds);
FixSet(ref OptionalEnds);
FixSet(ref IncludedValueEnds);
}
static private void AddEnd(ref Set<AssociationEndMember> set, AssociationEndMember element)
{
if (null == set)
{
set = new Set<AssociationEndMember>();
}
set.Add(element);
}
static private void FixSet(ref Set<AssociationEndMember> set)
{
if (null == set)
{
set = Set<AssociationEndMember>.Empty;
}
else
{
set.MakeReadOnly();
}
}
}
}

View File

@@ -0,0 +1,111 @@
//---------------------------------------------------------------------
// <copyright file="ChangeNode.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Metadata.Edm;
using System.Collections.Generic;
using System.Text;
using System.Globalization;
namespace System.Data.Mapping.Update.Internal
{
/// <summary>
/// This class encapsulates changes propagated to a node in an update mapping view.
/// It contains lists of deleted and inserted rows. Key intersections betweens rows
/// in the two sets are treated as updates in the store.
/// </summary>
/// <remarks>
/// <para>
/// Additional tags indicating the roles of particular values (e.g., concurrency, undefined,
/// etc.) are stored within each row: where appropriate, constants appearing
/// within a row are associated with a <see cref="PropagatorResult" /> through the <see cref=
/// "UpdateTranslator" />.
/// </para>
/// <para>
/// The 'leaves' of an update mapping view (UMV) are extent expressions. A change node
/// associated with an extent expression is simply the list of changes to the C-Space
/// requested by a caller. As changes propagate 'up' the UMV expression tree, we recursively
/// apply transformations such that the change node associated with the root of the UMV
/// represents changes to apply in the S-Space.
/// </para>
/// </remarks>
internal class ChangeNode
{
#region Constructors
/// <summary>
/// Constructs a change node containing changes belonging to the specified collection
/// schema definition.
/// </summary>
/// <param name="elementType">Sets <see cref="ElementType" /> property.</param>
internal ChangeNode(TypeUsage elementType)
{
m_elementType = elementType;
}
#endregion
#region Fields
private TypeUsage m_elementType;
private List<PropagatorResult> m_inserted = new List<PropagatorResult>();
private List<PropagatorResult> m_deleted = new List<PropagatorResult>();
private PropagatorResult m_placeholder;
#endregion
#region Properties
/// <summary>
/// Gets the type of the rows contained in this node. This type corresponds (not coincidentally)
/// to the type of an expression in an update mapping view.
/// </summary>
internal TypeUsage ElementType { get { return m_elementType; } }
/// <summary>
/// Gets a list of rows to be inserted.
/// </summary>
internal List<PropagatorResult> Inserted { get { return m_inserted; } }
/// <summary>
/// Gets a list of rows to be deleted.
/// </summary>
internal List<PropagatorResult> Deleted { get { return m_deleted; } }
/// <summary>
/// Gets or sets a version of a record at this node with default record. The record has the type
/// of the node we are visiting.
/// </summary>
internal PropagatorResult Placeholder
{
get { return m_placeholder; }
set { m_placeholder = value; }
}
#endregion
#if DEBUG
public override string ToString()
{
StringBuilder builder = new StringBuilder();
builder.AppendLine("{");
builder.AppendFormat(CultureInfo.InvariantCulture, " ElementType = {0}", ElementType).AppendLine();
builder.AppendLine(" Inserted = {");
foreach (PropagatorResult insert in Inserted)
{
builder.Append(" ").AppendLine(insert.ToString());
}
builder.AppendLine(" }");
builder.AppendLine(" Deleted = {");
foreach (PropagatorResult delete in Deleted)
{
builder.Append(" ").AppendLine(delete.ToString());
}
builder.AppendLine(" }");
builder.AppendFormat(CultureInfo.InvariantCulture, " PlaceHolder = {0}", Placeholder).AppendLine();
builder.Append("}");
return builder.ToString();
}
#endif
}
}

View File

@@ -0,0 +1,158 @@
//---------------------------------------------------------------------
// <copyright file="CompositeKey.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Common.Utils;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
namespace System.Data.Mapping.Update.Internal
{
/// <summary>
/// Represents a key composed of multiple parts.
/// </summary>
internal class CompositeKey
{
#region Fields
/// <summary>
/// Gets components of this composite key.
/// </summary>
internal readonly PropagatorResult[] KeyComponents;
#endregion
#region Constructors
/// <summary>
/// Initialize a new composite key using the given constant values. Order is important.
/// </summary>
/// <param name="values">Key values.</param>
internal CompositeKey(PropagatorResult[] constants)
{
Debug.Assert(null != constants, "key values must be given");
KeyComponents = constants;
}
#endregion
#region Methods
/// <summary>
/// Creates a key comparer operating in the context of the given translator.
/// </summary>
internal static IEqualityComparer<CompositeKey> CreateComparer(KeyManager keyManager)
{
return new CompositeKeyComparer(keyManager);
}
/// <summary>
/// Creates a merged key instance where each key component contains both elements.
/// </summary>
/// <param name="other">Must be a non-null compatible key (same number of components).</param>
/// <returns>Merged key.</returns>
internal CompositeKey Merge(KeyManager keyManager, CompositeKey other)
{
Debug.Assert(null != other && other.KeyComponents.Length == this.KeyComponents.Length, "expected a compatible CompositeKey");
PropagatorResult[] mergedKeyValues = new PropagatorResult[this.KeyComponents.Length];
for (int i = 0; i < this.KeyComponents.Length; i++)
{
mergedKeyValues[i] = this.KeyComponents[i].Merge(keyManager, other.KeyComponents[i]);
}
return new CompositeKey(mergedKeyValues);
}
#endregion
/// <summary>
/// Equality and comparison implementation for composite keys.
/// </summary>
private class CompositeKeyComparer : IEqualityComparer<CompositeKey>
{
private readonly KeyManager _manager;
internal CompositeKeyComparer(KeyManager manager)
{
_manager = EntityUtil.CheckArgumentNull(manager, "manager");
}
// determines equality by comparing each key component
public bool Equals(CompositeKey left, CompositeKey right)
{
// Short circuit the comparison if we know the other reference is equivalent
if (object.ReferenceEquals(left, right)) { return true; }
// If either side is null, return false order (both can't be null because of
// the previous check)
if (null == left || null == right) { return false; }
Debug.Assert(null != left.KeyComponents && null != right.KeyComponents,
"(Update/JoinPropagator) CompositeKey must be initialized");
if (left.KeyComponents.Length != right.KeyComponents.Length) { return false; }
for (int i = 0; i < left.KeyComponents.Length; i++)
{
PropagatorResult leftValue = left.KeyComponents[i];
PropagatorResult rightValue = right.KeyComponents[i];
// if both side are identifiers, check if they're the same or one is constrained by the
// other (if there is a dependent-principal relationship, they get fixed up to the same
// value)
if (leftValue.Identifier != PropagatorResult.NullIdentifier)
{
if (rightValue.Identifier == PropagatorResult.NullIdentifier ||
_manager.GetCliqueIdentifier(leftValue.Identifier) != _manager.GetCliqueIdentifier(rightValue.Identifier))
{
return false;
}
}
else
{
if (rightValue.Identifier != PropagatorResult.NullIdentifier ||
!ByValueEqualityComparer.Default.Equals(leftValue.GetSimpleValue(), rightValue.GetSimpleValue()))
{
return false;
}
}
}
return true;
}
// creates a hash code by XORing hash codes for all key components.
public int GetHashCode(CompositeKey key)
{
EntityUtil.CheckArgumentNull(key, "key");
int result = 0;
foreach (PropagatorResult keyComponent in key.KeyComponents)
{
result = (result << 5) ^ GetComponentHashCode(keyComponent);
}
return result;
}
// Gets the value to use for hash code
private int GetComponentHashCode(PropagatorResult keyComponent)
{
if (keyComponent.Identifier == PropagatorResult.NullIdentifier)
{
// no identifier exists for this key component, so use the actual key
// value
Debug.Assert(null != keyComponent && null != keyComponent,
"key value must not be null");
return ByValueEqualityComparer.Default.GetHashCode(keyComponent.GetSimpleValue());
}
else
{
// use ID for FK graph clique (this ensures that keys fixed up to the same
// value based on a constraint will have the same hash code)
return _manager.GetCliqueIdentifier(keyComponent.Identifier).GetHashCode();
}
}
}
}
}

View File

@@ -0,0 +1,349 @@
//---------------------------------------------------------------------
// <copyright file="DynamicUpdateCommand.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Collections.Generic;
using System.Data.Common.CommandTrees;
using System.Data.Metadata.Edm;
using System.Data.Common;
using System.Data.EntityClient;
using System.Diagnostics;
using System.Data.Common.Utils;
using System.Linq;
using System.Data.Common.CommandTrees.ExpressionBuilder;
using System.Data.Spatial;
namespace System.Data.Mapping.Update.Internal
{
internal sealed class DynamicUpdateCommand : UpdateCommand
{
private readonly ModificationOperator m_operator;
private readonly TableChangeProcessor m_processor;
private readonly List<KeyValuePair<int, DbSetClause>> m_inputIdentifiers;
private readonly Dictionary<int, string> m_outputIdentifiers;
private readonly DbModificationCommandTree m_modificationCommandTree;
internal DynamicUpdateCommand(TableChangeProcessor processor, UpdateTranslator translator, ModificationOperator op,
PropagatorResult originalValues, PropagatorResult currentValues, DbModificationCommandTree tree,
Dictionary<int, string> outputIdentifiers)
: base(originalValues, currentValues)
{
m_processor = EntityUtil.CheckArgumentNull(processor, "processor");
m_operator = op;
m_modificationCommandTree = EntityUtil.CheckArgumentNull(tree, "commandTree");
m_outputIdentifiers = outputIdentifiers; // may be null (not all commands have output identifiers)
// initialize identifier information (supports lateral propagation of server gen values)
if (ModificationOperator.Insert == op || ModificationOperator.Update == op)
{
const int capacity = 2; // "average" number of identifiers per row
m_inputIdentifiers = new List<KeyValuePair<int ,DbSetClause>>(capacity);
foreach (KeyValuePair<EdmMember, PropagatorResult> member in
Helper.PairEnumerations(TypeHelpers.GetAllStructuralMembers(this.CurrentValues.StructuralType),
this.CurrentValues.GetMemberValues()))
{
DbSetClause setter;
int identifier = member.Value.Identifier;
if (PropagatorResult.NullIdentifier != identifier &&
TryGetSetterExpression(tree, member.Key, op, out setter)) // can find corresponding setter
{
foreach (int principal in translator.KeyManager.GetPrincipals(identifier))
{
m_inputIdentifiers.Add(new KeyValuePair<int, DbSetClause>(principal, setter));
}
}
}
}
}
// effects: try to find setter expression for the given member
// requires: command tree must be an insert or update tree (since other DML trees hnabve
private static bool TryGetSetterExpression(DbModificationCommandTree tree, EdmMember member, ModificationOperator op, out DbSetClause setter)
{
Debug.Assert(op == ModificationOperator.Insert || op == ModificationOperator.Update, "only inserts and updates have setters");
IEnumerable<DbModificationClause> clauses;
if (ModificationOperator.Insert == op)
{
clauses = ((DbInsertCommandTree)tree).SetClauses;
}
else
{
clauses = ((DbUpdateCommandTree)tree).SetClauses;
}
foreach (DbSetClause setClause in clauses)
{
// check if this is the correct setter
if (((DbPropertyExpression)setClause.Property).Property.EdmEquals(member))
{
setter = setClause;
return true;
}
}
// no match found
setter = null;
return false;
}
internal override long Execute(UpdateTranslator translator, EntityConnection connection, Dictionary<int, object> identifierValues, List<KeyValuePair<PropagatorResult, object>> generatedValues)
{
// Compile command
using (DbCommand command = this.CreateCommand(translator, identifierValues))
{
// configure command to use the connection and transaction for this session
command.Transaction = ((null != connection.CurrentTransaction) ? connection.CurrentTransaction.StoreTransaction : null);
command.Connection = connection.StoreConnection;
if (translator.CommandTimeout.HasValue)
{
command.CommandTimeout = translator.CommandTimeout.Value;
}
// Execute the query
int rowsAffected;
if (m_modificationCommandTree.HasReader)
{
// retrieve server gen results
rowsAffected = 0;
using (DbDataReader reader = command.ExecuteReader(CommandBehavior.SequentialAccess))
{
if (reader.Read())
{
rowsAffected++;
IBaseList<EdmMember> members = TypeHelpers.GetAllStructuralMembers(this.CurrentValues.StructuralType);
for (int ordinal = 0; ordinal < reader.FieldCount; ordinal++)
{
// column name of result corresponds to column name of table
string columnName = reader.GetName(ordinal);
EdmMember member = members[columnName];
object value;
if (Helper.IsSpatialType(member.TypeUsage) && !reader.IsDBNull(ordinal))
{
value = SpatialHelpers.GetSpatialValue(translator.MetadataWorkspace, reader, member.TypeUsage, ordinal);
}
else
{
value = reader.GetValue(ordinal);
}
// retrieve result which includes the context for back-propagation
int columnOrdinal = members.IndexOf(member);
PropagatorResult result = this.CurrentValues.GetMemberValue(columnOrdinal);
// register for back-propagation
generatedValues.Add(new KeyValuePair<PropagatorResult, object>(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 command can be intercepted
CommandHelper.ConsumeReader(reader);
}
}
else
{
rowsAffected = command.ExecuteNonQuery();
}
return rowsAffected;
}
}
/// <summary>
/// Gets DB command definition encapsulating store logic for this command.
/// </summary>
private DbCommand CreateCommand(UpdateTranslator translator, Dictionary<int, object> identifierValues)
{
DbModificationCommandTree commandTree = m_modificationCommandTree;
// check if any server gen identifiers need to be set
if (null != m_inputIdentifiers)
{
Dictionary<DbSetClause, DbSetClause> modifiedClauses = new Dictionary<DbSetClause, DbSetClause>();
for (int idx = 0; idx < m_inputIdentifiers.Count; idx++)
{
KeyValuePair<int, DbSetClause> inputIdentifier = m_inputIdentifiers[idx];
object value;
if (identifierValues.TryGetValue(inputIdentifier.Key, out value))
{
// reset the value of the identifier
DbSetClause newClause = new DbSetClause(inputIdentifier.Value.Property, DbExpressionBuilder.Constant(value));
modifiedClauses[inputIdentifier.Value] = newClause;
m_inputIdentifiers[idx] = new KeyValuePair<int, DbSetClause>(inputIdentifier.Key, newClause);
}
}
commandTree = RebuildCommandTree(commandTree, modifiedClauses);
}
return translator.CreateCommand(commandTree);
}
private DbModificationCommandTree RebuildCommandTree(DbModificationCommandTree originalTree, Dictionary<DbSetClause, DbSetClause> clauseMappings)
{
if (clauseMappings.Count == 0)
{
return originalTree;
}
DbModificationCommandTree result;
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?");
if (originalTree.CommandTreeKind == DbCommandTreeKind.Insert)
{
DbInsertCommandTree insertTree = (DbInsertCommandTree)originalTree;
result = new DbInsertCommandTree(insertTree.MetadataWorkspace, insertTree.DataSpace,
insertTree.Target, ReplaceClauses(insertTree.SetClauses, clauseMappings).AsReadOnly(), insertTree.Returning);
}
else
{
DbUpdateCommandTree updateTree = (DbUpdateCommandTree)originalTree;
result = new DbUpdateCommandTree(updateTree.MetadataWorkspace, updateTree.DataSpace,
updateTree.Target, updateTree.Predicate, ReplaceClauses(updateTree.SetClauses, clauseMappings).AsReadOnly(), updateTree.Returning);
}
return result;
}
/// <summary>
/// Creates a new list of modification clauses with the specified remapped clauses replaced.
/// </summary>
private List<DbModificationClause> ReplaceClauses(IList<DbModificationClause> originalClauses, Dictionary<DbSetClause, DbSetClause> mappings)
{
List<DbModificationClause> result = new List<DbModificationClause>(originalClauses.Count);
for (int idx = 0; idx < originalClauses.Count; idx++)
{
DbSetClause replacementClause;
if (mappings.TryGetValue((DbSetClause)originalClauses[idx], out replacementClause))
{
result.Add(replacementClause);
}
else
{
result.Add(originalClauses[idx]);
}
}
return result;
}
internal ModificationOperator Operator { get { return m_operator; } }
internal override EntitySet Table { get { return this.m_processor.Table; } }
internal override IEnumerable<int> InputIdentifiers
{
get
{
if (null == m_inputIdentifiers)
{
yield break;
}
else
{
foreach (KeyValuePair<int, DbSetClause> inputIdentifier in m_inputIdentifiers)
{
yield return inputIdentifier.Key;
}
}
}
}
internal override IEnumerable<int> OutputIdentifiers
{
get
{
if (null == m_outputIdentifiers)
{
return Enumerable.Empty<int>();
}
return m_outputIdentifiers.Keys;
}
}
internal override UpdateCommandKind Kind
{
get { return UpdateCommandKind.Dynamic; }
}
internal override IList<IEntityStateEntry> GetStateEntries(UpdateTranslator translator)
{
List<IEntityStateEntry> stateEntries = new List<IEntityStateEntry>(2);
if (null != this.OriginalValues)
{
foreach (IEntityStateEntry stateEntry in SourceInterpreter.GetAllStateEntries(
this.OriginalValues, translator, this.Table))
{
stateEntries.Add(stateEntry);
}
}
if (null != this.CurrentValues)
{
foreach (IEntityStateEntry stateEntry in SourceInterpreter.GetAllStateEntries(
this.CurrentValues, translator, this.Table))
{
stateEntries.Add(stateEntry);
}
}
return stateEntries;
}
internal override int CompareToType(UpdateCommand otherCommand)
{
Debug.Assert(!object.ReferenceEquals(this, otherCommand), "caller is supposed to ensure otherCommand is different reference");
DynamicUpdateCommand other = (DynamicUpdateCommand)otherCommand;
// order by operation type
int result = (int)this.Operator - (int)other.Operator;
if (0 != result) { return result; }
// order by Container.Table
result = StringComparer.Ordinal.Compare(this.m_processor.Table.Name, other.m_processor.Table.Name);
if (0 != result) { return result; }
result = StringComparer.Ordinal.Compare(this.m_processor.Table.EntityContainer.Name, other.m_processor.Table.EntityContainer.Name);
if (0 != result) { return result; }
// order by table key
PropagatorResult thisResult = (this.Operator == ModificationOperator.Delete ? this.OriginalValues : this.CurrentValues);
PropagatorResult otherResult = (other.Operator == ModificationOperator.Delete ? other.OriginalValues : other.CurrentValues);
for (int i = 0; i < m_processor.KeyOrdinals.Length; i++)
{
int keyOrdinal = m_processor.KeyOrdinals[i];
object thisValue = thisResult.GetMemberValue(keyOrdinal).GetSimpleValue();
object otherValue = otherResult.GetMemberValue(keyOrdinal).GetSimpleValue();
result = ByValueComparer.Default.Compare(thisValue, otherValue);
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 < m_processor.KeyOrdinals.Length; i++)
{
int keyOrdinal = m_processor.KeyOrdinals[i];
int thisValue = thisResult.GetMemberValue(keyOrdinal).Identifier;
int otherValue = otherResult.GetMemberValue(keyOrdinal).Identifier;
result = thisValue - otherValue;
if (0 != result) { return result; }
}
return result;
}
}
}

View File

@@ -0,0 +1,66 @@
//---------------------------------------------------------------------
// <copyright file="ExtractedStateEntry.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
//---------------------------------------------------------------------
using System.Collections.Generic;
using System.Data.Common.CommandTrees;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Linq;
namespace System.Data.Mapping.Update.Internal
{
/// <summary>
/// Represents the data contained in a StateEntry using internal data structures
/// of the UpdatePipeline.
/// </summary>
internal struct ExtractedStateEntry
{
internal readonly EntityState State;
internal readonly PropagatorResult Original;
internal readonly PropagatorResult Current;
internal readonly IEntityStateEntry Source;
internal ExtractedStateEntry(UpdateTranslator translator, IEntityStateEntry stateEntry)
{
Debug.Assert(null != stateEntry, "stateEntry must not be null");
this.State = stateEntry.State;
this.Source = stateEntry;
switch (stateEntry.State)
{
case EntityState.Deleted:
this.Original = translator.RecordConverter.ConvertOriginalValuesToPropagatorResult(
stateEntry, ModifiedPropertiesBehavior.AllModified);
this.Current = null;
break;
case EntityState.Unchanged:
this.Original = translator.RecordConverter.ConvertOriginalValuesToPropagatorResult(
stateEntry, ModifiedPropertiesBehavior.NoneModified);
this.Current = translator.RecordConverter.ConvertCurrentValuesToPropagatorResult(
stateEntry, ModifiedPropertiesBehavior.NoneModified);
break;
case EntityState.Modified:
this.Original = translator.RecordConverter.ConvertOriginalValuesToPropagatorResult(
stateEntry, ModifiedPropertiesBehavior.SomeModified);
this.Current = translator.RecordConverter.ConvertCurrentValuesToPropagatorResult(
stateEntry, ModifiedPropertiesBehavior.SomeModified);
break;
case EntityState.Added:
this.Original = null;
this.Current = translator.RecordConverter.ConvertCurrentValuesToPropagatorResult(
stateEntry, ModifiedPropertiesBehavior.AllModified);
break;
default:
Debug.Fail("unexpected IEntityStateEntry.State for entity " + stateEntry.State);
this.Original = null;
this.Current = null;
break;
}
}
}
}

View File

@@ -0,0 +1,392 @@
//---------------------------------------------------------------------
// <copyright file="ExtractorMetadata.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
namespace System.Data.Mapping.Update.Internal
{
using System.Data.Common;
using System.Data.Common.Utils;
using System.Data.Entity;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Diagnostics;
using System.Linq;
internal enum ModifiedPropertiesBehavior
{
/// <summary>
/// Indicates that all properties are modified. Used for added and deleted entities and for
/// modified complex type sub-records.
/// </summary>
AllModified,
/// <summary>
/// Indicates that no properties are modified. Used for unmodified complex type sub-records.
/// </summary>
NoneModified,
/// <summary>
/// Indicates that some properties are modified. Used for modified entities.
/// </summary>
SomeModified,
}
/// <summary>
/// Encapsulates metadata information relevant to update for records extracted from
/// the entity state manager, such as concurrency flags and key information.
/// </summary>
internal class ExtractorMetadata
{
internal ExtractorMetadata(EntitySetBase entitySetBase, StructuralType type, UpdateTranslator translator)
{
EntityUtil.CheckArgumentNull(entitySetBase, "entitySetBase");
m_type = EntityUtil.CheckArgumentNull(type, "type");
m_translator = EntityUtil.CheckArgumentNull(translator, "translator");
EntityType entityType = null;
Set<EdmMember> keyMembers;
Set<EdmMember> foreignKeyMembers;
switch (type.BuiltInTypeKind)
{
case BuiltInTypeKind.RowType:
// for row types (which are actually association end key records in disguise), all members
// are keys
keyMembers = new Set<EdmMember>(((RowType)type).Properties).MakeReadOnly();
foreignKeyMembers = Set<EdmMember>.Empty;
break;
case BuiltInTypeKind.EntityType:
entityType = (EntityType)type;
keyMembers = new Set<EdmMember>(entityType.KeyMembers).MakeReadOnly();
foreignKeyMembers = new Set<EdmMember>(((EntitySet)entitySetBase).ForeignKeyDependents
.SelectMany(fk => fk.Item2.ToProperties)).MakeReadOnly();
break;
default:
keyMembers = Set<EdmMember>.Empty;
foreignKeyMembers = Set<EdmMember>.Empty;
break;
}
IBaseList<EdmMember> members = TypeHelpers.GetAllStructuralMembers(type);
m_memberMap = new MemberInformation[members.Count];
// for each member, cache expensive to compute metadata information
for (int ordinal = 0; ordinal < members.Count; ordinal++)
{
EdmMember member = members[ordinal];
// figure out flags for this member
PropagatorFlags flags = PropagatorFlags.NoFlags;
int? entityKeyOrdinal = default(int?);
if (keyMembers.Contains(member))
{
flags |= PropagatorFlags.Key;
if (null != entityType)
{
entityKeyOrdinal = entityType.KeyMembers.IndexOf(member);
}
}
if (foreignKeyMembers.Contains(member))
{
flags |= PropagatorFlags.ForeignKey;
}
if (MetadataHelper.GetConcurrencyMode(member) == ConcurrencyMode.Fixed)
{
flags |= PropagatorFlags.ConcurrencyValue;
}
// figure out whether this member is mapped to any server generated
// columns in the store
bool isServerGenerated = m_translator.ViewLoader.IsServerGen(entitySetBase, m_translator.MetadataWorkspace, member);
// figure out whether member nullability is used as a condition in mapping
bool isNullConditionMember = m_translator.ViewLoader.IsNullConditionMember(entitySetBase, m_translator.MetadataWorkspace, member);
// add information about this member
m_memberMap[ordinal] = new MemberInformation(ordinal, entityKeyOrdinal, flags, member, isServerGenerated, isNullConditionMember);
}
}
private readonly MemberInformation[] m_memberMap;
private readonly StructuralType m_type;
private readonly UpdateTranslator m_translator;
/// <summary>
/// Requires: record must have correct type for this metadata instance.
/// Populates a new <see cref="PropagatorResult"/> object representing a member of a record matching the
/// type of this extractor. Given a record and a member, this method wraps the value of the member
/// in a PropagatorResult. This operation can be performed efficiently by this class, which knows
/// important stuff about the type being extracted.
/// </summary>
/// <param name="stateEntry">state manager entry containing value (used for error reporting)</param>
/// <param name="record">Record containing value (used to find the actual value)</param>
/// <param name="currentValues">Indicates whether we are reading current or original values.</param>
/// <param name="key">Entity key for the state entry. Must be set for entity records.</param>
/// <param name="ordinal">Ordinal of Member for which to retrieve a value.</param>
/// modified (must be ordinally aligned with the type). Null indicates all members are modified.</param>
/// <param name="modifiedPropertiesBehavior">Indicates how to determine whether a property is modified.</param>
/// <returns>Propagator result describing this member value.</returns>
internal PropagatorResult RetrieveMember(IEntityStateEntry stateEntry, IExtendedDataRecord record, bool useCurrentValues,
EntityKey key, int ordinal, ModifiedPropertiesBehavior modifiedPropertiesBehavior)
{
MemberInformation memberInformation = m_memberMap[ordinal];
// get identifier value
int identifier;
if (memberInformation.IsKeyMember)
{
// retrieve identifier for this key member
Debug.Assert(null != (object)key, "entities must have keys, and only entity members are marked IsKeyMember by " +
"the metadata wrapper");
int keyOrdinal = memberInformation.EntityKeyOrdinal.Value;
identifier = m_translator.KeyManager.GetKeyIdentifierForMemberOffset(key, keyOrdinal, ((EntityType)m_type).KeyMembers.Count);
}
else if (memberInformation.IsForeignKeyMember)
{
identifier = m_translator.KeyManager.GetKeyIdentifierForMember(key, record.GetName(ordinal), useCurrentValues);
}
else
{
identifier = PropagatorResult.NullIdentifier;
}
// determine if the member is modified
bool isModified = modifiedPropertiesBehavior == ModifiedPropertiesBehavior.AllModified ||
(modifiedPropertiesBehavior == ModifiedPropertiesBehavior.SomeModified &&
stateEntry.ModifiedProperties != null &&
stateEntry.ModifiedProperties[memberInformation.Ordinal]);
// determine member value
Debug.Assert(record.GetName(ordinal) == memberInformation.Member.Name, "expect record to present properties in metadata order");
if (memberInformation.CheckIsNotNull && record.IsDBNull(ordinal))
{
throw EntityUtil.Update(Strings.Update_NullValue(record.GetName(ordinal)), null, stateEntry);
}
object value = record.GetValue(ordinal);
// determine what kind of member this is
// entityKey (association end)
EntityKey entityKey = value as EntityKey;
if (null != (object)entityKey)
{
return CreateEntityKeyResult(stateEntry, entityKey);
}
// record (nested complex type)
IExtendedDataRecord nestedRecord = value as IExtendedDataRecord;
if (null != nestedRecord)
{
// for structural types, we track whether the entire complex type value is modified or not
var nestedModifiedPropertiesBehavior = isModified
? ModifiedPropertiesBehavior.AllModified
: ModifiedPropertiesBehavior.NoneModified;
UpdateTranslator translator = m_translator;
return ExtractResultFromRecord(stateEntry, isModified, nestedRecord, useCurrentValues, translator, nestedModifiedPropertiesBehavior);
}
// simple value (column/property value)
return CreateSimpleResult(stateEntry, record, memberInformation, identifier, isModified, ordinal, value);
}
// Note that this is called only for association ends. Entities have key values inline.
private PropagatorResult CreateEntityKeyResult(IEntityStateEntry stateEntry, EntityKey entityKey)
{
// get metadata for key
EntityType entityType = entityKey.GetEntitySet(m_translator.MetadataWorkspace).ElementType;
RowType keyRowType = entityType.GetKeyRowType(m_translator.MetadataWorkspace);
ExtractorMetadata keyMetadata = m_translator.GetExtractorMetadata(stateEntry.EntitySet, keyRowType);
int keyMemberCount = keyRowType.Properties.Count;
PropagatorResult[] keyValues = new PropagatorResult[keyMemberCount];
for (int ordinal = 0; ordinal < keyRowType.Properties.Count; ordinal++)
{
EdmMember keyMember = keyRowType.Properties[ordinal];
// retrieve information about this key value
MemberInformation keyMemberInformation = keyMetadata.m_memberMap[ordinal];
int keyIdentifier = m_translator.KeyManager.GetKeyIdentifierForMemberOffset(entityKey, ordinal, keyRowType.Properties.Count);
object keyValue = null;
if (entityKey.IsTemporary)
{
// If the EntityKey is temporary, we need to retrieve the appropriate
// key value from the entity itself (or in this case, the IEntityStateEntry).
IEntityStateEntry entityEntry = stateEntry.StateManager.GetEntityStateEntry(entityKey);
Debug.Assert(entityEntry.State == EntityState.Added,
"The corresponding entry for a temp EntityKey should be in the Added State.");
keyValue = entityEntry.CurrentValues[keyMember.Name];
}
else
{
// Otherwise, we extract the value from within the EntityKey.
keyValue = entityKey.FindValueByName(keyMember.Name);
}
Debug.Assert(keyValue != null, "keyValue should've been retrieved.");
// construct propagator result
keyValues[ordinal] = PropagatorResult.CreateKeyValue(
keyMemberInformation.Flags,
keyValue,
stateEntry,
keyIdentifier);
// see UpdateTranslator.Identifiers for information on key identifiers and ordinals
}
return PropagatorResult.CreateStructuralValue(keyValues, keyMetadata.m_type, false);
}
private PropagatorResult CreateSimpleResult(IEntityStateEntry stateEntry, IExtendedDataRecord record, MemberInformation memberInformation,
int identifier, bool isModified, int recordOrdinal, object value)
{
CurrentValueRecord updatableRecord = record as CurrentValueRecord;
// construct flags for the value, which is needed for complex type and simple members
PropagatorFlags flags = memberInformation.Flags;
if (!isModified) { flags |= PropagatorFlags.Preserve; }
if (PropagatorResult.NullIdentifier != identifier)
{
// construct a key member
PropagatorResult result;
if ((memberInformation.IsServerGenerated || memberInformation.IsForeignKeyMember) && null != updatableRecord)
{
result = PropagatorResult.CreateServerGenKeyValue(flags, value, stateEntry, identifier, recordOrdinal);
}
else
{
result = PropagatorResult.CreateKeyValue(flags, value, stateEntry, identifier);
}
// we register the entity as the "owner" of an identity so that back-propagation can succeed
// (keys can only be back-propagated to entities, not association ends). It also allows us
// to walk to the entity state entry in case of exceptions, since the state entry propagated
// through the stack may be eliminated in a project above a join.
m_translator.KeyManager.RegisterIdentifierOwner(result);
return result;
}
else
{
if ((memberInformation.IsServerGenerated || memberInformation.IsForeignKeyMember) && null != updatableRecord)
{
// note: we only produce a server gen result when
return PropagatorResult.CreateServerGenSimpleValue(flags, value, updatableRecord, recordOrdinal);
}
else
{
return PropagatorResult.CreateSimpleValue(flags, value);
}
}
}
/// <summary>
/// Converts a record to a propagator result
/// </summary>
/// <param name="stateEntry">state manager entry containing the record</param>
/// <param name="isModified">Indicates whether the root element is modified (i.e., whether the type has changed)</param>
/// <param name="record">Record to convert</param>
/// <param name="useCurrentValues">Indicates whether we are retrieving current or original values.</param>
/// <param name="translator">Translator for session context; registers new metadata for the record type if none
/// exists</param>
/// <param name="modifiedPropertiesBehavior">Indicates how to determine whether a property is modified.</param>
/// <returns>Result corresponding to the given record</returns>
internal static PropagatorResult ExtractResultFromRecord(IEntityStateEntry stateEntry, bool isModified, IExtendedDataRecord record,
bool useCurrentValues, UpdateTranslator translator, ModifiedPropertiesBehavior modifiedPropertiesBehavior)
{
StructuralType structuralType = (StructuralType)record.DataRecordInfo.RecordType.EdmType;
ExtractorMetadata metadata = translator.GetExtractorMetadata(stateEntry.EntitySet, structuralType);
EntityKey key = stateEntry.EntityKey;
PropagatorResult[] nestedValues = new PropagatorResult[record.FieldCount];
for (int ordinal = 0; ordinal < nestedValues.Length; ordinal++)
{
nestedValues[ordinal] = metadata.RetrieveMember(stateEntry, record, useCurrentValues, key,
ordinal, modifiedPropertiesBehavior);
}
return PropagatorResult.CreateStructuralValue(nestedValues, structuralType, isModified);
}
private class MemberInformation
{
/// <summary>
/// Gets ordinal of the member.
/// </summary>
internal readonly int Ordinal;
/// <summary>
/// Gets key ordinal for primary key member (null if not a primary key).
/// </summary>
internal readonly int? EntityKeyOrdinal;
/// <summary>
/// Gets propagator flags for the member, excluding the 'Preserve' flag
/// which can only be set in context.
/// </summary>
internal readonly PropagatorFlags Flags;
/// <summary>
/// Indicates whether this is a key member.
/// </summary>
internal bool IsKeyMember
{
get
{
return PropagatorFlags.Key == (Flags & PropagatorFlags.Key);
}
}
/// <summary>
/// Indicates whether this is a foreign key member.
/// </summary>
internal bool IsForeignKeyMember
{
get
{
return PropagatorFlags.ForeignKey == (Flags & PropagatorFlags.ForeignKey);
}
}
/// <summary>
/// Indicates whether this value is server generated.
/// </summary>
internal readonly bool IsServerGenerated;
/// <summary>
/// Indicates whether non-null values are supported for this member.
/// </summary>
internal readonly bool CheckIsNotNull;
/// <summary>
/// Gets the member described by this wrapper.
/// </summary>
internal readonly EdmMember Member;
internal MemberInformation(int ordinal, int? entityKeyOrdinal, PropagatorFlags flags, EdmMember member, bool isServerGenerated, bool isNullConditionMember)
{
Debug.Assert(entityKeyOrdinal.HasValue ==
(member.DeclaringType.BuiltInTypeKind == BuiltInTypeKind.EntityType && (flags & PropagatorFlags.Key) == PropagatorFlags.Key),
"key ordinal should only be provided if this is an entity key property");
this.Ordinal = ordinal;
this.EntityKeyOrdinal = entityKeyOrdinal;
this.Flags = flags;
this.Member = member;
this.IsServerGenerated = isServerGenerated;
// in two cases, we must check that a member value is not null:
// - where the type participates in an isnull condition, nullability constraints must be honored
// - for complex types, mapping relies on nullability constraint
// - in other cases, nullability does not impact round trippability so we don't check
this.CheckIsNotNull = !TypeSemantics.IsNullable(member) &&
(isNullConditionMember || member.TypeUsage.EdmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType);
}
}
}
}

View File

@@ -0,0 +1,354 @@
//---------------------------------------------------------------------
// <copyright file="FunctionMappingTranslator.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
namespace System.Data.Mapping.Update.Internal
{
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Linq;
/// <summary>
/// Modification function mapping translators are defined per extent (entity set
/// or association set) and manage the creation of function commands.
/// </summary>
internal abstract class ModificationFunctionMappingTranslator
{
/// <summary>
/// Requires: this translator must be registered to handle the entity set
/// for the given state entry.
///
/// Translates the given state entry to a command.
/// </summary>
/// <param name="translator">Parent update translator (global state for the workload)</param>
/// <param name="stateEntry">State entry to translate. Must belong to the
/// entity/association set handled by this translator</param>
/// <returns>Command corresponding to the given state entry</returns>
internal abstract FunctionUpdateCommand Translate(
UpdateTranslator translator,
ExtractedStateEntry stateEntry);
/// <summary>
/// Initialize a translator for the given entity set mapping.
/// </summary>
/// <param name="setMapping">Entity set mapping.</param>
/// <returns>Translator.</returns>
internal static ModificationFunctionMappingTranslator CreateEntitySetTranslator(
StorageEntitySetMapping setMapping)
{
return new EntitySetTranslator(setMapping);
}
/// <summary>
/// Initialize a translator for the given association set mapping.
/// </summary>
/// <param name="setMapping">Association set mapping.</param>
/// <returns>Translator.</returns>
internal static ModificationFunctionMappingTranslator CreateAssociationSetTranslator(
StorageAssociationSetMapping setMapping)
{
return new AssociationSetTranslator(setMapping);
}
private sealed class EntitySetTranslator : ModificationFunctionMappingTranslator
{
private readonly Dictionary<EntityType, StorageEntityTypeModificationFunctionMapping> m_typeMappings;
internal EntitySetTranslator(StorageEntitySetMapping setMapping)
{
Debug.Assert(null != setMapping && null != setMapping.ModificationFunctionMappings &&
0 < setMapping.ModificationFunctionMappings.Count, "set mapping must exist and must specify function mappings");
m_typeMappings = new Dictionary<EntityType, StorageEntityTypeModificationFunctionMapping>();
foreach (StorageEntityTypeModificationFunctionMapping typeMapping in setMapping.ModificationFunctionMappings)
{
m_typeMappings.Add(typeMapping.EntityType, typeMapping);
}
}
internal override FunctionUpdateCommand Translate(
UpdateTranslator translator,
ExtractedStateEntry stateEntry)
{
var mapping = GetFunctionMapping(stateEntry);
StorageEntityTypeModificationFunctionMapping typeMapping = mapping.Item1;
StorageModificationFunctionMapping functionMapping = mapping.Item2;
EntityKey entityKey = stateEntry.Source.EntityKey;
var stateEntries = new HashSet<IEntityStateEntry> { stateEntry.Source };
// gather all referenced association ends
var collocatedEntries =
// find all related entries corresponding to collocated association types
from end in functionMapping.CollocatedAssociationSetEnds
join candidateEntry in translator.GetRelationships(entityKey)
on end.CorrespondingAssociationEndMember.DeclaringType equals candidateEntry.EntitySet.ElementType
select Tuple.Create(end.CorrespondingAssociationEndMember, candidateEntry);
var currentReferenceEnds = new Dictionary<AssociationEndMember, IEntityStateEntry>();
var originalReferenceEnds = new Dictionary<AssociationEndMember, IEntityStateEntry>();
foreach (var candidate in collocatedEntries)
{
ProcessReferenceCandidate(entityKey, stateEntries, currentReferenceEnds, originalReferenceEnds, candidate.Item1, candidate.Item2);
}
// create function object
FunctionUpdateCommand command;
// consider the following scenario, we need to loop through all the state entries that is correlated with entity2 and make sure it is not changed.
// entity1 <-- Independent Association <-- entity2 <-- Fk association <-- entity 3
// |
// entity4 <-- Fk association <--
if (stateEntries.All(e => e.State == EntityState.Unchanged))
{
// we shouldn't update the entity if it is unchanged, only update when referenced association is changed.
// if not, then this will trigger a fake update for principal end as describe in bug 894569.
command = null;
}
else
{
command = new FunctionUpdateCommand(functionMapping, translator, stateEntries.ToList().AsReadOnly(), stateEntry);
// bind all function parameters
BindFunctionParameters(translator, stateEntry, functionMapping, command, currentReferenceEnds, originalReferenceEnds);
// interpret all result bindings
if (null != functionMapping.ResultBindings)
{
foreach (StorageModificationFunctionResultBinding resultBinding in functionMapping.ResultBindings)
{
PropagatorResult result = stateEntry.Current.GetMemberValue(resultBinding.Property);
command.AddResultColumn(translator, resultBinding.ColumnName, result);
}
}
}
return command;
}
private static void ProcessReferenceCandidate(
EntityKey source,
HashSet<IEntityStateEntry> stateEntries,
Dictionary<AssociationEndMember, IEntityStateEntry> currentReferenceEnd,
Dictionary<AssociationEndMember, IEntityStateEntry> originalReferenceEnd,
AssociationEndMember endMember,
IEntityStateEntry candidateEntry)
{
Func<DbDataRecord, int, EntityKey> getEntityKey = (record, ordinal) => (EntityKey)record[ordinal];
Action<DbDataRecord, Action<IEntityStateEntry>> findMatch = (record, registerTarget) =>
{
// find the end corresponding to the 'to' end
int toOrdinal = record.GetOrdinal(endMember.Name);
Debug.Assert(-1 != toOrdinal, "to end of relationship doesn't exist in record");
// the 'from' end must be the other end
int fromOrdinal = 0 == toOrdinal ? 1 : 0;
if (getEntityKey(record, fromOrdinal) == source)
{
stateEntries.Add(candidateEntry);
registerTarget(candidateEntry);
}
};
switch (candidateEntry.State)
{
case EntityState.Unchanged:
findMatch(
candidateEntry.CurrentValues,
(target) =>
{
currentReferenceEnd.Add(endMember, target);
originalReferenceEnd.Add(endMember, target);
});
break;
case EntityState.Added:
findMatch(
candidateEntry.CurrentValues,
(target) => currentReferenceEnd.Add(endMember, target));
break;
case EntityState.Deleted:
findMatch(
candidateEntry.OriginalValues,
(target) => originalReferenceEnd.Add(endMember, target));
break;
default:
break;
}
}
private Tuple<StorageEntityTypeModificationFunctionMapping, StorageModificationFunctionMapping> GetFunctionMapping(ExtractedStateEntry stateEntry)
{
// choose mapping based on type and operation
StorageModificationFunctionMapping functionMapping;
EntityType entityType;
if (null != stateEntry.Current)
{
entityType = (EntityType)stateEntry.Current.StructuralType;
}
else
{
entityType = (EntityType)stateEntry.Original.StructuralType;
}
StorageEntityTypeModificationFunctionMapping typeMapping = m_typeMappings[entityType];
switch (stateEntry.State)
{
case EntityState.Added:
functionMapping = typeMapping.InsertFunctionMapping;
EntityUtil.ValidateNecessaryModificationFunctionMapping(functionMapping, "Insert", stateEntry.Source, "EntityType", entityType.Name);
break;
case EntityState.Deleted:
functionMapping = typeMapping.DeleteFunctionMapping;
EntityUtil.ValidateNecessaryModificationFunctionMapping(functionMapping, "Delete", stateEntry.Source, "EntityType", entityType.Name);
break;
case EntityState.Unchanged:
case EntityState.Modified:
functionMapping = typeMapping.UpdateFunctionMapping;
EntityUtil.ValidateNecessaryModificationFunctionMapping(functionMapping, "Update", stateEntry.Source, "EntityType", entityType.Name);
break;
default:
functionMapping = null;
Debug.Fail("unexpected state");
break;
}
return Tuple.Create(typeMapping, functionMapping);
}
// Walks through all parameter bindings in the function mapping and binds the parameters to the
// requested properties of the given state entry.
private void BindFunctionParameters(UpdateTranslator translator, ExtractedStateEntry stateEntry, StorageModificationFunctionMapping functionMapping, FunctionUpdateCommand command, Dictionary<AssociationEndMember, IEntityStateEntry> currentReferenceEnds, Dictionary<AssociationEndMember, IEntityStateEntry> originalReferenceEnds)
{
// bind all parameters
foreach (StorageModificationFunctionParameterBinding parameterBinding in functionMapping.ParameterBindings)
{
PropagatorResult result;
// extract value
if (null != parameterBinding.MemberPath.AssociationSetEnd)
{
// find the relationship entry corresponding to the navigation
AssociationEndMember endMember = parameterBinding.MemberPath.AssociationSetEnd.CorrespondingAssociationEndMember;
IEntityStateEntry relationshipEntry;
bool hasTarget = parameterBinding.IsCurrent
? currentReferenceEnds.TryGetValue(endMember, out relationshipEntry)
: originalReferenceEnds.TryGetValue(endMember, out relationshipEntry);
if (!hasTarget)
{
if (endMember.RelationshipMultiplicity == RelationshipMultiplicity.One)
{
string entitySetName = stateEntry.Source.EntitySet.Name;
string associationSetName = parameterBinding.MemberPath.AssociationSetEnd.ParentAssociationSet.Name;
throw EntityUtil.Update(Strings.Update_MissingRequiredRelationshipValue(entitySetName, associationSetName),
null,
command.GetStateEntries(translator));
}
else
{
result = PropagatorResult.CreateSimpleValue(PropagatorFlags.NoFlags, null);
}
}
else
{
// get the actual value
PropagatorResult relationshipResult = parameterBinding.IsCurrent ?
translator.RecordConverter.ConvertCurrentValuesToPropagatorResult(relationshipEntry, ModifiedPropertiesBehavior.AllModified) :
translator.RecordConverter.ConvertOriginalValuesToPropagatorResult(relationshipEntry, ModifiedPropertiesBehavior.AllModified);
PropagatorResult endResult = relationshipResult.GetMemberValue(endMember);
EdmProperty keyProperty = (EdmProperty)parameterBinding.MemberPath.Members[0];
result = endResult.GetMemberValue(keyProperty);
}
}
else
{
// walk through the member path to find the appropriate propagator results
result = parameterBinding.IsCurrent ? stateEntry.Current : stateEntry.Original;
for (int i = parameterBinding.MemberPath.Members.Count; i > 0;)
{
--i;
EdmMember member = parameterBinding.MemberPath.Members[i];
result = result.GetMemberValue(member);
}
}
// create DbParameter
command.SetParameterValue(result, parameterBinding, translator);
}
// Add rows affected parameter
command.RegisterRowsAffectedParameter(functionMapping.RowsAffectedParameter);
}
}
private sealed class AssociationSetTranslator : ModificationFunctionMappingTranslator
{
// If this value is null, it indicates that the association set is
// only implicitly mapped as part of an entity set
private readonly StorageAssociationSetModificationFunctionMapping m_mapping;
internal AssociationSetTranslator(StorageAssociationSetMapping setMapping)
{
if (null != setMapping)
{
m_mapping = setMapping.ModificationFunctionMapping;
}
}
internal override FunctionUpdateCommand Translate(
UpdateTranslator translator,
ExtractedStateEntry stateEntry)
{
if (null == m_mapping) { return null; }
bool isInsert = EntityState.Added == stateEntry.State;
EntityUtil.ValidateNecessaryModificationFunctionMapping(
isInsert ? m_mapping.InsertFunctionMapping : m_mapping.DeleteFunctionMapping,
isInsert ? "Insert" : "Delete",
stateEntry.Source, "AssociationSet", m_mapping.AssociationSet.Name);
// initialize a new command
StorageModificationFunctionMapping functionMapping = isInsert ? m_mapping.InsertFunctionMapping : m_mapping.DeleteFunctionMapping;
FunctionUpdateCommand command = new FunctionUpdateCommand(functionMapping, translator, new [] { stateEntry.Source }.ToList().AsReadOnly(), stateEntry);
// extract the relationship values from the state entry
PropagatorResult recordResult;
if (isInsert)
{
recordResult = stateEntry.Current;
}
else
{
recordResult = stateEntry.Original;
}
// bind parameters
foreach (StorageModificationFunctionParameterBinding parameterBinding in functionMapping.ParameterBindings)
{
// extract the relationship information
Debug.Assert(2 == parameterBinding.MemberPath.Members.Count, "relationship parameter binding member " +
"path should include the relationship end and key property only");
EdmProperty keyProperty = (EdmProperty)parameterBinding.MemberPath.Members[0];
AssociationEndMember endMember = (AssociationEndMember)parameterBinding.MemberPath.Members[1];
// get the end member
PropagatorResult endResult = recordResult.GetMemberValue(endMember);
PropagatorResult keyResult = endResult.GetMemberValue(keyProperty);
command.SetParameterValue(keyResult, parameterBinding, translator);
}
// add rows affected output parameter
command.RegisterRowsAffectedParameter(functionMapping.RowsAffectedParameter);
return command;
}
}
}
}

View File

@@ -0,0 +1,410 @@
//---------------------------------------------------------------------
// <copyright file="FunctionUpdateCommand.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @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;
/// <summary>
/// Aggregates information about a modification command delegated to a store function.
/// </summary>
internal sealed class FunctionUpdateCommand : UpdateCommand
{
#region Constructors
/// <summary>
/// Initialize a new function command. Initializes the command object.
/// </summary>
/// <param name="functionMapping">Function mapping metadata</param>
/// <param name="translator">Translator</param>
/// <param name="stateEntries">State entries handled by this operation.</param>
/// <param name="stateEntry">'Root' state entry being handled by this function.</param>
internal FunctionUpdateCommand(StorageModificationFunctionMapping functionMapping, UpdateTranslator translator,
System.Collections.ObjectModel.ReadOnlyCollection<IEntityStateEntry> 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<IEntityStateEntry> m_stateEntries;
/// <summary>
/// Gets the store command wrapped by this command.
/// </summary>
private readonly DbCommand m_dbCommand;
/// <summary>
/// Gets pairs for column names and propagator results (so that we can associate reader results with
/// the source records for server generated values).
/// </summary>
private List<KeyValuePair<string, PropagatorResult>> m_resultColumns;
/// <summary>
/// Gets map from identifiers (key component proxies) to parameters holding the actual
/// key values. Supports propagation of identifier values (fixup for server-gen keys)
/// </summary>
private List<KeyValuePair<int, DbParameter>> m_inputIdentifiers;
/// <summary>
/// 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)
/// </summary>
private Dictionary<int, string> m_outputIdentifiers;
/// <summary>
/// Gets a reference to the rows affected output parameter for the stored procedure. May be null.
/// </summary>
private DbParameter m_rowsAffectedParameter;
#endregion
#region Properties
internal override IEnumerable<int> InputIdentifiers
{
get
{
if (null == m_inputIdentifiers)
{
yield break;
}
else
{
foreach (KeyValuePair<int, DbParameter> inputIdentifier in m_inputIdentifiers)
{
yield return inputIdentifier.Key;
}
}
}
}
internal override IEnumerable<int> OutputIdentifiers
{
get
{
if (null == m_outputIdentifiers)
{
return Enumerable.Empty<int>();
}
return m_outputIdentifiers.Keys;
}
}
internal override UpdateCommandKind Kind
{
get { return UpdateCommandKind.Function; }
}
#endregion
#region Methods
/// <summary>
/// Gets state entries contributing to this function. Supports error reporting.
/// </summary>
internal override IList<IEntityStateEntry> 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<KeyValuePair<int, DbParameter>>(initialSize);
}
foreach (int principal in translator.KeyManager.GetPrincipals(identifier))
{
m_inputIdentifiers.Add(new KeyValuePair<int, DbParameter>(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<KeyValuePair<string, PropagatorResult>>(initializeSize);
}
m_resultColumns.Add(new KeyValuePair<string, PropagatorResult>(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<int, string>(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<int, object> identifierValues,
List<KeyValuePair<PropagatorResult, object>> 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<int, DbParameter> 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<EdmMember> 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<int, PropagatorResult>(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<PropagatorResult, object>(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;
}
/// <summary>
/// Gets modification operator corresponding to the given entity state.
/// </summary>
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
}
}

View File

@@ -0,0 +1,237 @@
//---------------------------------------------------------------------
// <copyright file="Graph.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Common.Utils;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Globalization;
using System.Linq;
namespace System.Data.Mapping.Update.Internal
{
/// <summary>
/// A directed graph class.
/// </summary>
/// <remarks>
/// Notes on language (in case you're familiar with one or the other convention):
///
/// node == vertex
/// arc == edge
/// predecessor == incoming
/// successor == outgoing
/// </remarks>
/// <typeparam name="TVertex">Type of nodes in the graph</typeparam>
internal class Graph<TVertex>
{
#region Constructors
/// <summary>
/// Initialize a new graph
/// </summary>
/// <param name="comparer">Comparer used to determine if two node references are
/// equivalent</param>
internal Graph(IEqualityComparer<TVertex> comparer)
{
EntityUtil.CheckArgumentNull(comparer, "comparer");
m_comparer = comparer;
m_successorMap = new Dictionary<TVertex, HashSet<TVertex>>(comparer);
m_predecessorCounts = new Dictionary<TVertex, int>(comparer);
m_vertices = new HashSet<TVertex>(comparer);
}
#endregion
#region Fields
/// <summary>
/// Gets successors of the node (outgoing edges).
/// </summary>
private readonly Dictionary<TVertex, HashSet<TVertex>> m_successorMap;
/// <summary>
/// Gets number of predecessors of the node.
/// </summary>
private readonly Dictionary<TVertex, int> m_predecessorCounts;
/// <summary>
/// Gets the vertices that exist in the graph.
/// </summary>
private readonly HashSet<TVertex> m_vertices;
private readonly IEqualityComparer<TVertex> m_comparer;
#endregion
#region Properties
/// <summary>
/// Returns the vertices of the graph.
/// </summary>
internal IEnumerable<TVertex> Vertices
{
get { return m_vertices; }
}
/// <summary>
/// Returns the edges of the graph in the form: [from, to]
/// </summary>
internal IEnumerable<KeyValuePair<TVertex, TVertex>> Edges
{
get
{
foreach (KeyValuePair<TVertex, HashSet<TVertex>> successors in m_successorMap)
{
foreach (TVertex vertex in successors.Value)
{
yield return new KeyValuePair<TVertex, TVertex>(successors.Key, vertex);
}
}
}
}
#endregion
#region Methods
/// <summary>
/// Adds a new node to the graph. Does nothing if the vertex already exists.
/// </summary>
/// <param name="vertex">New node</param>
internal void AddVertex(TVertex vertex)
{
m_vertices.Add(vertex);
}
/// <summary>
/// Adds a new edge to the graph. NOTE: only adds edges for existing vertices.
/// </summary>
/// <param name="from">Source node</param>
/// <param name="to">Target node</param>
internal void AddEdge(TVertex from, TVertex to)
{
// Add only edges relevant to the current graph vertices
if (m_vertices.Contains(from) && m_vertices.Contains(to))
{
HashSet<TVertex> successors;
if (!m_successorMap.TryGetValue(from, out successors))
{
successors = new HashSet<TVertex>(m_comparer);
m_successorMap.Add(from, successors);
}
if (successors.Add(to))
{
// If the edge does not already exist, increment the count of incoming edges (predecessors).
int predecessorCount;
if (!m_predecessorCounts.TryGetValue(to, out predecessorCount))
{
predecessorCount = 1;
}
else
{
++predecessorCount;
}
m_predecessorCounts[to] = predecessorCount;
}
}
}
/// <summary>
/// DESTRUCTIVE OPERATION: performing a sort modifies the graph
/// Performs topological sort on graph. Nodes with no remaining incoming edges are removed
/// in sort order (assumes elements implement IComparable(Of TVertex))
/// </summary>
/// <returns>true if the sort succeeds; false if it fails and there is a remainder</returns>
internal bool TryTopologicalSort(out IEnumerable<TVertex> orderedVertices, out IEnumerable<TVertex> remainder)
{
// populate all predecessor-less nodes to root queue
var rootsPriorityQueue = new SortedSet<TVertex>(Comparer<TVertex>.Default);
foreach (TVertex vertex in m_vertices)
{
int predecessorCount;
if (!m_predecessorCounts.TryGetValue(vertex, out predecessorCount) || 0 == predecessorCount)
{
rootsPriorityQueue.Add(vertex);
}
}
var result = new TVertex[m_vertices.Count];
int resultCount = 0;
// perform sort
while (0 < rootsPriorityQueue.Count)
{
// get the vertex that is next in line in the secondary ordering
TVertex from = rootsPriorityQueue.Min;
rootsPriorityQueue.Remove(from);
// remove all outgoing edges (free all vertices that depend on 'from')
HashSet<TVertex> toSet;
if (m_successorMap.TryGetValue(from, out toSet))
{
foreach (TVertex to in toSet)
{
int predecessorCount = m_predecessorCounts[to] - 1;
m_predecessorCounts[to] = predecessorCount;
if (predecessorCount == 0)
{
// 'to' contains no incoming edges, so it is now a root
rootsPriorityQueue.Add(to);
}
}
// remove the entire successor set since it has been emptied
m_successorMap.Remove(from);
}
// add the freed vertex to the result and remove it from the graph
result[resultCount++] = from;
m_vertices.Remove(from);
}
// check that all elements were yielded
if (m_vertices.Count == 0)
{
// all vertices were ordered
orderedVertices = result;
remainder = Enumerable.Empty<TVertex>();
return true;
}
else
{
orderedVertices = result.Take(resultCount);
remainder = m_vertices;
return false;
}
}
/// <summary>
/// For debugging purposes.
/// </summary>
/// <returns></returns>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
foreach (KeyValuePair<TVertex, HashSet<TVertex>> outgoingEdge in m_successorMap)
{
bool first = true;
sb.AppendFormat(CultureInfo.InvariantCulture, "[{0}] --> ", outgoingEdge.Key);
foreach (TVertex vertex in outgoingEdge.Value)
{
if (first) { first = false; }
else { sb.Append(", "); }
sb.AppendFormat(CultureInfo.InvariantCulture, "[{0}]", vertex);
}
sb.Append("; ");
}
return sb.ToString();
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,260 @@
//---------------------------------------------------------------------
// <copyright file="Propagator.PlaceholderVisitor.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
namespace System.Data.Mapping.Update.Internal
{
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Common.CommandTrees;
using System.Data.Metadata.Edm;
using System.Data.Spatial;
using System.Diagnostics;
internal partial class Propagator
{
/// <summary>
/// Class generating default records for extents. Has a single external entry point, the
/// <see cref="CreatePlaceholder" /> static method.
/// </summary>
private class ExtentPlaceholderCreator
{
#region Constructors
/// <summary>
/// Constructs a new placeholder creator.
/// </summary>
/// <param name="parent">Context used to generate all elements of the placeholder.</param>
private ExtentPlaceholderCreator(UpdateTranslator parent)
{
EntityUtil.CheckArgumentNull(parent, "parent");
m_parent = parent;
}
#endregion
#region Fields
static private Dictionary<PrimitiveTypeKind, object> s_typeDefaultMap = InitializeTypeDefaultMap();
private UpdateTranslator m_parent;
#endregion
#region Methods
/// <summary>
/// Initializes a map from primitive scalar types in the C-Space to default values
/// used within the placeholder.
/// </summary>
private static Dictionary<PrimitiveTypeKind, object> InitializeTypeDefaultMap()
{
Dictionary<PrimitiveTypeKind, object> typeDefaultMap = new Dictionary<PrimitiveTypeKind, object>(
EqualityComparer<PrimitiveTypeKind>.Default);
// Use CLR defaults for value types, arbitrary constants for reference types
// (since these default to null)
typeDefaultMap[PrimitiveTypeKind.Binary] = new Byte[0];
typeDefaultMap[PrimitiveTypeKind.Boolean] = default(Boolean);
typeDefaultMap[PrimitiveTypeKind.Byte] = default(Byte);
typeDefaultMap[PrimitiveTypeKind.DateTime] = default(DateTime);
typeDefaultMap[PrimitiveTypeKind.Time] = default(TimeSpan);
typeDefaultMap[PrimitiveTypeKind.DateTimeOffset] = default(DateTimeOffset);
typeDefaultMap[PrimitiveTypeKind.Decimal] = default(Decimal);
typeDefaultMap[PrimitiveTypeKind.Double] = default(Double);
typeDefaultMap[PrimitiveTypeKind.Guid] = default(Guid);
typeDefaultMap[PrimitiveTypeKind.Int16] = default(Int16);
typeDefaultMap[PrimitiveTypeKind.Int32] = default(Int32);
typeDefaultMap[PrimitiveTypeKind.Int64] = default(Int64);
typeDefaultMap[PrimitiveTypeKind.Single] = default(Single);
typeDefaultMap[PrimitiveTypeKind.SByte] = default(SByte);
typeDefaultMap[PrimitiveTypeKind.String] = String.Empty;
typeDefaultMap[PrimitiveTypeKind.Geometry] = DbGeometry.FromText("POINT EMPTY");
typeDefaultMap[PrimitiveTypeKind.GeometryPoint] = DbGeometry.FromText("POINT EMPTY");
typeDefaultMap[PrimitiveTypeKind.GeometryLineString] = DbGeometry.FromText("LINESTRING EMPTY");
typeDefaultMap[PrimitiveTypeKind.GeometryPolygon] = DbGeometry.FromText("POLYGON EMPTY");
typeDefaultMap[PrimitiveTypeKind.GeometryMultiPoint] = DbGeometry.FromText("MULTIPOINT EMPTY");
typeDefaultMap[PrimitiveTypeKind.GeometryMultiLineString] = DbGeometry.FromText("MULTILINESTRING EMPTY");
typeDefaultMap[PrimitiveTypeKind.GeometryMultiPolygon] = DbGeometry.FromText("MULTIPOLYGON EMPTY");
typeDefaultMap[PrimitiveTypeKind.GeometryCollection] = DbGeometry.FromText("GEOMETRYCOLLECTION EMPTY");
typeDefaultMap[PrimitiveTypeKind.Geography] = DbGeography.FromText("POINT EMPTY");
typeDefaultMap[PrimitiveTypeKind.GeographyPoint] = DbGeography.FromText("POINT EMPTY");
typeDefaultMap[PrimitiveTypeKind.GeographyLineString] = DbGeography.FromText("LINESTRING EMPTY");
typeDefaultMap[PrimitiveTypeKind.GeographyPolygon] = DbGeography.FromText("POLYGON EMPTY");
typeDefaultMap[PrimitiveTypeKind.GeographyMultiPoint] = DbGeography.FromText("MULTIPOINT EMPTY");
typeDefaultMap[PrimitiveTypeKind.GeographyMultiLineString] = DbGeography.FromText("MULTILINESTRING EMPTY");
typeDefaultMap[PrimitiveTypeKind.GeographyMultiPolygon] = DbGeography.FromText("MULTIPOLYGON EMPTY");
typeDefaultMap[PrimitiveTypeKind.GeographyCollection] = DbGeography.FromText("GEOMETRYCOLLECTION EMPTY");
#if DEBUG
foreach (object o in typeDefaultMap.Values)
{
Debug.Assert(null != o, "DbConstantExpression instances do not support null values");
}
#endif
return typeDefaultMap;
}
/// <summary>
/// Creates a record for an extent containing default values. Assumes the extent is either
/// a relationship set or an entity set.
/// </summary>
/// <remarks>
/// Each scalar value appearing in the record is a <see cref="DbConstantExpression" />. A placeholder is created by recursively
/// building a record, so an entity record type will return a new record (<see cref="DbNewInstanceExpression" />)
/// consisting of some recursively built record for each column in the type.
/// </remarks>
/// <param name="extent">Extent</param>
/// <param name="parent">Command tree used to generate portions of the record</param>
/// <returns>A default record for the </returns>
internal static PropagatorResult CreatePlaceholder(EntitySetBase extent, UpdateTranslator parent)
{
EntityUtil.CheckArgumentNull(extent, "extent");
ExtentPlaceholderCreator creator = new ExtentPlaceholderCreator(parent);
AssociationSet associationSet = extent as AssociationSet;
if (null != associationSet)
{
return creator.CreateAssociationSetPlaceholder(associationSet);
}
EntitySet entitySet = extent as EntitySet;
if (null != entitySet)
{
return creator.CreateEntitySetPlaceholder(entitySet);
}
throw EntityUtil.NotSupported(System.Data.Entity.Strings.Update_UnsupportedExtentType(
extent.Name, extent.GetType().Name));
}
/// <summary>
/// Specialization of <see cref="CreatePlaceholder" /> for an entity set extent.
/// </summary>
/// <param name="entitySet"></param>
/// <returns></returns>
private PropagatorResult CreateEntitySetPlaceholder(EntitySet entitySet)
{
EntityUtil.CheckArgumentNull(entitySet, "entitySet");
ReadOnlyMetadataCollection<EdmProperty> members = entitySet.ElementType.Properties;
PropagatorResult[] memberValues = new PropagatorResult[members.Count];
for (int ordinal = 0; ordinal < members.Count; ordinal++)
{
PropagatorResult memberValue = CreateMemberPlaceholder(members[ordinal]);
memberValues[ordinal] = memberValue;
}
PropagatorResult result = PropagatorResult.CreateStructuralValue(memberValues, entitySet.ElementType, false);
return result;
}
/// <summary>
/// Specialization of <see cref="CreatePlaceholder" /> for a relationship set extent.
/// </summary>
/// <param name="associationSet"></param>
/// <returns></returns>
private PropagatorResult CreateAssociationSetPlaceholder(AssociationSet associationSet)
{
Debug.Assert(null != associationSet, "Caller must verify parameters are not null");
var endMetadata = associationSet.ElementType.AssociationEndMembers;
PropagatorResult[] endReferenceValues = new PropagatorResult[endMetadata.Count];
// Create a reference expression for each end in the relationship
for (int endOrdinal = 0; endOrdinal < endMetadata.Count; endOrdinal++)
{
var end = endMetadata[endOrdinal];
EntityType entityType = (EntityType)((RefType)end.TypeUsage.EdmType).ElementType;
// Retrieve key values for this end
PropagatorResult[] keyValues = new PropagatorResult[entityType.KeyMembers.Count];
for (int memberOrdinal = 0; memberOrdinal < entityType.KeyMembers.Count; memberOrdinal++)
{
EdmMember keyMember = entityType.KeyMembers[memberOrdinal];
PropagatorResult keyValue = CreateMemberPlaceholder(keyMember);
keyValues[memberOrdinal] = keyValue;
}
RowType endType = entityType.GetKeyRowType(m_parent.MetadataWorkspace);
PropagatorResult refKeys = PropagatorResult.CreateStructuralValue(keyValues, endType, false);
endReferenceValues[endOrdinal] = refKeys;
}
PropagatorResult result = PropagatorResult.CreateStructuralValue(endReferenceValues, associationSet.ElementType, false);
return result;
}
/// <summary>
/// Returns a placeholder for a specific metadata member.
/// </summary>
/// <param name="member">EdmMember for which to produce a placeholder.</param>
/// <returns>Placeholder element for the given member.</returns>
private PropagatorResult CreateMemberPlaceholder(EdmMember member)
{
EntityUtil.CheckArgumentNull(member, "member");
return Visit(member);
}
#region Visitor implementation
/// <summary>
/// Given default values for children members, produces a new default expression for the requested (parent) member.
/// </summary>
/// <param name="node">Parent member</param>
/// <returns>Default value for parent member</returns>
internal PropagatorResult Visit(EdmMember node)
{
PropagatorResult result;
TypeUsage nodeType = Helper.GetModelTypeUsage(node);
if (Helper.IsScalarType(nodeType.EdmType))
{
GetPropagatorResultForPrimitiveType(Helper.AsPrimitive(nodeType.EdmType), out result);
}
else
{
// Construct a new 'complex type' (really any structural type) member.
StructuralType structuralType = (StructuralType)nodeType.EdmType;
IBaseList<EdmMember> members = TypeHelpers.GetAllStructuralMembers(structuralType);
PropagatorResult[] args = new PropagatorResult[members.Count];
for (int ordinal = 0; ordinal < members.Count; ordinal++)
// foreach (EdmMember member in members)
{
args[ordinal] = Visit(members[ordinal]);
}
result = PropagatorResult.CreateStructuralValue(args, structuralType, false);
}
return result;
}
// Find "sanctioned" default value
private static void GetPropagatorResultForPrimitiveType(PrimitiveType primitiveType, out PropagatorResult result)
{
object value;
PrimitiveTypeKind primitiveTypeKind = primitiveType.PrimitiveTypeKind;
if (!s_typeDefaultMap.TryGetValue(primitiveTypeKind, out value))
{
// If none exists, default to lowest common denominator for constants
value = default(byte);
}
// Return a new constant expression flagged as unknown since the value is only there for
// show. (Not entirely for show, because null constraints may require a value for a record,
// whether that record is a placeholder or not).
result = PropagatorResult.CreateSimpleValue(PropagatorFlags.NoFlags, value);
}
#endregion
#endregion
}
}
}

View File

@@ -0,0 +1,140 @@
//---------------------------------------------------------------------
// <copyright file="Propagator.JoinPropagator.JoinPredicateVisitor.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Common.CommandTrees;
using System.Collections.Generic;
using System.Diagnostics;
using System.Collections.ObjectModel;
namespace System.Data.Mapping.Update.Internal
{
internal partial class Propagator
{
private partial class JoinPropagator
{
/// <summary>
/// Extracts equi-join properties from a join condition.
/// </summary>
/// <remarks>
/// Assumptions:
/// <list>
/// <item>Only conjunctions of equality predicates are supported</item>
/// <item>Each equality predicate is of the form (left property == right property). The order
/// is important.</item>
/// </list>
/// </remarks>
private class JoinConditionVisitor : UpdateExpressionVisitor<object>
{
#region Constructors
/// <summary>
/// Initializes a join predicate visitor. The visitor will populate the given property
/// lists with expressions describing the left and right hand side of equi-join
/// sub-clauses.
/// </summary>
private JoinConditionVisitor()
{
m_leftKeySelectors = new List<DbExpression>();
m_rightKeySelectors = new List<DbExpression>();
}
#endregion
#region Fields
private readonly List<DbExpression> m_leftKeySelectors;
private readonly List<DbExpression> m_rightKeySelectors;
private static readonly string s_visitorName = typeof(JoinConditionVisitor).FullName;
#endregion
#region Properties
override protected string VisitorName
{
get { return s_visitorName; }
}
#endregion
#region Methods
#region Static helper methods
/// <summary>
/// Determine properties from the left and right inputs to an equi-join participating
/// in predicate.
/// </summary>
/// <remarks>
/// The property definitions returned are 'aligned'. If the join predicate reads:
/// <code>
/// a = b AND c = d AND e = f
/// </code>
/// then the output is as follows:
/// <code>
/// leftProperties = {a, c, e}
/// rightProperties = {b, d, f}
/// </code>
/// See Walker class for an explanation of this coding pattern.
/// </remarks>
static internal void GetKeySelectors(DbExpression joinCondition, out ReadOnlyCollection<DbExpression> leftKeySelectors, out ReadOnlyCollection<DbExpression> rightKeySelectors)
{
EntityUtil.CheckArgumentNull(joinCondition, "joinCondition");
// Constructs a new predicate visitor, which implements a visitor for expression nodes
// and returns no values. This visitor instead builds up a list of properties as leaves
// of the join predicate are visited.
JoinConditionVisitor visitor = new JoinConditionVisitor();
// Walk the predicate using the predicate visitor.
joinCondition.Accept(visitor);
// Retrieve properties discovered visiting predicate leaf nodes.
leftKeySelectors = visitor.m_leftKeySelectors.AsReadOnly();
rightKeySelectors = visitor.m_rightKeySelectors.AsReadOnly();
Debug.Assert(leftKeySelectors.Count == rightKeySelectors.Count,
"(Update/JoinPropagator) The equi-join must have an equal number of left and right properties");
}
#endregion
#region Visitor implementation
/// <summary>
/// Visit and node after its children have visited. There is nothing to do here
/// because only leaf equality nodes contain properties extracted by this visitor.
/// </summary>
/// <param name="node">And expression node</param>
/// <returns>Results ignored by this visitor implementation.</returns>
public override object Visit(DbAndExpression node)
{
EntityUtil.CheckArgumentNull(node, "node");
Visit(node.Left);
Visit(node.Right);
return null;
}
/// <summary>
/// Perform work for an equality expression node.
/// </summary>
/// <param name="node">Equality expresion node</param>
/// <returns>Results ignored by this visitor implementation.</returns>
public override object Visit(DbComparisonExpression node)
{
EntityUtil.CheckArgumentNull(node, "node");
if (DbExpressionKind.Equals == node.ExpressionKind)
{
m_leftKeySelectors.Add(node.Left);
m_rightKeySelectors.Add(node.Right);
return null;
}
else
{
throw ConstructNotSupportedException(node);
}
}
#endregion
#endregion
}
}
}
}

View File

@@ -0,0 +1,114 @@
//---------------------------------------------------------------------
// <copyright file="Propagator.JoinPropagator.SubstitutingCloneVisitor.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Collections.Generic;
using System.Data.Common;
namespace System.Data.Mapping.Update.Internal
{
internal partial class Propagator
{
private partial class JoinPropagator
{
/// <summary>
/// Describes the mode of behavior for the <see cref="PlaceholderPopulator"/>.
/// </summary>
private enum PopulateMode
{
/// <summary>
/// Produce a null extension record (for outer joins) marked as modified
/// </summary>
NullModified,
/// <summary>
/// Produce a null extension record (for outer joins) marked as preserve
/// </summary>
NullPreserve,
/// <summary>
/// Produce a placeholder for a record that is known to exist but whose specific
/// values are unknown.
/// </summary>
Unknown,
}
/// <summary>
/// Fills in a placeholder with join key data (also performs a clone so that the
/// placeholder can be reused).
/// </summary>
/// <remarks>
/// Clones of placeholder nodes are created when either the structure of the node
/// needs to change or the record markup for the node needs to change.
/// </remarks>
private static class PlaceholderPopulator
{
#region Methods
/// <summary>
/// Construct a new placeholder with the shape of the given placeholder. Key values are
/// injected into the resulting place holder and default values are substituted with
/// either propagator constants or progagator nulls depending on the mode established
/// by the <paramref name="mode"/> flag.
/// </summary>
/// <remarks>
/// The key is essentially an array of values. The key map indicates that for a particular
/// placeholder an expression (keyMap.Keys) corresponds to some ordinal in the key array.
/// </remarks>
/// <param name="placeholder">Placeholder to clone</param>
/// <param name="key">Key to substitute</param>
/// <param name="placeholderKey">Key elements in the placeholder (ordinally aligned with 'key')</param>
/// <param name="mode">Mode of operation.</param>
/// <param name="translator">Translator context.</param>
/// <returns>Cloned placeholder with key values</returns>
internal static PropagatorResult Populate(PropagatorResult placeholder, CompositeKey key,
CompositeKey placeholderKey, PopulateMode mode, UpdateTranslator translator)
{
EntityUtil.CheckArgumentNull(placeholder, "placeholder");
EntityUtil.CheckArgumentNull(key, "key");
EntityUtil.CheckArgumentNull(placeholderKey, "placeholderKey");
EntityUtil.CheckArgumentNull(translator, "translator");
// Figure out which flags to apply to generated elements.
bool isNull = mode == PopulateMode.NullModified || mode == PopulateMode.NullPreserve;
bool preserve = mode == PopulateMode.NullPreserve || mode == PopulateMode.Unknown;
PropagatorFlags flags = PropagatorFlags.NoFlags;
if (!isNull) { flags |= PropagatorFlags.Unknown; } // only null values are known
if (preserve) { flags |= PropagatorFlags.Preserve; }
PropagatorResult result = placeholder.Replace(node =>
{
// See if this is a key element
int keyIndex = -1;
for (int i = 0; i < placeholderKey.KeyComponents.Length; i++)
{
if (placeholderKey.KeyComponents[i] == node)
{
keyIndex = i;
break;
}
}
if (keyIndex != -1)
{
// Key value.
return key.KeyComponents[keyIndex];
}
else
{
// for simple entries, just return using the markup context for this
// populator
object value = isNull ? null : node.GetSimpleValue();
return PropagatorResult.CreateSimpleValue(flags, value);
}
});
return result;
}
#endregion
}
}
}
}

View File

@@ -0,0 +1,336 @@
//---------------------------------------------------------------------
// <copyright file="Propagator.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
namespace System.Data.Mapping.Update.Internal
{
using System.Data.Common.CommandTrees;
using System.Data.Common.Utils;
using System.Data.Metadata.Edm;
using System.Diagnostics;
/// <summary>
/// <para>
/// Comments assume there is a map between the CDM and store. Other maps are possible, but
/// for simplicity, we discuss the 'from' portion of the map as the C-Space and the 'to' portion
/// of the map as the S-Space.
/// </para>
/// <para>
/// This class translates C-Space change requests into S-Space change requests given a C-Space change
/// request, an update view loader, and a target table. It has precisely one entry
/// point, the static <see cref="Propagate"/> method. It performs the translation by evaluating an update
/// mapping view w.r.t. change requests (propagating a change request through the view).
/// </para>
/// </summary>
/// <remarks>
/// <para>
/// This class implements propagation rules for the following relational operators in the update mapping
/// view:
/// </para>
/// <list>
/// <item>Projection</item>
/// <item>Selection (filter)</item>
/// <item>Union all</item>
/// <item>Inner equijoin</item>
/// <item>Left outer equijoin</item>
/// </list>
/// </remarks>
internal partial class Propagator : UpdateExpressionVisitor<ChangeNode>
{
#region Constructors
/// <summary>
/// Construct a new propagator.
/// </summary>
/// <param name="parent">UpdateTranslator supporting retrieval of changes for C-Space
/// extents referenced in the update mapping view.</param>
/// <param name="table">Table for which updates are being produced.</param>
private Propagator(UpdateTranslator parent, EntitySet table)
{
// Initialize propagator state.
EntityUtil.CheckArgumentNull(parent, "parent");
EntityUtil.CheckArgumentNull(table, "table");
m_updateTranslator = parent;
m_table = table;
}
#endregion
#region Fields
private readonly UpdateTranslator m_updateTranslator;
private readonly EntitySet m_table;
private static readonly string s_visitorName = typeof(Propagator).FullName;
#endregion
#region Properties
/// <summary>
/// Gets context for updates performed by this propagator.
/// </summary>
internal UpdateTranslator UpdateTranslator
{
get { return m_updateTranslator; }
}
override protected string VisitorName
{
get { return s_visitorName; }
}
#endregion
#region Methods
/// <summary>
/// Propagate changes from C-Space (contained in <paramref name="parent" /> to the S-Space.
/// </summary>
/// <remarks>
/// See Walker class for an explanation of this coding pattern.
/// </remarks>
/// <param name="parent">Grouper supporting retrieval of changes for C-Space
/// extents referenced in the update mapping view.</param>
/// <param name="table">Table for which updates are being produced.</param>
/// <param name="umView">Update mapping view to propagate.</param>
/// <returns>Changes in S-Space.</returns>
static internal ChangeNode Propagate(UpdateTranslator parent, EntitySet table, DbQueryCommandTree umView)
{
// Construct a new instance of a propagator, which implements a visitor interface
// for expression nodes (nodes in the update mapping view) and returns changes nodes
// (seeded by C-Space extent changes returned by the grouper).
DbExpressionVisitor<ChangeNode> propagator = new Propagator(parent, table);
// Walk the update mapping view using the visitor pattern implemented in this class.
// The update mapping view describes the S-Space table we're targeting, so the result
// returned for the root of view corresponds to changes propagated to the S-Space.
return umView.Query.Accept(propagator);
}
/// <summary>
/// Utility method constructs a new empty change node.
/// </summary>
/// <param name="node">Update mapping view node associated with the change.</param>
/// <returns>Empty change node with the appropriate type for the view node.</returns>
private static ChangeNode BuildChangeNode(DbExpression node)
{
TypeUsage nodeType = node.ResultType;
TypeUsage elementType = MetadataHelper.GetElementType(nodeType);
return new ChangeNode(elementType);
}
#region Visitor implementation
public override ChangeNode Visit(DbCrossJoinExpression node)
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.Update_UnsupportedJoinType(node.ExpressionKind));
}
/// <summary>
/// Propagates changes across a join expression node by implementing progation rules w.r.t. inputs
/// from the left- and right- hand sides of the join. The work is actually performed
/// by the <see cref="JoinPropagator" />.
/// </summary>
/// <param name="node">A join expression node.</param>
/// <returns>Results propagated to the given join expression node.</returns>
public override ChangeNode Visit(DbJoinExpression node)
{
EntityUtil.CheckArgumentNull(node, "node");
if (DbExpressionKind.InnerJoin != node.ExpressionKind && DbExpressionKind.LeftOuterJoin != node.ExpressionKind)
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.Update_UnsupportedJoinType(node.ExpressionKind));
}
// There are precisely two inputs to the join which we treat as the left and right children.
DbExpression leftExpr = node.Left.Expression;
DbExpression rightExpr = node.Right.Expression;
// Get the results of propagating changes to the left and right inputs to the join.
ChangeNode left = Visit(leftExpr);
ChangeNode right = Visit(rightExpr);
// Construct a new join propagator, passing in the left and right results, the actual
// join expression, and this parent propagator.
JoinPropagator evaluator = new JoinPropagator(left, right, node, this);
// Execute propagation.
ChangeNode result = evaluator.Propagate();
return result;
}
/// <summary>
/// Given the results returned for the left and right inputs to a union, propagates changes
/// through the union.
///
/// Propagation rule (U = union node, L = left input, R = right input, D(x) = deleted rows
/// in x, I(x) = inserted rows in x)
///
/// U = L union R
/// D(U) = D(L) union D(R)
/// I(U) = I(L) union I(R)
/// </summary>
/// <param name="node">Union expression node in the update mapping view.</param>
/// <returns>Result of propagating changes to this union all node.</returns>
public override ChangeNode Visit(DbUnionAllExpression node)
{
EntityUtil.CheckArgumentNull(node, "node");
// Initialize an empty change node result for the union all node
ChangeNode result = BuildChangeNode(node);
// Retrieve result of propagating changes to the left and right children.
ChangeNode left = Visit(node.Left);
ChangeNode right = Visit(node.Right);
// Implement insertion propagation rule I(U) = I(L) union I(R)
result.Inserted.AddRange(left.Inserted);
result.Inserted.AddRange(right.Inserted);
// Implement deletion progation rule D(U) = D(L) union D(R)
result.Deleted.AddRange(left.Deleted);
result.Deleted.AddRange(right.Deleted);
// The choice of side for the placeholder is arbitrary, since CQTs enforce type compatibility
// for the left and right hand sides of the union.
result.Placeholder = left.Placeholder;
return result;
}
/// <summary>
/// Propagate projection.
///
/// Propagation rule (P = projection node, S = projection input, D(x) = deleted rows in x,
/// I(x) = inserted rows in x)
///
/// P = Proj_f S
/// D(P) = Proj_f D(S)
/// I(P) = Proj_f I(S)
/// </summary>
/// <param name="node">Projection expression node.</param>
/// <returns>Result of propagating changes to the projection expression node.</returns>
public override ChangeNode Visit(DbProjectExpression node)
{
EntityUtil.CheckArgumentNull(node, "node");
// Initialize an empty change node result for the projection node.
ChangeNode result = BuildChangeNode(node);
// Retrieve result of propagating changes to the input of the projection.
ChangeNode input = Visit(node.Input.Expression);
// Implement propagation rule for insert I(P) = Proj_f I(S)
foreach(PropagatorResult row in input.Inserted)
{
result.Inserted.Add(Project(node, row, result.ElementType));
}
// Implement propagation rule for delete D(P) = Proj_f D(S)
foreach(PropagatorResult row in input.Deleted)
{
result.Deleted.Add(Project(node, row, result.ElementType));
}
// Generate a placeholder for the projection node by projecting values in the
// placeholder for the input node.
result.Placeholder = Project(node, input.Placeholder, result.ElementType);
return result;
}
/// <summary>
/// Performs projection for a single row. Evaluates each projection argument against the specified
/// row, returning a result with the specified type.
/// </summary>
/// <param name="node">Projection expression.</param>
/// <param name="row">Row to project.</param>
/// <param name="resultType">Type of the projected row.</param>
/// <returns>Projected row.</returns>
private PropagatorResult Project(DbProjectExpression node, PropagatorResult row, TypeUsage resultType)
{
EntityUtil.CheckArgumentNull(node, "node");
Debug.Assert(null != node.Projection, "CQT validates DbProjectExpression.Projection property");
DbNewInstanceExpression projection = node.Projection as DbNewInstanceExpression;
if (null == projection)
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.Update_UnsupportedProjection(node.Projection.ExpressionKind));
}
// Initialize empty structure containing space for every element of the projection.
PropagatorResult[] projectedValues = new PropagatorResult[projection.Arguments.Count];
// Extract value from the input row for every projection argument requested.
for (int ordinal = 0; ordinal < projectedValues.Length; ordinal++)
{
projectedValues[ordinal] = Evaluator.Evaluate(projection.Arguments[ordinal], row, this);
}
// Return a new row containing projected values.
PropagatorResult projectedRow = PropagatorResult.CreateStructuralValue(projectedValues, (StructuralType)resultType.EdmType, false);
return projectedRow;
}
/// <summary>
/// Propagation rule (F = filter node, S = input to filter, I(x) = rows inserted
/// into x, D(x) = rows deleted from x, Sigma_p = filter predicate)
///
/// F = Sigma_p S
/// D(F) = Sigma_p D(S)
/// I(F) = Sigma_p I(S)
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
public override ChangeNode Visit(DbFilterExpression node)
{
EntityUtil.CheckArgumentNull(node, "node");
// Initialize an empty change node for this filter node.
ChangeNode result = BuildChangeNode(node);
// Retrieve result of propagating changes to the input of the filter.
ChangeNode input = Visit(node.Input.Expression);
// Implement insert propagation rule I(F) = Sigma_p I(S)
result.Inserted.AddRange(Evaluator.Filter(node.Predicate, input.Inserted, this));
// Implement delete propagation rule D(F) = Sigma_p D(S
result.Deleted.AddRange(Evaluator.Filter(node.Predicate, input.Deleted, this));
// The placeholder for a filter node is identical to that of the input, which has an
// identical shape (type).
result.Placeholder = input.Placeholder;
return result;
}
/// <summary>
/// Handles extent expressions (these are the terminal nodes in update mapping views). This handler
/// retrieves the changes from the grouper.
/// </summary>
/// <param name="node">Extent expression node</param>
/// <returns></returns>
public override ChangeNode Visit(DbScanExpression node)
{
EntityUtil.CheckArgumentNull(node, "node");
// Gets modifications requested for this extent from the grouper.
EntitySetBase extent = node.Target;
ChangeNode extentModifications = UpdateTranslator.GetExtentModifications(extent);
if (null == extentModifications.Placeholder)
{
// Bootstrap placeholder (essentially a record for the extent populated with default values).
extentModifications.Placeholder = ExtentPlaceholderCreator.CreatePlaceholder(extent, UpdateTranslator);
}
return extentModifications;
}
#endregion
#endregion
}
}

View File

@@ -0,0 +1,47 @@
//---------------------------------------------------------------------
// <copyright file="PropagatorFlags.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
namespace System.Data.Mapping.Update.Internal
{
/// <summary>
/// Tracks roles played by a record as it propagates
/// w.r.t. an update mapping view.
/// </summary>
[Flags]
internal enum PropagatorFlags : byte
{
/// <summary>
/// No role.
/// </summary>
NoFlags = 0,
/// <summary>
/// Value is unchanged. Used only for attributes that appear in updates (in other words,
/// in both delete and insert set).
/// </summary>
Preserve = 1,
/// <summary>
/// Value is a concurrency token. Placeholder for post Beta 2 work.
/// </summary>
ConcurrencyValue = 2,
/// <summary>
/// Value is unknown. Used only for attributes that appear in updates (in other words,
/// in both delete and insert set).
/// </summary>
Unknown = 8,
/// <summary>
/// Value is a key, and therefore a concurrency value, but it is shared so it
/// only needs to be checked in a single table (in the case of entity splitting)
/// </summary>
Key = 16,
/// <summary>
/// Value is a foreign key.
/// </summary>
ForeignKey = 32,
}
}

View File

@@ -0,0 +1,97 @@
//---------------------------------------------------------------------
// <copyright file="RecordConverter.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
namespace System.Data.Mapping.Update.Internal
{
using System.Data.Entity;
/// <summary>
/// Converts records to new instance expressions. Assumes that all inputs come from a single data reader (because
/// it caches record layout). If multiple readers are used, multiple converters must be constructed in case
/// the different readers return different layouts for types.
/// </summary>
/// <remarks>
/// Conventions for modifiedProperties enumeration: null means all properties are modified, empty means none,
/// non-empty means some.
/// </remarks>
internal class RecordConverter
{
#region Constructors
/// <summary>
/// Initializes a new converter given a command tree context. Initializes a new record layout cache.
/// </summary>
/// <param name="updateTranslator">Sets <see cref="m_updateTranslator" /></param>
internal RecordConverter(UpdateTranslator updateTranslator)
{
m_updateTranslator = updateTranslator;
}
#endregion
#region Fields
/// <summary>
/// Context used to produce expressions.
/// </summary>
private UpdateTranslator m_updateTranslator;
#endregion
#region Methods
/// <summary>
/// Converts original values in a state entry to a DbNewInstanceExpression. The record must be either an entity or
/// a relationship set instance.
/// </summary>
/// <remarks>
/// This method is not thread safe.
/// </remarks>
/// <param name="stateEntry">Gets state entry this record is associated with.</param>
/// <param name="modifiedPropertiesBehavior">Indicates how to determine whether a property is modified.</param>
/// <returns>New instance expression.</returns>
internal PropagatorResult ConvertOriginalValuesToPropagatorResult(IEntityStateEntry stateEntry, ModifiedPropertiesBehavior modifiedPropertiesBehavior)
{
return ConvertStateEntryToPropagatorResult(stateEntry, useCurrentValues: false, modifiedPropertiesBehavior: modifiedPropertiesBehavior);
}
/// <summary>
/// Converts current values in a state entry to a DbNewInstanceExpression. The record must be either an entity or
/// a relationship set instance.
/// </summary>
/// <remarks>
/// This method is not thread safe.
/// </remarks>
/// <param name="stateEntry">Gets state entry this record is associated with.</param>
/// <param name="modifiedPropertiesBehavior">Indicates how to determine whether a property is modified.</param>
/// <returns>New instance expression.</returns>
internal PropagatorResult ConvertCurrentValuesToPropagatorResult(IEntityStateEntry stateEntry, ModifiedPropertiesBehavior modifiedPropertiesBehavior)
{
return ConvertStateEntryToPropagatorResult(stateEntry, useCurrentValues: true, modifiedPropertiesBehavior: modifiedPropertiesBehavior);
}
private PropagatorResult ConvertStateEntryToPropagatorResult(IEntityStateEntry stateEntry, bool useCurrentValues, ModifiedPropertiesBehavior modifiedPropertiesBehavior)
{
try
{
EntityUtil.CheckArgumentNull(stateEntry, "stateEntry");
IExtendedDataRecord record = useCurrentValues
? EntityUtil.CheckArgumentNull(stateEntry.CurrentValues as IExtendedDataRecord, "stateEntry.CurrentValues")
: EntityUtil.CheckArgumentNull(stateEntry.OriginalValues as IExtendedDataRecord, "stateEntry.OriginalValues");
bool isModified = false; // the root of the state entry is unchanged because the type is static
return ExtractorMetadata.ExtractResultFromRecord(stateEntry, isModified, record, useCurrentValues, m_updateTranslator, modifiedPropertiesBehavior);
}
catch (Exception e)
{
if (UpdateTranslator.RequiresContext(e))
{
throw EntityUtil.Update(Strings.Update_ErrorLoadingRecord, e, stateEntry);
}
throw;
}
}
#endregion
}
}

View File

@@ -0,0 +1,489 @@
//---------------------------------------------------------------------
// <copyright file="RelationshipConstraintValidator.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
namespace System.Data.Mapping.Update.Internal
{
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Common.Utils;
using System.Data.Entity;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
internal partial class UpdateTranslator
{
/// <summary>
/// Class validating relationship cardinality constraints. Only reasons about constraints that can be inferred
/// by examining change requests from the store.
/// (no attempt is made to ensure consistency of the store subsequently, since this would require pulling in all
/// values from the store).
/// </summary>
private class RelationshipConstraintValidator
{
#region Constructor
internal RelationshipConstraintValidator(UpdateTranslator updateTranslator)
{
m_existingRelationships = new Dictionary<DirectionalRelationship, DirectionalRelationship>(EqualityComparer<DirectionalRelationship>.Default);
m_impliedRelationships = new Dictionary<DirectionalRelationship, IEntityStateEntry>(EqualityComparer<DirectionalRelationship>.Default);
m_referencingRelationshipSets = new Dictionary<EntitySet, List<AssociationSet>>(EqualityComparer<EntitySet>.Default);
m_updateTranslator = updateTranslator;
}
#endregion
#region Fields
/// <summary>
/// Relationships registered in the validator.
/// </summary>
private readonly Dictionary<DirectionalRelationship, DirectionalRelationship> m_existingRelationships;
/// <summary>
/// Relationships the validator determines are required based on registered entities.
/// </summary>
private readonly Dictionary<DirectionalRelationship, IEntityStateEntry> m_impliedRelationships;
/// <summary>
/// Cache used to store relationship sets with ends bound to entity sets.
/// </summary>
private readonly Dictionary<EntitySet, List<AssociationSet>> m_referencingRelationshipSets;
/// <summary>
/// Update translator containing session context.
/// </summary>
private readonly UpdateTranslator m_updateTranslator;
#endregion
#region Methods
/// <summary>
/// Add an entity to be tracked by the validator. Requires that the input describes an entity.
/// </summary>
/// <param name="stateEntry">State entry for the entity being tracked.</param>
internal void RegisterEntity(IEntityStateEntry stateEntry)
{
EntityUtil.CheckArgumentNull(stateEntry, "stateEntry");
if (EntityState.Added == stateEntry.State || EntityState.Deleted == stateEntry.State)
{
// We only track added and deleted entities because modifications to entities do not affect
// cardinality constraints. Relationships are based on end keys, and it is not
// possible to modify key values.
Debug.Assert(null != (object)stateEntry.EntityKey, "entity state entry must have an entity key");
EntityKey entityKey = EntityUtil.CheckArgumentNull(stateEntry.EntityKey, "stateEntry.EntityKey");
EntitySet entitySet = (EntitySet)stateEntry.EntitySet;
EntityType entityType = EntityState.Added == stateEntry.State ?
GetEntityType(stateEntry.CurrentValues) :
GetEntityType(stateEntry.OriginalValues);
// figure out relationship set ends that are associated with this entity set
foreach (AssociationSet associationSet in GetReferencingAssocationSets(entitySet))
{
// describe unidirectional relationships in which the added entity is the "destination"
var ends = associationSet.AssociationSetEnds;
foreach (var fromEnd in ends)
{
foreach (var toEnd in ends)
{
// end to itself does not describe an interesting relationship subpart
if (object.ReferenceEquals(toEnd.CorrespondingAssociationEndMember,
fromEnd.CorrespondingAssociationEndMember)) { continue; }
// skip ends that don't target the current entity set
if (!toEnd.EntitySet.EdmEquals(entitySet)) { continue; }
// skip ends that aren't required
if (0 == MetadataHelper.GetLowerBoundOfMultiplicity(
fromEnd.CorrespondingAssociationEndMember.RelationshipMultiplicity)) { continue; }
// skip ends that don't target the current entity type
if (!MetadataHelper.GetEntityTypeForEnd(toEnd.CorrespondingAssociationEndMember)
.IsAssignableFrom(entityType)) { continue; }
// register the relationship so that we know it's required
DirectionalRelationship relationship = new DirectionalRelationship(entityKey, fromEnd.CorrespondingAssociationEndMember,
toEnd.CorrespondingAssociationEndMember, associationSet, stateEntry);
m_impliedRelationships.Add(relationship, stateEntry);
}
}
}
}
}
// requires: input is an IExtendedDataRecord representing an entity
// returns: entity type for the given record
private static EntityType GetEntityType(DbDataRecord dbDataRecord)
{
IExtendedDataRecord extendedRecord = dbDataRecord as IExtendedDataRecord;
Debug.Assert(extendedRecord != null);
Debug.Assert(BuiltInTypeKind.EntityType == extendedRecord.DataRecordInfo.RecordType.EdmType.BuiltInTypeKind);
return (EntityType)extendedRecord.DataRecordInfo.RecordType.EdmType;
}
/// <summary>
/// Add a relationship to be tracked by the validator.
/// </summary>
/// <param name="associationSet">Relationship set to which the given record belongs.</param>
/// <param name="record">Relationship record. Must conform to the type of the relationship set.</param>
/// <param name="stateEntry">State entry for the relationship being tracked</param>
internal void RegisterAssociation(AssociationSet associationSet, IExtendedDataRecord record, IEntityStateEntry stateEntry)
{
EntityUtil.CheckArgumentNull(associationSet, "relationshipSet");
EntityUtil.CheckArgumentNull(record, "record");
EntityUtil.CheckArgumentNull(stateEntry, "stateEntry");
Debug.Assert(associationSet.ElementType.Equals(record.DataRecordInfo.RecordType.EdmType));
// retrieve the ends of the relationship
Dictionary<string, EntityKey> endNameToKeyMap = new Dictionary<string, EntityKey>(
StringComparer.Ordinal);
foreach (FieldMetadata field in record.DataRecordInfo.FieldMetadata)
{
string endName = field.FieldType.Name;
EntityKey entityKey = (EntityKey)record.GetValue(field.Ordinal);
endNameToKeyMap.Add(endName, entityKey);
}
// register each unidirectional relationship subpart in the relationship instance
var ends = associationSet.AssociationSetEnds;
foreach (var fromEnd in ends)
{
foreach (var toEnd in ends)
{
// end to itself does not describe an interesting relationship subpart
if (object.ReferenceEquals(toEnd.CorrespondingAssociationEndMember, fromEnd.CorrespondingAssociationEndMember))
{
continue;
}
EntityKey toEntityKey = endNameToKeyMap[toEnd.CorrespondingAssociationEndMember.Name];
DirectionalRelationship relationship = new DirectionalRelationship(toEntityKey, fromEnd.CorrespondingAssociationEndMember,
toEnd.CorrespondingAssociationEndMember, associationSet, stateEntry);
AddExistingRelationship(relationship);
}
}
}
/// <summary>
/// Validates cardinality constraints for all added entities/relationships.
/// </summary>
internal void ValidateConstraints()
{
// ensure all expected relationships exist
foreach (KeyValuePair<DirectionalRelationship, IEntityStateEntry> expected in m_impliedRelationships)
{
DirectionalRelationship expectedRelationship = expected.Key;
IEntityStateEntry stateEntry = expected.Value;
// determine actual end cardinality
int count = GetDirectionalRelationshipCountDelta(expectedRelationship);
if (EntityState.Deleted == stateEntry.State)
{
// our cardinality expectations are reversed for delete (cardinality of 1 indicates
// we want -1 operation total)
count = -count;
}
// determine expected cardinality
int minimumCount = MetadataHelper.GetLowerBoundOfMultiplicity(expectedRelationship.FromEnd.RelationshipMultiplicity);
int? maximumCountDeclared = MetadataHelper.GetUpperBoundOfMultiplicity(expectedRelationship.FromEnd.RelationshipMultiplicity);
int maximumCount = maximumCountDeclared.HasValue ? maximumCountDeclared.Value : count; // negative value
// indicates unlimited cardinality
if (count < minimumCount || count > maximumCount)
{
// We could in theory "fix" the cardinality constraint violation by introducing surrogates,
// but we risk doing work on behalf of the user they don't want performed (e.g., deleting an
// entity or relationship the user has intentionally left untouched).
throw EntityUtil.UpdateRelationshipCardinalityConstraintViolation(
expectedRelationship.AssociationSet.Name, minimumCount, maximumCountDeclared,
TypeHelpers.GetFullName(expectedRelationship.ToEntityKey.EntityContainerName, expectedRelationship.ToEntityKey.EntitySetName),
count, expectedRelationship.FromEnd.Name,
stateEntry);
}
}
// ensure actual relationships have required ends
foreach (DirectionalRelationship actualRelationship in m_existingRelationships.Keys)
{
int addedCount;
int deletedCount;
actualRelationship.GetCountsInEquivalenceSet(out addedCount, out deletedCount);
int absoluteCount = Math.Abs(addedCount - deletedCount);
int minimumCount = MetadataHelper.GetLowerBoundOfMultiplicity(actualRelationship.FromEnd.RelationshipMultiplicity);
int? maximumCount = MetadataHelper.GetUpperBoundOfMultiplicity(actualRelationship.FromEnd.RelationshipMultiplicity);
// Check that we haven't inserted or deleted too many relationships
if (maximumCount.HasValue)
{
EntityState? violationType = default(EntityState?);
int? violationCount = default(int?);
if (addedCount > maximumCount.Value)
{
violationType = EntityState.Added;
violationCount = addedCount;
}
else if (deletedCount > maximumCount.Value)
{
violationType = EntityState.Deleted;
violationCount = deletedCount;
}
if (violationType.HasValue)
{
throw EntityUtil.Update(Strings.Update_RelationshipCardinalityViolation(maximumCount.Value,
violationType.Value, actualRelationship.AssociationSet.ElementType.FullName,
actualRelationship.FromEnd.Name, actualRelationship.ToEnd.Name, violationCount.Value),
null, actualRelationship.GetEquivalenceSet().Select(reln => reln.StateEntry));
}
}
// We care about the case where there is a relationship but no entity when
// the relationship and entity map to the same table. If there is a relationship
// with 1..1 cardinality to the entity and the relationship is being added or deleted,
// it is required that the entity is also added or deleted.
if (1 == absoluteCount && 1 == minimumCount && 1 == maximumCount) // 1..1 relationship being added/deleted
{
bool isAdd = addedCount > deletedCount;
// Ensure the entity is also being added or deleted
IEntityStateEntry entityEntry;
// Identify the following error conditions:
// - the entity is not being modified at all
// - the entity is being modified, but not in the way we expect (it's not being added or deleted)
if (!m_impliedRelationships.TryGetValue(actualRelationship, out entityEntry) ||
(isAdd && EntityState.Added != entityEntry.State) ||
(!isAdd && EntityState.Deleted != entityEntry.State))
{
throw EntityUtil.UpdateEntityMissingConstraintViolation(actualRelationship.AssociationSet.Name,
actualRelationship.ToEnd.Name, actualRelationship.StateEntry);
}
}
}
}
/// <summary>
/// Determines the net change in relationship count.
/// For instance, if the directional relationship is added 2 times and deleted 3, the return value is -1.
/// </summary>
private int GetDirectionalRelationshipCountDelta(DirectionalRelationship expectedRelationship)
{
// lookup up existing relationship from expected relationship
DirectionalRelationship existingRelationship;
if (m_existingRelationships.TryGetValue(expectedRelationship, out existingRelationship))
{
int addedCount;
int deletedCount;
existingRelationship.GetCountsInEquivalenceSet(out addedCount, out deletedCount);
return addedCount - deletedCount;
}
else
{
// no modifications to the relationship... return 0 (no net change)
return 0;
}
}
private void AddExistingRelationship(DirectionalRelationship relationship)
{
DirectionalRelationship existingRelationship;
if (m_existingRelationships.TryGetValue(relationship, out existingRelationship))
{
existingRelationship.AddToEquivalenceSet(relationship);
}
else
{
m_existingRelationships.Add(relationship, relationship);
}
}
/// <summary>
/// Determine which relationship sets reference the given entity set.
/// </summary>
/// <param name="entitySet">Entity set for which to identify relationships</param>
/// <returns>Relationship sets referencing the given entity set</returns>
private IEnumerable<AssociationSet> GetReferencingAssocationSets(EntitySet entitySet)
{
List<AssociationSet> relationshipSets;
// check if this information is cached
if (!m_referencingRelationshipSets.TryGetValue(entitySet, out relationshipSets))
{
relationshipSets = new List<AssociationSet>();
// relationship sets must live in the same container as the entity sets they reference
EntityContainer container = entitySet.EntityContainer;
foreach (EntitySetBase extent in container.BaseEntitySets)
{
AssociationSet associationSet = extent as AssociationSet;
if (null != associationSet && !associationSet.ElementType.IsForeignKey)
{
foreach (var end in associationSet.AssociationSetEnds)
{
if (end.EntitySet.Equals(entitySet))
{
relationshipSets.Add(associationSet);
break;
}
}
}
}
// add referencing relationship information to the cache
m_referencingRelationshipSets.Add(entitySet, relationshipSets);
}
return relationshipSets;
}
#endregion
#region Nested types
/// <summary>
/// An instance of an actual or expected relationship. This class describes one direction
/// of the relationship.
/// </summary>
private class DirectionalRelationship : IEquatable<DirectionalRelationship>
{
/// <summary>
/// Entity key for the entity being referenced by the relationship.
/// </summary>
internal readonly EntityKey ToEntityKey;
/// <summary>
/// Name of the end referencing the entity key.
/// </summary>
internal readonly AssociationEndMember FromEnd;
/// <summary>
/// Name of the end the entity key references.
/// </summary>
internal readonly AssociationEndMember ToEnd;
/// <summary>
/// State entry containing this relationship.
/// </summary>
internal readonly IEntityStateEntry StateEntry;
/// <summary>
/// Reference to the relationship set.
/// </summary>
internal readonly AssociationSet AssociationSet;
/// <summary>
/// Reference to next 'equivalent' relationship in circular linked list.
/// </summary>
private DirectionalRelationship _equivalenceSetLinkedListNext;
private readonly int _hashCode;
internal DirectionalRelationship(EntityKey toEntityKey, AssociationEndMember fromEnd, AssociationEndMember toEnd, AssociationSet associationSet, IEntityStateEntry stateEntry)
{
ToEntityKey = EntityUtil.CheckArgumentNull(toEntityKey, "toEntityKey");
FromEnd = EntityUtil.CheckArgumentNull(fromEnd, "fromEnd");
ToEnd = EntityUtil.CheckArgumentNull(toEnd, "toEnd");
AssociationSet = EntityUtil.CheckArgumentNull(associationSet, "associationSet");
StateEntry = EntityUtil.CheckArgumentNull(stateEntry, "stateEntry");
_equivalenceSetLinkedListNext = this;
_hashCode = toEntityKey.GetHashCode() ^
fromEnd.GetHashCode() ^
toEnd.GetHashCode() ^
associationSet.GetHashCode();
}
/// <summary>
/// Requires: 'other' must refer to the same relationship metadata and the same target entity and
/// must not already be a part of an equivalent set.
/// Adds the given relationship to linked list containing all equivalent relationship instances
/// for this relationship (e.g. all orders associated with a specific customer)
/// </summary>
internal void AddToEquivalenceSet(DirectionalRelationship other)
{
Debug.Assert(null != other, "other must not be null");
Debug.Assert(this.Equals(other), "other must be another instance of the same relationship target");
Debug.Assert(Object.ReferenceEquals(other._equivalenceSetLinkedListNext, other), "other must not be part of an equivalence set yet");
DirectionalRelationship currentSuccessor = this._equivalenceSetLinkedListNext;
this._equivalenceSetLinkedListNext = other;
other._equivalenceSetLinkedListNext = currentSuccessor;
}
/// <summary>
/// Returns all relationships in equivalence set.
/// </summary>
internal IEnumerable<DirectionalRelationship> GetEquivalenceSet()
{
// yield everything in circular linked list
DirectionalRelationship current = this;
do
{
yield return current;
current = current._equivalenceSetLinkedListNext;
}
while (!object.ReferenceEquals(current, this));
}
/// <summary>
/// Determines the number of add and delete operations contained in this equivalence set.
/// </summary>
internal void GetCountsInEquivalenceSet(out int addedCount, out int deletedCount)
{
addedCount = 0;
deletedCount = 0;
// yield everything in circular linked list
DirectionalRelationship current = this;
do
{
if (current.StateEntry.State == EntityState.Added)
{
addedCount++;
}
else if (current.StateEntry.State == EntityState.Deleted)
{
deletedCount++;
}
current = current._equivalenceSetLinkedListNext;
}
while (!object.ReferenceEquals(current, this));
}
public override int GetHashCode()
{
return _hashCode;
}
public bool Equals(DirectionalRelationship other)
{
if (object.ReferenceEquals(this, other)) { return true; }
if (null == other) { return false; }
if (ToEntityKey != other.ToEntityKey) { return false; }
if (AssociationSet != other.AssociationSet) { return false; }
if (ToEnd != other.ToEnd) { return false; }
if (FromEnd != other.FromEnd) { return false; }
return true;
}
public override bool Equals(object obj)
{
Debug.Fail("use only typed Equals method");
return Equals(obj as DirectionalRelationship);
}
public override string ToString()
{
return String.Format(CultureInfo.InvariantCulture, "{0}.{1}-->{2}: {3}",
AssociationSet.Name, FromEnd.Name, ToEnd.Name, StringUtil.BuildDelimitedList(ToEntityKey.EntityKeyValues, null, null));
}
}
#endregion
}
}
}

Some files were not shown because too many files have changed in this diff Show More