//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// BindingGraph class
//
//
//---------------------------------------------------------------------
namespace System.Data.Services.Client
{
#region Namespaces
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
#endregion
///
/// Color of each vertex to be used for Depth First Search
///
internal enum VertexColor
{
/// White color means un-visited
White,
/// Gray color means added to queue for DFS
Gray,
/// Black color means already visited hence reachable from root
Black
}
///
/// The BindingGraph maps objects tracked by the DataServiceContext to vertices in a
/// graph used to manage the information needed for data binding. The objects tracked
/// by the BindingGraph are entity type objects and observable entity collections.
///
internal sealed class BindingGraph
{
/// The observer of the graph
private BindingObserver observer;
/// Graph containing entities, collections and their relationships
private Graph graph;
/// Constructor
/// Observer of the graph
public BindingGraph(BindingObserver observer)
{
this.observer = observer;
this.graph = new Graph();
}
/// Adds a collection to the graph
/// Source object for the collection, this object has navigation property corresponding to collection
/// Property in that corresponds to the collection
/// Collection being added
/// Entity set of entities in the collection
/// true if a new vertex had to be created, false if it already exists
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)]
public bool AddCollection(
object source,
string sourceProperty,
object collection,
string collectionEntitySet)
{
Debug.Assert(collection != null, "'collection' can not be null");
Debug.Assert(
BindingEntityInfo.IsDataServiceCollection(collection.GetType()),
"Argument 'collection' must be an DataServiceCollection of entity type T");
if (this.graph.ExistsVertex(collection))
{
return false;
}
Vertex collectionVertex = this.graph.AddVertex(collection);
collectionVertex.IsCollection = true;
collectionVertex.EntitySet = collectionEntitySet;
ICollection collectionItf = collection as ICollection;
if (source != null)
{
collectionVertex.Parent = this.graph.LookupVertex(source);
collectionVertex.ParentProperty = sourceProperty;
this.graph.AddEdge(source, collection, sourceProperty);
// Update the observer on the child collection
Type entityType = BindingUtils.GetCollectionEntityType(collection.GetType());
Debug.Assert(entityType != null, "Collection must at least be inherited from DataServiceCollection");
// Fail if the collection entity type does not implement INotifyPropertyChanged.
if (!typeof(INotifyPropertyChanged).IsAssignableFrom(entityType))
{
throw new InvalidOperationException(Strings.DataBinding_NotifyPropertyChangedNotImpl(entityType));
}
typeof(BindingGraph)
.GetMethod("SetObserver", BindingFlags.Instance | BindingFlags.NonPublic)
.MakeGenericMethod(entityType)
.Invoke(this, new object[] { collectionItf });
}
else
{
// When there is no source, then this vertex is the root vertex
this.graph.Root = collectionVertex;
}
Debug.Assert(
collectionVertex.Parent != null || collectionVertex.IsRootCollection,
"If parent is null, then collectionVertex should be a root collection");
// Register for collection notifications
this.AttachCollectionNotification(collection);
// Perform deep add, by recursively adding entities in the collection
foreach (var item in collectionItf)
{
this.AddEntity(
source,
sourceProperty,
item,
collectionEntitySet,
collection);
}
return true;
}
/// Adds an entity to the graph
/// Source object for the entity, this object has navigation property that links to entity
/// Property in that links to entity
/// Entity being added
/// Entity set of entity being added
/// Item from which the directed edge in the graph goes into . This can be a collection
/// true if a new vertex had to be created, false if it already exists
///
/// This method processes the current 'target' entity and then recursively moves into the graph through
/// the navigation properties. The 'source' is a previously processed item - it is the 'parent'
/// of the target entity.
/// The code generated EntitySetAttribute is processed by this method.
/// A source entity can reference the target entity directly through an entity reference navigation property,
/// or indirectly through a collection navigation property.
///
public bool AddEntity(
object source,
string sourceProperty,
object target,
string targetEntitySet,
object edgeSource)
{
Vertex sourceVertex = this.graph.LookupVertex(edgeSource);
Debug.Assert(sourceVertex != null, "Must have a valid edge source");
Vertex entityVertex = null;
bool addedNewEntity = false;
if (target != null)
{
entityVertex = this.graph.LookupVertex(target);
if (entityVertex == null)
{
entityVertex = this.graph.AddVertex(target);
entityVertex.EntitySet = BindingEntityInfo.GetEntitySet(target, targetEntitySet);
// Register for entity notifications, fail if the entity does not implement INotifyPropertyChanged.
if (!this.AttachEntityOrComplexObjectNotification(target))
{
throw new InvalidOperationException(Strings.DataBinding_NotifyPropertyChangedNotImpl(target.GetType()));
}
addedNewEntity = true;
}
// Add relationship. Connect the from end to the target.
if (this.graph.ExistsEdge(edgeSource, target, sourceVertex.IsCollection ? null : sourceProperty))
{
throw new InvalidOperationException(Strings.DataBinding_EntityAlreadyInCollection(target.GetType()));
}
this.graph.AddEdge(edgeSource, target, sourceVertex.IsCollection ? null : sourceProperty);
}
if (!sourceVertex.IsCollection)
{
this.observer.HandleUpdateEntityReference(
source,
sourceProperty,
sourceVertex.EntitySet,
target,
entityVertex == null ? null : entityVertex.EntitySet);
}
else
{
Debug.Assert(target != null, "Target must be non-null when adding to collections");
this.observer.HandleAddEntity(
source,
sourceProperty,
sourceVertex.Parent != null ? sourceVertex.Parent.EntitySet : null,
edgeSource as ICollection,
target,
entityVertex.EntitySet);
}
if (addedNewEntity)
{
// Perform recursive add operation through target's properties
this.AddFromProperties(target);
}
return addedNewEntity;
}
///
/// Removes the from the binding graph
///
/// Item to remove
/// Parent of the
/// Parent property that refers to
public void Remove(object item, object parent, string parentProperty)
{
Vertex vertexToRemove = this.graph.LookupVertex(item);
if (vertexToRemove == null)
{
return;
}
Debug.Assert(!vertexToRemove.IsRootCollection, "Root collections are never removed");
// Parent will always be non-null for deletes from collections, this will include
// both root and child collections. For root collections, parentProperty will be null.
Debug.Assert(parent != null, "Parent has to be present.");
// When parentProperty is null, parent is itself a root collection
if (parentProperty != null)
{
BindingEntityInfo.BindingPropertyInfo bpi = BindingEntityInfo.GetObservableProperties(parent.GetType())
.Single(p => p.PropertyInfo.PropertyName == parentProperty);
Debug.Assert(bpi.PropertyKind == BindingPropertyKind.BindingPropertyKindCollection, "parentProperty must refer to an DataServiceCollection");
parent = bpi.PropertyInfo.GetValue(parent);
}
object source = null;
string sourceProperty = null;
string sourceEntitySet = null;
string targetEntitySet = null;
this.GetEntityCollectionInfo(
parent,
out source,
out sourceProperty,
out sourceEntitySet,
out targetEntitySet);
targetEntitySet = BindingEntityInfo.GetEntitySet(item, targetEntitySet);
this.observer.HandleDeleteEntity(
source,
sourceProperty,
sourceEntitySet,
parent as ICollection,
item,
targetEntitySet);
this.graph.RemoveEdge(parent, item, null);
}
/// Removes the collection from the graph
/// Collection to remove
public void RemoveCollection(object collection)
{
Vertex collectionVertex = this.graph.LookupVertex(collection);
Debug.Assert(collectionVertex != null, "Must be tracking the vertex for the collection");
foreach (Edge collectionEdge in collectionVertex.OutgoingEdges.ToList())
{
this.graph.RemoveEdge(collection, collectionEdge.Target.Item, null);
}
// This is where actual removal from graph happens, detach notifications for removed object
this.RemoveUnreachableVertices();
}
/// Removes a relationship between two items based on source and relation label
/// Source item
/// Label for relation
public void RemoveRelation(object source, string relation)
{
Edge edge = this.graph
.LookupVertex(source)
.OutgoingEdges
.SingleOrDefault(e => e.Source.Item == source && e.Label == relation);
if (edge != null)
{
this.graph.RemoveEdge(edge.Source.Item, edge.Target.Item, edge.Label);
}
// This is where actual removal from graph happens, detach notifications for removed object
this.RemoveUnreachableVertices();
}
#if DEBUG
/// Checks to see if an object is being tracked by the graph
/// Object being checked
/// true if the object exists in the graph, false otherwise
public bool IsTracking(object item)
{
return this.graph.ExistsVertex(item);
}
#endif
/// Remove all non-tracked entities from the graph
public void RemoveNonTrackedEntities()
{
// Cleanup all untracked entities
foreach (var entity in this.graph.Select(o => BindingEntityInfo.IsEntityType(o.GetType()) && !this.observer.IsContextTrackingEntity(o)))
{
this.graph.ClearEdgesForVertex(this.graph.LookupVertex(entity));
}
this.RemoveUnreachableVertices();
}
///
/// Returns a sequence of items belonging to a collection. Uses the children of a collection
/// vertex for this enumeration.
///
/// Collection being enumerated.
/// Sequence of items belonging to the collection.
public IEnumerable