Code:
/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / DataWeb / Client / System / Data / Services / Client / AtomMaterializer.cs / 1305376 / AtomMaterializer.cs
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Provides a class that can materialize ATOM entries into typed
// objects, while maintaining a log of changes done.
//
//---------------------------------------------------------------------
namespace System.Data.Services.Client
{
#region Namespaces.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Services.Common;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
#endregion Namespaces.
///
/// Use this class to invoke projection methods from AtomMaterializer.
///
internal static class AtomMaterializerInvoker
{
/// Enumerates casting each element to a type.
/// Element type to enumerate over.
/// Element source.
///
/// An IEnumerable<T> that iterates over the specified .
///
///
/// This method should be unnecessary with .NET 4.0 covariance support.
///
internal static IEnumerable EnumerateAsElementType(IEnumerable source)
{
return AtomMaterializer.EnumerateAsElementType(source);
}
/// Creates a list to a target element type.
/// Materializer used to flow link tracking.
/// Element type to enumerate over.
/// Element type for list.
/// Element source.
///
/// An IEnumerable<T> that iterates over the specified .
///
///
/// This method should be unnecessary with .NET 4.0 covariance support.
///
internal static List ListAsElementType(object materializer, IEnumerable source) where T : TTarget
{
Debug.Assert(materializer.GetType() == typeof(AtomMaterializer), "materializer.GetType() == typeof(AtomMaterializer)");
return AtomMaterializer.ListAsElementType((AtomMaterializer)materializer, source);
}
/// Checks whether the entity on the specified is null.
/// Root entry for paths.
/// Expected type for .
/// Path to pull value for.
/// Whether the specified is null.
///
/// This method will not instantiate entity types on the path.
///
internal static bool ProjectionCheckValueForPathIsNull(
object entry,
Type expectedType,
object path)
{
Debug.Assert(entry.GetType() == typeof(AtomEntry), "entry.GetType() == typeof(AtomEntry)");
Debug.Assert(path.GetType() == typeof(ProjectionPath), "path.GetType() == typeof(ProjectionPath)");
return AtomMaterializer.ProjectionCheckValueForPathIsNull((AtomEntry)entry, expectedType, (ProjectionPath)path);
}
/// Provides support for Select invocations for projections.
/// Materializer under which projection is taking place.
/// Root entry for paths.
/// Expected type for .
/// Expected result type.
/// Path to traverse.
/// Selector callback.
/// An enumerable with the select results.
internal static IEnumerable ProjectionSelect(
object materializer,
object entry,
Type expectedType,
Type resultType,
object path,
Func selector)
{
Debug.Assert(materializer.GetType() == typeof(AtomMaterializer), "materializer.GetType() == typeof(AtomMaterializer)");
Debug.Assert(entry.GetType() == typeof(AtomEntry), "entry.GetType() == typeof(AtomEntry)");
Debug.Assert(path.GetType() == typeof(ProjectionPath), "path.GetType() == typeof(ProjectionPath)");
return AtomMaterializer.ProjectionSelect((AtomMaterializer)materializer, (AtomEntry)entry, expectedType, resultType, (ProjectionPath)path, selector);
}
/// Provides support for getting payload entries during projections.
/// Entry to get sub-entry from.
/// Name of sub-entry.
/// The sub-entry (never null).
internal static AtomEntry ProjectionGetEntry(object entry, string name)
{
Debug.Assert(entry.GetType() == typeof(AtomEntry), "entry.GetType() == typeof(AtomEntry)");
return AtomMaterializer.ProjectionGetEntry((AtomEntry)entry, name);
}
/// Initializes a projection-driven entry (with a specific type and specific properties).
/// Materializer under which projection is taking place.
/// Root entry for paths.
/// Expected type for .
/// Expected result type.
/// Properties to materialize.
/// Functions to get values for functions.
/// The initialized entry.
internal static object ProjectionInitializeEntity(
object materializer,
object entry,
Type expectedType,
Type resultType,
string[] properties,
Func[] propertyValues)
{
Debug.Assert(materializer.GetType() == typeof(AtomMaterializer), "materializer.GetType() == typeof(AtomMaterializer)");
Debug.Assert(entry.GetType() == typeof(AtomEntry), "entry.GetType() == typeof(AtomEntry)");
return AtomMaterializer.ProjectionInitializeEntity((AtomMaterializer)materializer, (AtomEntry)entry, expectedType, resultType, properties, propertyValues);
}
/// Projects a simple value from the specified .
/// Materializer under which projection is taking place.
/// Root entry for paths.
/// Expected type for .
/// Path to pull value for.
/// The value for the specified .
///
/// This method will not instantiate entity types, except to satisfy requests
/// for payload-driven feeds or leaf entities.
///
internal static object ProjectionValueForPath(object materializer, object entry, Type expectedType, object path)
{
Debug.Assert(materializer.GetType() == typeof(AtomMaterializer), "materializer.GetType() == typeof(AtomMaterializer)");
Debug.Assert(entry.GetType() == typeof(AtomEntry), "entry.GetType() == typeof(AtomEntry)");
Debug.Assert(path.GetType() == typeof(ProjectionPath), "path.GetType() == typeof(ProjectionPath)");
return AtomMaterializer.ProjectionValueForPath((AtomMaterializer)materializer, (AtomEntry)entry, expectedType, (ProjectionPath)path);
}
/// Materializes an entry with no special selection.
/// Materializer under which materialization should take place.
/// Entry with object to materialize.
/// Expected type for the entry.
/// The materialized instance.
internal static object DirectMaterializePlan(object materializer, object entry, Type expectedEntryType)
{
Debug.Assert(materializer.GetType() == typeof(AtomMaterializer), "materializer.GetType() == typeof(AtomMaterializer)");
Debug.Assert(entry.GetType() == typeof(AtomEntry), "entry.GetType() == typeof(AtomEntry)");
return AtomMaterializer.DirectMaterializePlan((AtomMaterializer)materializer, (AtomEntry)entry, expectedEntryType);
}
/// Materializes an entry without including in-lined expanded links.
/// Materializer under which materialization should take place.
/// Entry with object to materialize.
/// Expected type for the entry.
/// The materialized instance.
internal static object ShallowMaterializePlan(object materializer, object entry, Type expectedEntryType)
{
Debug.Assert(materializer.GetType() == typeof(AtomMaterializer), "materializer.GetType() == typeof(AtomMaterializer)");
Debug.Assert(entry.GetType() == typeof(AtomEntry), "entry.GetType() == typeof(AtomEntry)");
return AtomMaterializer.ShallowMaterializePlan((AtomMaterializer)materializer, (AtomEntry)entry, expectedEntryType);
}
}
///
/// Use this class to materialize objects provided from an .
///
[DebuggerDisplay("AtomMaterializer {parser}")]
internal class AtomMaterializer
{
#region Private fields.
/// Associated context, used for resolving instances and types.
private readonly DataServiceContext context;
/// Expected type to materialize.
private readonly Type expectedType;
//// Whether missing properties should be ignored.
////private readonly bool ignoreMissingProperties;
/// Log into which materialization activity is recorded.
private readonly AtomMaterializerLog log;
/// Function to materialize an entry and produce a value.
private readonly ProjectionPlan materializeEntryPlan;
///
/// Callback to be invoked when an object is materialized; receives the tag and object.
///
private readonly Action materializedObjectCallback;
/// Merge behavior.
private readonly MergeOption mergeOption;
/// Collection->Next Link Table for nested links
private readonly Dictionary nextLinkTable;
///
/// from which content will be read.
///
private readonly AtomParser parser;
/// Current value being materialized; possibly null.
private object currentValue;
/// Whether missing properties should be ignored.
///
/// This should be readonly, but we make it writeable as a
/// work-around until server projections are implemented.
///
private bool ignoreMissingProperties;
/// Target instance that the materializer expects to update.
private object targetInstance;
#endregion Private fields.
#region Constructors.
/// Initializes a new instance.
/// from which content will be read.
/// Context into which instances will be materialized.
/// Expected type to materialize.
///
/// Whether missing properties on the type should be ignored (i.e., the payload
/// is allowed to have properties that are not materialized and dropped instead).
///
/// Merge behavior.
/// Log into which materialization activity is recorded.
///
/// Callback to be invoked when an object is materialized; receives the tag and object.
///
/// Query components describingg processing of components; possibly null.
/// Projection plan (if compiled in an earlier query).
internal AtomMaterializer(
AtomParser parser,
DataServiceContext context,
Type expectedType,
bool ignoreMissingProperties,
MergeOption mergeOption,
AtomMaterializerLog log,
Action materializedObjectCallback,
QueryComponents queryComponents,
ProjectionPlan plan)
{
Debug.Assert(context != null, "context != null");
Debug.Assert(parser != null, "parser != null");
Debug.Assert(log != null, "log != null");
this.context = context;
this.parser = parser;
this.expectedType = expectedType;
this.ignoreMissingProperties = ignoreMissingProperties;
this.mergeOption = mergeOption;
this.log = log;
this.materializedObjectCallback = materializedObjectCallback;
this.nextLinkTable = new Dictionary(ReferenceEqualityComparer.Instance);
this.materializeEntryPlan = plan ?? CreatePlan(queryComponents);
}
#endregion Constructors.
#region Internal properties.
/// DataServiceContext instance this materializer is bound to.
internal DataServiceContext Context
{
get { return this.context; }
}
/// Function to materialize an entry and produce a value.
internal ProjectionPlan MaterializeEntryPlan
{
get { return this.materializeEntryPlan; }
}
/// Target instance that the materializer expects to update.
///
/// This property is typically null, but will be set to an existing
/// instance when POST-ing a value, so values can get merged into it.
///
internal object TargetInstance
{
get
{
return this.targetInstance;
}
set
{
Debug.Assert(value != null, "value != null -- otherwise we have no instance target.");
this.targetInstance = value;
}
}
/// Feed being materialized; possibly null.
internal AtomFeed CurrentFeed
{
get
{
return this.parser.CurrentFeed;
}
}
/// Entry being materialized; possibly null.
internal AtomEntry CurrentEntry
{
get
{
return this.parser.CurrentEntry;
}
}
/// Current value being materialized; possibly null.
///
/// This will typically be an entity if
/// is assigned, but may contain a string for example if a top-level
/// primitive of type string is found.
///
internal object CurrentValue
{
get
{
return this.currentValue;
}
}
/// Log into which materialization activity is recorded.
internal AtomMaterializerLog Log
{
get { return this.log; }
}
/// Table storing the next links ----oicated with the current payload
internal Dictionary NextLinkTable
{
get { return this.nextLinkTable; }
}
/// Whether we have finished processing the current data stream.
internal bool IsEndOfStream
{
get { return this.parser.DataKind == AtomDataKind.Finished; }
}
#endregion Internal properties.
#region Projection support.
/// Enumerates casting each element to a type.
/// Element type to enumerate over.
/// Element source.
///
/// An IEnumerable<T> that iterates over the specified .
///
///
/// This method should be unnecessary with .NET 4.0 covariance support.
///
internal static IEnumerable EnumerateAsElementType(IEnumerable source)
{
Debug.Assert(source != null, "source != null");
IEnumerable typedSource = source as IEnumerable;
if (typedSource != null)
{
return typedSource;
}
else
{
return EnumerateAsElementTypeInternal(source);
}
}
/// Enumerates casting each element to a type.
/// Element type to enumerate over.
/// Element source.
///
/// An IEnumerable<T> that iterates over the specified .
///
///
/// This method should be unnecessary with .NET 4.0 covariance support.
///
internal static IEnumerable EnumerateAsElementTypeInternal(IEnumerable source)
{
Debug.Assert(source != null, "source != null");
foreach (object item in source)
{
yield return (T)item;
}
}
/// Checks whether the specified type is a DataServiceCollection type (or inherits from one).
/// Type to check.
/// true if the type inherits from DataServiceCollection; false otherwise.
internal static bool IsDataServiceCollection(Type type)
{
while (type != null)
{
if (type.IsGenericType && WebUtil.IsDataServiceCollectionType(type.GetGenericTypeDefinition()))
{
return true;
}
type = type.BaseType;
}
return false;
}
/// Creates a list to a target element type.
/// Materializer used to flow link tracking.
/// Element type to enumerate over.
/// Element type for list.
/// Element source.
///
/// An IEnumerable<T> that iterates over the specified .
///
///
/// This method should be unnecessary with .NET 4.0 covariance support.
///
internal static List ListAsElementType(AtomMaterializer materializer, IEnumerable source) where T : TTarget
{
Debug.Assert(materializer != null, "materializer != null");
Debug.Assert(source != null, "source != null");
List typedSource = source as List;
if (typedSource != null)
{
return typedSource;
}
List list;
IList sourceList = source as IList;
if (sourceList != null)
{
list = new List(sourceList.Count);
}
else
{
list = new List();
}
foreach (T item in source)
{
list.Add((TTarget)item);
}
// We can flow the same continuation becaues they're immutable, and
// we don't need to set the continuation property because List doesn't
// have one.
DataServiceQueryContinuation continuation;
if (materializer.nextLinkTable.TryGetValue(source, out continuation))
{
materializer.nextLinkTable[list] = continuation;
}
return list;
}
/// Checks whether the entity on the specified is null.
/// Root entry for paths.
/// Expected type for .
/// Path to pull value for.
/// Whether the specified is null.
///
/// This method will not instantiate entity types on the path.
/// Note that if the target is a collection, the result is always false,
/// as the model does not allow null feeds (but instead gets an empty
/// collection, possibly with continuation tokens and such).
///
internal static bool ProjectionCheckValueForPathIsNull(
AtomEntry entry,
Type expectedType,
ProjectionPath path)
{
Debug.Assert(entry != null, "entry != null");
Debug.Assert(path != null, "path != null");
if (path.Count == 0 || path.Count == 1 && path[0].Member == null)
{
return entry.IsNull;
}
bool result = false;
AtomContentProperty atomProperty = null;
List properties = entry.DataValues;
for (int i = 0; i < path.Count; i++)
{
var segment = path[i];
if (segment.Member == null)
{
continue;
}
bool segmentIsLeaf = i == path.Count - 1;
string propertyName = segment.Member;
ClientType.ClientProperty property = ClientType.Create(expectedType).GetProperty(propertyName, false);
atomProperty = GetPropertyOrThrow(properties, propertyName);
ValidatePropertyMatch(property, atomProperty);
if (atomProperty.Feed != null)
{
Debug.Assert(segmentIsLeaf, "segmentIsLeaf -- otherwise the path generated traverses a feed, which should be disallowed");
result = false;
}
else
{
Debug.Assert(
atomProperty.Entry != null,
"atomProperty.Entry != null -- otherwise a primitive property / complex type is being rewritte with a null check; this is only supported for entities and collection");
if (segmentIsLeaf)
{
result = atomProperty.Entry.IsNull;
}
properties = atomProperty.Entry.DataValues;
entry = atomProperty.Entry;
}
expectedType = property.PropertyType;
}
return result;
}
/// Provides support for Select invocations for projections.
/// Materializer under which projection is taking place.
/// Root entry for paths.
/// Expected type for .
/// Expected result type.
/// Path to traverse.
/// Selector callback.
/// An enumerable with the select results.
internal static IEnumerable ProjectionSelect(
AtomMaterializer materializer,
AtomEntry entry,
Type expectedType,
Type resultType,
ProjectionPath path,
Func selector)
{
ClientType entryType = entry.ActualType ?? ClientType.Create(expectedType);
IEnumerable list = (IEnumerable)Util.ActivatorCreateInstance(typeof(List<>).MakeGenericType(resultType));
AtomContentProperty atomProperty = null;
ClientType.ClientProperty property = null;
for (int i = 0; i < path.Count; i++)
{
var segment = path[i];
if (segment.Member == null)
{
continue;
}
string propertyName = segment.Member;
property = entryType.GetProperty(propertyName, false);
atomProperty = GetPropertyOrThrow(entry, propertyName);
if (atomProperty.Entry != null)
{
entry = atomProperty.Entry;
entryType = ClientType.Create(property.NullablePropertyType, false);
}
}
ValidatePropertyMatch(property, atomProperty);
AtomFeed sourceFeed = atomProperty.Feed;
Debug.Assert(
sourceFeed != null,
"sourceFeed != null -- otherwise ValidatePropertyMatch should have thrown or property isn't a collection (and should be part of this plan)");
Action addMethod = GetAddToCollectionDelegate(list.GetType());
foreach (var paramEntry in sourceFeed.Entries)
{
object projected = selector(materializer, paramEntry, property.CollectionType /* perhaps nested? */);
addMethod(list, projected);
}
ProjectionPlan plan = new ProjectionPlan();
plan.LastSegmentType = property.CollectionType;
plan.Plan = selector;
plan.ProjectedType = resultType;
materializer.FoundNextLinkForCollection(list, sourceFeed.NextLink, plan);
return list;
}
/// Provides support for getting payload entries during projections.
/// Entry to get sub-entry from.
/// Name of sub-entry.
/// The sub-entry (never null).
internal static AtomEntry ProjectionGetEntry(AtomEntry entry, string name)
{
Debug.Assert(entry != null, "entry != null -- ProjectionGetEntry never returns a null entry, and top-level materialization shouldn't pass one in");
AtomContentProperty property = GetPropertyOrThrow(entry, name);
if (property.Entry == null)
{
throw new InvalidOperationException(Strings.AtomMaterializer_PropertyNotExpectedEntry(name, entry.Identity));
}
CheckEntryToAccessNotNull(property.Entry, name);
return property.Entry;
}
/// Initializes a projection-driven entry (with a specific type and specific properties).
/// Materializer under which projection is taking place.
/// Root entry for paths.
/// Expected type for .
/// Expected result type.
/// Properties to materialize.
/// Functions to get values for functions.
/// The initialized entry.
internal static object ProjectionInitializeEntity(
AtomMaterializer materializer,
AtomEntry entry,
Type expectedType,
Type resultType,
string[] properties,
Func[] propertyValues)
{
if (entry == null || entry.IsNull)
{
throw new NullReferenceException(Strings.AtomMaterializer_EntryToInitializeIsNull(resultType.FullName));
}
object result;
if (!entry.EntityHasBeenResolved)
{
AtomMaterializer.ProjectionEnsureEntryAvailableOfType(materializer, entry, resultType);
}
else if (!resultType.IsAssignableFrom(entry.ActualType.ElementType))
{
string message = Strings.AtomMaterializer_ProjectEntityTypeMismatch(
resultType.FullName,
entry.ActualType.ElementType.FullName,
entry.Identity);
throw new InvalidOperationException(message);
}
result = entry.ResolvedObject;
for (int i = 0; i < properties.Length; i++)
{
var property = entry.ActualType.GetProperty(properties[i], materializer.ignoreMissingProperties);
object value = propertyValues[i](materializer, entry, expectedType);
if (entry.ShouldUpdateFromPayload && ClientType.Create(property.NullablePropertyType, false).IsEntityType)
{
materializer.Log.SetLink(entry, property.PropertyName, value);
}
bool isEntity = property.CollectionType == null || !ClientType.CheckElementTypeIsEntity(property.CollectionType);
if (entry.ShouldUpdateFromPayload)
{
if (isEntity)
{
property.SetValue(result, value, property.PropertyName, false);
}
else
{
IEnumerable valueAsEnumerable = (IEnumerable)value;
DataServiceQueryContinuation continuation = materializer.nextLinkTable[valueAsEnumerable];
Uri nextLinkUri = continuation == null ? null : continuation.NextLinkUri;
ProjectionPlan plan = continuation == null ? null : continuation.Plan;
materializer.MergeLists(entry, property, valueAsEnumerable, nextLinkUri, plan);
}
}
else if (!isEntity)
{
materializer.FoundNextLinkForUnmodifiedCollection(property.GetValue(entry.ResolvedObject) as IEnumerable);
}
}
return result;
}
/// Projects a simple value from the specified .
/// Materializer under which projection is taking place.
/// Root entry for paths.
/// Expected type for .
/// Path to pull value for.
/// The value for the specified .
///
/// This method will not instantiate entity types, except to satisfy requests
/// for payload-driven feeds or leaf entities.
///
internal static object ProjectionValueForPath(AtomMaterializer materializer, AtomEntry entry, Type expectedType, ProjectionPath path)
{
Debug.Assert(materializer != null, "materializer != null");
Debug.Assert(entry != null, "entry != null");
Debug.Assert(path != null, "path != null");
// An empty path indicates that we do a regular materialization.
if (path.Count == 0 || path.Count == 1 && path[0].Member == null)
{
if (!entry.EntityHasBeenResolved)
{
materializer.Materialize(entry, expectedType, /* includeLinks */ false);
}
return entry.ResolvedObject;
}
object result = null;
AtomContentProperty atomProperty = null;
List properties = entry.DataValues;
for (int i = 0; i < path.Count; i++)
{
var segment = path[i];
if (segment.Member == null)
{
continue;
}
bool segmentIsLeaf = i == path.Count - 1;
string propertyName = segment.Member;
// only primitive values can be mapped so we don't need to apply mappings for non-leaf segments
if (segmentIsLeaf)
{
CheckEntryToAccessNotNull(entry, propertyName);
if (!entry.EntityPropertyMappingsApplied)
{
// EPM should happen only once per entry
ClientType attributeSourceType = MaterializeAtom.GetEntryClientType(entry.TypeName, materializer.context, expectedType, false);
ApplyEntityPropertyMappings(entry, attributeSourceType);
}
}
ClientType.ClientProperty property = ClientType.Create(expectedType).GetProperty(propertyName, false);
atomProperty = GetPropertyOrThrow(properties, propertyName);
ValidatePropertyMatch(property, atomProperty);
AtomFeed feedValue = atomProperty.Feed;
if (feedValue != null)
{
Debug.Assert(segmentIsLeaf, "segmentIsLeaf -- otherwise the path generated traverses a feed, which should be disallowed");
// When we're materializing a feed as a leaf, we actually project each element.
Type collectionType = ClientType.GetImplementationType(segment.ProjectionType, typeof(ICollection<>));
if (collectionType == null)
{
collectionType = ClientType.GetImplementationType(segment.ProjectionType, typeof(IEnumerable<>));
}
Debug.Assert(
collectionType != null,
"collectionType != null -- otherwise the property should never have been recognized as a collection");
Type nestedExpectedType = collectionType.GetGenericArguments()[0];
Type feedType = segment.ProjectionType;
if (feedType.IsInterface || IsDataServiceCollection(feedType))
{
feedType = typeof(System.Collections.ObjectModel.Collection<>).MakeGenericType(nestedExpectedType);
}
IEnumerable list = (IEnumerable)Util.ActivatorCreateInstance(feedType);
MaterializeToList(materializer, list, nestedExpectedType, feedValue.Entries);
if (IsDataServiceCollection(segment.ProjectionType))
{
Type dataServiceCollectionType = WebUtil.GetDataServiceCollectionOfT(nestedExpectedType);
list = (IEnumerable)Util.ActivatorCreateInstance(
dataServiceCollectionType,
list, // items
TrackingMode.None); // tracking mode
}
ProjectionPlan plan = CreatePlanForShallowMaterialization(nestedExpectedType);
materializer.FoundNextLinkForCollection(list, feedValue.NextLink, plan);
result = list;
}
else if (atomProperty.Entry != null)
{
// If this is a leaf, then we'll do a tracking, payload-driven
// materialization. If this isn't the leaf, then we'll
// simply traverse through its properties.
if (segmentIsLeaf && !atomProperty.Entry.EntityHasBeenResolved && !atomProperty.IsNull)
{
materializer.Materialize(atomProperty.Entry, property.PropertyType, /* includeLinks */ false);
}
properties = atomProperty.Entry.DataValues;
result = atomProperty.Entry.ResolvedObject;
entry = atomProperty.Entry;
}
else
{
if (atomProperty.Properties != null)
{
if (atomProperty.MaterializedValue == null && !atomProperty.IsNull)
{
ClientType complexType = ClientType.Create(property.PropertyType);
object complexInstance = Util.ActivatorCreateInstance(property.PropertyType);
MaterializeDataValues(complexType, atomProperty.Properties, materializer.ignoreMissingProperties, materializer.context);
ApplyDataValues(complexType, atomProperty.Properties, materializer.ignoreMissingProperties, materializer.context, complexInstance);
atomProperty.MaterializedValue = complexInstance;
}
}
else
{
MaterializeDataValue(property.NullablePropertyType, atomProperty, materializer.context);
}
properties = atomProperty.Properties;
result = atomProperty.MaterializedValue;
}
expectedType = property.PropertyType;
}
return result;
}
///
/// Ensures that an entry of is
/// available on the specified .
///
/// Materilizer used for logging.
/// Entry to ensure.
/// Required type.
///
/// As the 'Projection' suffix suggests, this method should only
/// be used during projection operations; it purposefully avoid
/// "source tree" type usage and POST reply entry resolution.
///
internal static void ProjectionEnsureEntryAvailableOfType(AtomMaterializer materializer, AtomEntry entry, Type requiredType)
{
Debug.Assert(materializer != null, "materializer != null");
Debug.Assert(entry != null, "entry != null");
Debug.Assert(
materializer.targetInstance == null,
"materializer.targetInstance == null -- projection shouldn't have a target instance set; that's only used for POST replies");
if (entry.EntityHasBeenResolved)
{
if (!requiredType.IsAssignableFrom(entry.ResolvedObject.GetType()))
{
throw new InvalidOperationException(
"Expecting type '" + requiredType + "' for '" + entry.Identity + "', but found " +
"a previously created instance of type '" + entry.ResolvedObject.GetType());
}
// Just check the type
return;
}
if (entry.Identity == null)
{
throw Error.InvalidOperation(Strings.Deserialize_MissingIdElement);
}
if (!materializer.TryResolveAsCreated(entry) &&
!materializer.TryResolveFromContext(entry, requiredType))
{
// The type is always required, so skip ResolveByCreating.
materializer.ResolveByCreatingWithType(entry, requiredType);
}
else
{
if (!requiredType.IsAssignableFrom(entry.ResolvedObject.GetType()))
{
throw Error.InvalidOperation(Strings.Deserialize_Current(requiredType, entry.ResolvedObject.GetType()));
}
}
}
/// Materializes an entry with no special selection.
/// Materializer under which materialization should take place.
/// Entry with object to materialize.
/// Expected type for the entry.
/// The materialized instance.
internal static object DirectMaterializePlan(AtomMaterializer materializer, AtomEntry entry, Type expectedEntryType)
{
materializer.Materialize(entry, expectedEntryType, true);
return entry.ResolvedObject;
}
/// Materializes an entry without including in-lined expanded links.
/// Materializer under which materialization should take place.
/// Entry with object to materialize.
/// Expected type for the entry.
/// The materialized instance.
internal static object ShallowMaterializePlan(AtomMaterializer materializer, AtomEntry entry, Type expectedEntryType)
{
materializer.Materialize(entry, expectedEntryType, false);
return entry.ResolvedObject;
}
///
/// Validates the specified matches
/// the parsed .
///
/// Property as understood by the type system.
/// Property as parsed.
internal static void ValidatePropertyMatch(ClientType.ClientProperty property, AtomContentProperty atomProperty)
{
Debug.Assert(property != null, "property != null");
Debug.Assert(atomProperty != null, "atomProperty != null");
if (property.IsKnownType && (atomProperty.Feed != null || atomProperty.Entry != null))
{
throw Error.InvalidOperation(Strings.Deserialize_MismatchAtomLinkLocalSimple);
}
if (atomProperty.Feed != null && property.CollectionType == null)
{
throw Error.InvalidOperation(Strings.Deserialize_MismatchAtomLinkFeedPropertyNotCollection(property.PropertyName));
}
if (atomProperty.Entry != null && property.CollectionType != null)
{
throw Error.InvalidOperation(Strings.Deserialize_MismatchAtomLinkEntryPropertyIsCollection(property.PropertyName));
}
}
#endregion Projection support.
#region Internal methods.
/// Reads the next value from the input content.
/// true if another value is available after reading; false otherwise.
///
/// After invocation, the currentValue field (and CurrentValue property) will
/// reflect the value materialized from the parser; possibly null if the
/// result is true (for null values); always null if the result is false.
///
internal bool Read()
{
this.currentValue = null;
// links from last entry should be cleared
this.nextLinkTable.Clear();
while (this.parser.Read())
{
Debug.Assert(
this.parser.DataKind != AtomDataKind.None,
"parser.DataKind != AtomDataKind.None -- otherwise parser.Read() didn't update its state");
Debug.Assert(
this.parser.DataKind != AtomDataKind.Finished,
"parser.DataKind != AtomDataKind.Finished -- otherwise parser.Read() shouldn't have returned true");
switch (this.parser.DataKind)
{
case AtomDataKind.Feed:
case AtomDataKind.FeedCount:
// Nothing to do.
break;
case AtomDataKind.Entry:
Debug.Assert(
this.parser.CurrentEntry != null,
"parser.CurrentEntry != null -- otherwise parser.DataKind shouldn't be Entry");
this.CurrentEntry.ResolvedObject = this.TargetInstance;
this.currentValue = this.materializeEntryPlan.Run(this, this.CurrentEntry, this.expectedType);
return true;
case AtomDataKind.PagingLinks:
// encountered paging links - this is the OUTER-MOST feed
// we don't need to do anything here, the CurrentFeed.NextLink should
// have already been set (either to an Uri or to null)
break;
default:
Debug.Assert(
this.parser.DataKind == AtomDataKind.Custom,
"parser.DataKind == AtomDataKind.Custom -- otherwise AtomMaterializer.Read switch is missing a case");
// V1 Compatibility Note:
// Top-level primitive types are allowed to have mixed content,
// and must be parsed with ReadCustomElementString.
//
// For complex types, we'll instead use the regular property
// reading path, with will throw on mixed content.
Type underlyingExpectedType = Nullable.GetUnderlyingType(this.expectedType) ?? this.expectedType;
ClientType targetType = ClientType.Create(underlyingExpectedType);
if (ClientConvert.IsKnownType(underlyingExpectedType))
{
// primitive type - we don't care about the namespace as per V1, but it should be in XmlConstants.DataWebNamespace ("http://schemas.microsoft.com/ado/2007/08/dataservices")
string elementText = this.parser.ReadCustomElementString();
if (elementText != null)
{
this.currentValue = ClientConvert.ChangeType(elementText, underlyingExpectedType);
}
return true;
}
else if (!targetType.IsEntityType && this.parser.IsDataWebElement)
{
// complex type - the namespace URI must be in dataweb namespace
AtomContentProperty property = this.parser.ReadCurrentPropertyValue();
if (property == null || property.IsNull)
{
this.currentValue = null;
}
else
{
this.currentValue = targetType.CreateInstance();
MaterializeDataValues(targetType, property.Properties, this.ignoreMissingProperties, this.context);
ApplyDataValues(targetType, property.Properties, this.ignoreMissingProperties, this.context, this.currentValue);
}
return true;
}
break;
}
}
Debug.Assert(this.parser.DataKind == AtomDataKind.Finished, "parser.DataKind == AtomDataKind.None");
Debug.Assert(this.parser.CurrentEntry == null, "parser.Current == null");
return false;
}
/// Helper method for constructor of DataServiceCollection.
/// Element type for collection.
/// The enumerable which has the continuation on it.
/// The DataServiceCollection to apply the continuation to.
internal void PropagateContinuation(IEnumerable from, DataServiceCollection to)
{
DataServiceQueryContinuation continuation;
if (this.nextLinkTable.TryGetValue(from, out continuation))
{
this.nextLinkTable.Add(to, continuation);
Util.SetNextLinkForCollection(to, continuation);
}
}
#endregion Internal methods.
#region Private methods.
///
/// Checks that the specified isn't null.
///
/// Entry to check.
/// Name of entry being accessed.
private static void CheckEntryToAccessNotNull(AtomEntry entry, string name)
{
Debug.Assert(entry != null, "entry != null");
Debug.Assert(name != null, "name != null");
if (entry.IsNull)
{
throw new NullReferenceException(Strings.AtomMaterializer_EntryToAccessIsNull(name));
}
}
/// Creates an entry materialization plan for a given projection.
/// Query components for plan to materialize.
/// A materialization plan.
private static ProjectionPlan CreatePlan(QueryComponents queryComponents)
{
// Can we have a primitive property as well?
LambdaExpression projection = queryComponents.Projection;
ProjectionPlan result;
if (projection == null)
{
result = CreatePlanForDirectMaterialization(queryComponents.LastSegmentType);
}
else
{
result = ProjectionPlanCompiler.CompilePlan(projection, queryComponents.NormalizerRewrites);
result.LastSegmentType = queryComponents.LastSegmentType;
}
return result;
}
/// Creates an entry materialization plan that is payload-driven.
/// Segment type for the entry to materialize (typically last of URI in query).
/// A payload-driven materialization plan.
private static ProjectionPlan CreatePlanForDirectMaterialization(Type lastSegmentType)
{
ProjectionPlan result = new ProjectionPlan();
result.Plan = AtomMaterializerInvoker.DirectMaterializePlan;
result.ProjectedType = lastSegmentType;
result.LastSegmentType = lastSegmentType;
return result;
}
/// Creates an entry materialization plan that is payload-driven and does not traverse expanded links.
/// Segment type for the entry to materialize (typically last of URI in query).
/// A payload-driven materialization plan.
private static ProjectionPlan CreatePlanForShallowMaterialization(Type lastSegmentType)
{
ProjectionPlan result = new ProjectionPlan();
result.Plan = AtomMaterializerInvoker.ShallowMaterializePlan;
result.ProjectedType = lastSegmentType;
result.LastSegmentType = lastSegmentType;
return result;
}
/// Gets a delegate that can be invoked to add an item to a collection of the specified type.
/// Type of list to use.
/// The delegate to invoke.
private static Action GetAddToCollectionDelegate(Type listType)
{
Debug.Assert(listType != null, "listType != null");
Type listElementType;
MethodInfo addMethod = ClientType.GetAddToCollectionMethod(listType, out listElementType);
ParameterExpression list = Expression.Parameter(typeof(object), "list");
ParameterExpression item = Expression.Parameter(typeof(object), "element");
Expression body = Expression.Call(Expression.Convert(list, listType), addMethod, Expression.Convert(item, listElementType));
#if ASTORIA_LIGHT
LambdaExpression lambda = ExpressionHelpers.CreateLambda(body, list, item);
#else
LambdaExpression lambda = Expression.Lambda(body, list, item);
#endif
return (Action)lambda.Compile();
}
///
/// Gets or creates a collection property on the specified .
///
/// Instance on which to get/create the collection.
/// Collection property on the .
/// Type to use as a collection, possibly null.
///
/// The collection corresponding to the specified ;
/// never null.
///
private static object GetOrCreateCollectionProperty(object instance, ClientType.ClientProperty property, Type collectionType)
{
Debug.Assert(instance != null, "instance != null");
Debug.Assert(property != null, "property != null");
Debug.Assert(property.CollectionType != null, "property.CollectionType != null -- otherwise property isn't a collection");
// NOTE: in V1, we would have instantiated nested objects before setting them.
object result;
result = property.GetValue(instance);
if (result == null)
{
if (collectionType == null)
{
collectionType = property.PropertyType;
if (collectionType.IsInterface)
{
collectionType = typeof(System.Collections.ObjectModel.Collection<>).MakeGenericType(property.CollectionType);
}
}
result = Activator.CreateInstance(collectionType);
property.SetValue(instance, result, property.PropertyName, false /* add */);
}
Debug.Assert(result != null, "result != null -- otherwise GetOrCreateCollectionProperty didn't fall back to creation");
return result;
}
/// Materializes the result of a projection into a list.
/// Materializer to use for the operation.
/// Target list.
/// Expected type for nested object.
/// Entries to materialize from.
///
/// This method supports projections and as such does shallow payload-driven
/// materialization of entities.
///
private static void MaterializeToList(
AtomMaterializer materializer,
IEnumerable list,
Type nestedExpectedType,
IEnumerable entries)
{
Debug.Assert(materializer != null, "materializer != null");
Debug.Assert(list != null, "list != null");
Action addMethod = GetAddToCollectionDelegate(list.GetType());
foreach (AtomEntry feedEntry in entries)
{
if (!feedEntry.EntityHasBeenResolved)
{
materializer.Materialize(feedEntry, nestedExpectedType, /* includeLinks */ false);
}
addMethod(list, feedEntry.ResolvedObject);
}
}
/// Materializes a single value.
/// Type of value to set.
/// Property holding value.
/// Context under which value will be set.
/// true if the value was set; false if it wasn't (typically because it's a complex value).
private static bool MaterializeDataValue(Type type, AtomContentProperty atomProperty, DataServiceContext context)
{
Debug.Assert(type != null, "type != null");
Debug.Assert(atomProperty != null, "atomProperty != null");
Debug.Assert(context != null, "context != null");
string propertyTypeName = atomProperty.TypeName;
string propertyValueText = atomProperty.Text;
ClientType nestedElementType = null;
Type underlyingType = Nullable.GetUnderlyingType(type) ?? type;
bool knownType = ClientConvert.IsKnownType(underlyingType);
if (!knownType)
{
nestedElementType = MaterializeAtom.GetEntryClientType(propertyTypeName, context, type, true);
Debug.Assert(nestedElementType != null, "nestedElementType != null -- otherwise ReadTypeAttribute (or someone!) should throw");
knownType = ClientConvert.IsKnownType(nestedElementType.ElementType);
}
if (knownType)
{
if (atomProperty.IsNull)
{
if (!ClientType.CanAssignNull(type))
{
throw new InvalidOperationException(Strings.AtomMaterializer_CannotAssignNull(atomProperty.Name, type.FullName));
}
atomProperty.MaterializedValue = null;
return true;
}
else
{
object value = propertyValueText;
if (propertyValueText != null)
{
value = ClientConvert.ChangeType(propertyValueText, (null != nestedElementType ? nestedElementType.ElementType : underlyingType));
}
atomProperty.MaterializedValue = value;
return true;
}
}
return false;
}
///
/// Materializes the primitive data vlues in the given list of .
///
/// Actual type for properties being materialized.
/// List of values to materialize.
///
/// Whether properties missing from the client types should be ignored.
///
/// Data context, used for type resolution.
///
/// Values are materialized in-place withi each
/// instance.
///
private static void MaterializeDataValues(
ClientType actualType,
List values,
bool ignoreMissingProperties,
DataServiceContext context)
{
Debug.Assert(actualType != null, "actualType != null");
Debug.Assert(values != null, "values != null");
Debug.Assert(context != null, "context != null");
foreach (var atomProperty in values)
{
string propertyName = atomProperty.Name;
var property = actualType.GetProperty(propertyName, ignoreMissingProperties); // may throw
if (property == null)
{
continue;
}
if (atomProperty.Feed == null && atomProperty.Entry == null)
{
bool materialized = MaterializeDataValue(property.NullablePropertyType, atomProperty, context);
if (!materialized && property.CollectionType != null)
{
// client object property is collection implying nested collection of complex objects
throw Error.NotSupported(Strings.ClientType_CollectionOfNonEntities);
}
}
}
}
/// Applies a data value to the specified .
/// Type to which a property value will be applied.
/// Property with value to apply.
///
/// Whether properties missing from the client types should be ignored.
///
/// Data context, used for type resolution.
/// Instance on which value will be applied.
private static void ApplyDataValue(ClientType type, AtomContentProperty property, bool ignoreMissingProperties, DataServiceContext context, object instance)
{
Debug.Assert(type != null, "type != null");
Debug.Assert(property != null, "property != null");
Debug.Assert(context != null, "context != null");
Debug.Assert(instance != null, "instance != context");
var prop = type.GetProperty(property.Name, ignoreMissingProperties);
if (prop == null)
{
return;
}
if (property.Properties != null)
{
if (prop.IsKnownType ||
ClientConvert.IsKnownType(MaterializeAtom.GetEntryClientType(property.TypeName, context, prop.PropertyType, true).ElementType))
{
// The error message is a bit odd, but it's compatible with V1.
throw Error.InvalidOperation(Strings.Deserialize_ExpectingSimpleValue);
}
// Complex type.
bool needToSet = false;
ClientType complexType = ClientType.Create(prop.PropertyType);
object complexInstance = prop.GetValue(instance);
if (complexInstance == null)
{
complexInstance = complexType.CreateInstance();
needToSet = true;
}
MaterializeDataValues(complexType, property.Properties, ignoreMissingProperties, context);
ApplyDataValues(complexType, property.Properties, ignoreMissingProperties, context, complexInstance);
if (needToSet)
{
prop.SetValue(instance, complexInstance, property.Name, true /* allowAdd? */);
}
}
else
{
prop.SetValue(instance, property.MaterializedValue, property.Name, true /* allowAdd? */);
}
}
///
/// Applies the values of the specified to a
/// given .
///
/// Type to which properties will be applied.
/// Properties to assign to the specified .
///
/// Whether properties missing from the client types should be ignored.
///
/// Data context, used for type resolution.
/// Instance on which values will be applied.
private static void ApplyDataValues(ClientType type, IEnumerable properties, bool ignoreMissingProperties, DataServiceContext context, object instance)
{
Debug.Assert(type != null, "type != null");
Debug.Assert(properties != null, "properties != null");
Debug.Assert(context != null, "properties != context");
Debug.Assert(instance != null, "instance != context");
foreach (var p in properties)
{
ApplyDataValue(type, p, ignoreMissingProperties, context, instance);
}
}
///
/// Sets a value on the property identified by the given .
///
/// Starting list of properties to set.
/// List of paths, delimited by '/' characters.
/// Value to set in text form.
/// Name of type of value to set.
private static void SetValueOnPath(List values, string path, string value, string typeName)
{
Debug.Assert(values != null, "values != null");
Debug.Assert(path != null, "path != null");
bool existing = true;
AtomContentProperty property = null;
foreach (string step in path.Split('/'))
{
if (values == null)
{
Debug.Assert(property != null, "property != null -- if values is null then this isn't the first step");
property.EnsureProperties();
values = property.Properties;
}
property = values.Where(v => v.Name == step).FirstOrDefault();
if (property == null)
{
AtomContentProperty newProperty = new AtomContentProperty();
existing = false;
newProperty.Name = step;
values.Add(newProperty);
property = newProperty;
}
else
{
// If any property along the way was already marked as null in the payload,
// we can exit early as there will be nothing to do in any
// further nested types.
if (property.IsNull)
{
//
return;
}
}
values = property.Properties;
}
Debug.Assert(property != null, "property != null -- property path should have at least one segment");
// Content wins, therefore only set values if the property didn't exist before.
if (existing == false)
{
property.TypeName = typeName;
property.Text = value;
}
}
///
/// Applies all available Entity Property Mappings on the specified .
///
/// ATOM entry on which to apply entity property mappings.
/// Client type used to read EPM info from.
private static void ApplyEntityPropertyMappings(AtomEntry entry, ClientType entryType)
{
Debug.Assert(entry != null, "entry != null");
Debug.Assert(entry.Tag is XElement, "entry.Tag is XElement");
Debug.Assert(entryType != null, "entryType != null -- othewise how would we know to apply property mappings (note that for projections entry.ActualType may be different that entryType)?");
Debug.Assert(!entry.EntityPropertyMappingsApplied, "!entry.EntityPropertyMappingsApplied -- EPM should happen only once per entry");
if (entryType.HasEntityPropertyMappings)
{
// NOTE: this is an XElement-based version of the code.
// There is a streaming version which has been since removed at:
// - %sdxroot%\ndp\fx\src\DataWeb\Client\System\Data\Services\Client\Epm\EpmContentDeserializer.cs
// - %sdxroot%\ndp\fx\src\DataWeb\Client\System\Data\Services\Client\Epm\EpmReaderState.cs
XElement entryElement = entry.Tag as XElement;
Debug.Assert(entryElement != null, "entryElement != null");
ApplyEntityPropertyMappings(entry, entryElement, entryType.EpmTargetTree.SyndicationRoot);
ApplyEntityPropertyMappings(entry, entryElement, entryType.EpmTargetTree.NonSyndicationRoot);
}
entry.EntityPropertyMappingsApplied = true;
}
///
/// Applies Entity Property Mappings on the specified
/// given a root.
///
/// ATOM entry on which to apply entity property mappings.
/// XML tree for the specified.
/// Target for mapping.
private static void ApplyEntityPropertyMappings(AtomEntry entry, XElement entryElement, EpmTargetPathSegment target)
{
Debug.Assert(target != null, "target != null");
Debug.Assert(!target.HasContent, "!target.HasContent");
//
Stack segments = new Stack();
Stack elements = new Stack();
segments.Push(target);
elements.Push(entryElement);
while (segments.Count > 0)
{
System.Data.Services.Common.EpmTargetPathSegment segment = segments.Pop();
XElement element = elements.Pop();
if (segment.HasContent)
{
var node = element.Nodes().Where(n => n.NodeType == XmlNodeType.Text || n.NodeType == XmlNodeType.SignificantWhitespace).FirstOrDefault();
string elementValue = (node == null) ? null : ((XText)node).Value;
Debug.Assert(segment.EpmInfo != null, "segment.EpmInfo != null -- otherwise segment.HasValue should be false");
string path = segment.EpmInfo.Attribute.SourcePath;
string typeName = (string)element.Attribute(XName.Get(XmlConstants.AtomTypeAttributeName, XmlConstants.DataWebMetadataNamespace));
//
SetValueOnPath(entry.DataValues, path, elementValue, typeName);
}
foreach (var item in segment.SubSegments)
{
if (item.IsAttribute)
{
string localName = item.SegmentName.Substring(1);
var attribute = element.Attribute(XName.Get(localName, item.SegmentNamespaceUri));
if (attribute != null)
{
// NOTE: nulls can't be expressed on the attribute; null values
// are required to always be part of the main content instead.
SetValueOnPath(entry.DataValues, item.EpmInfo.Attribute.SourcePath, attribute.Value, null);
}
}
else
{
var child = element.Element(XName.Get(item.SegmentName, item.SegmentNamespaceUri));
if (child != null)
{
segments.Push(item);
elements.Push(child);
}
}
}
Debug.Assert(segments.Count == elements.Count, "segments.Count == elements.Count -- otherwise they're out of [....]");
}
}
/// Gets a property from the specified list, throwing if not found.
/// List to get value from.
/// Property name to look up.
/// The specified property (never null).
private static AtomContentProperty GetPropertyOrThrow(List properties, string propertyName)
{
AtomContentProperty atomProperty = null;
if (properties != null)
{
atomProperty = properties.Where(p => p.Name == propertyName).FirstOrDefault();
}
if (atomProperty == null)
{
throw new InvalidOperationException(Strings.AtomMaterializer_PropertyMissing(propertyName));
}
Debug.Assert(atomProperty != null, "atomProperty != null");
return atomProperty;
}
/// Gets a property from the specified , throwing if not found.
/// Entry to get value from.
/// Property name to look up.
/// The specified property (never null).
private static AtomContentProperty GetPropertyOrThrow(AtomEntry entry, string propertyName)
{
AtomContentProperty atomProperty = null;
var properties = entry.DataValues;
if (properties != null)
{
atomProperty = properties.Where(p => p.Name == propertyName).FirstOrDefault();
}
if (atomProperty == null)
{
throw new InvalidOperationException(Strings.AtomMaterializer_PropertyMissingFromEntry(propertyName, entry.Identity));
}
Debug.Assert(atomProperty != null, "atomProperty != null");
return atomProperty;
}
/// Merges a list into the property of a given .
/// Entry to merge into.
/// Property on entry to merge into.
/// List of materialized values.
/// Next link for feed from which the materialized values come from.
/// Projection plan for the list.
///
/// This method will handle entries that shouldn't be updated correctly.
///
private void MergeLists(AtomEntry entry, ClientType.ClientProperty property, IEnumerable list, Uri nextLink, ProjectionPlan plan)
{
Debug.Assert(entry != null, "entry != null");
Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null");
Debug.Assert(property != null, "property != null");
Debug.Assert(entry != null, "entry != null");
Debug.Assert(plan != null || nextLink == null, "plan != null || nextLink == null");
// Simple case: the list is of the target type, and the resolved entity
// has null; we can simply assign the collection. No merge required.
if (entry.ShouldUpdateFromPayload &&
property.NullablePropertyType == list.GetType() &&
property.GetValue(entry.ResolvedObject) == null)
{
property.SetValue(entry.ResolvedObject, list, property.PropertyName, false /* allowAdd */);
this.FoundNextLinkForCollection(list, nextLink, plan);
foreach (object item in list)
{
this.log.AddedLink(entry, property.PropertyName, item);
}
return;
}
this.ApplyItemsToCollection(entry, property, list, nextLink, plan);
}
/// Tries to resolve the object as the target one in a POST refresh.
/// Entry to resolve.
/// true if the entity was resolved; false otherwise.
private bool TryResolveAsTarget(AtomEntry entry)
{
if (entry.ResolvedObject == null)
{
return false;
}
// The only case when the entity hasn't been resolved but
// it has already been set is when the target instance
// was set directly to refresh a POST.
Debug.Assert(
entry.ResolvedObject == this.TargetInstance,
"entry.ResolvedObject == this.TargetInstance -- otherwise there we ResolveOrCreateInstance more than once on the same entry");
Debug.Assert(
this.mergeOption == MergeOption.OverwriteChanges || this.mergeOption == MergeOption.PreserveChanges,
"MergeOption.OverwriteChanges and MergeOption.PreserveChanges are the only expected values during SaveChanges");
entry.ActualType = ClientType.Create(entry.ResolvedObject.GetType());
this.log.FoundTargetInstance(entry);
entry.ShouldUpdateFromPayload = this.mergeOption == MergeOption.PreserveChanges ? false : true;
entry.EntityHasBeenResolved = true;
return true;
}
/// Tries to resolve the object as one from the context (only if tracking is enabled).
/// Entry to resolve.
/// Expected entry type for the specified .
/// true if the entity was resolved; false otherwise.
private bool TryResolveFromContext(AtomEntry entry, Type expectedEntryType)
{
// We should either create a new instance or grab one from the context.
bool tracking = this.mergeOption != MergeOption.NoTracking;
if (tracking)
{
EntityStates state;
entry.ResolvedObject = this.context.TryGetEntity(entry.Identity, entry.ETagText, this.mergeOption, out state);
if (entry.ResolvedObject != null)
{
if (!expectedEntryType.IsInstanceOfType(entry.ResolvedObject))
{
throw Error.InvalidOperation(Strings.Deserialize_Current(expectedEntryType, entry.ResolvedObject.GetType()));
}
entry.ActualType = ClientType.Create(entry.ResolvedObject.GetType());
entry.EntityHasBeenResolved = true;
// Note that deleted items will have their properties overwritten even
// if PreserveChanges is used as a merge option.
entry.ShouldUpdateFromPayload =
this.mergeOption == MergeOption.OverwriteChanges ||
(this.mergeOption == MergeOption.PreserveChanges && state == EntityStates.Unchanged) ||
(this.mergeOption == MergeOption.PreserveChanges && state == EntityStates.Deleted);
this.log.FoundExistingInstance(entry);
return true;
}
}
return false;
}
/// "Resolved" the entity in the by instantiating it.
/// Entry to resolve.
/// Type to create.
///
/// After invocation, entry.ResolvedObject is exactly of type .
///
private void ResolveByCreatingWithType(AtomEntry entry, Type type)
{
Debug.Assert(
entry.ResolvedObject == null,
"entry.ResolvedObject == null -- otherwise we're about to overwrite - should never be called");
entry.ActualType = ClientType.Create(type);
entry.ResolvedObject = Activator.CreateInstance(type);
entry.CreatedByMaterializer = true;
entry.ShouldUpdateFromPayload = true;
entry.EntityHasBeenResolved = true;
this.log.CreatedInstance(entry);
}
/// "Resolved" the entity in the by instantiating it.
/// Entry to resolve.
/// Type expected by the projection.
///
/// This method allows the expected entry to be overriden by either a callback
/// or by the type read by the parser.
///
private void ResolveByCreating(AtomEntry entry, Type expectedEntryType)
{
Debug.Assert(
entry.ResolvedObject == null,
"entry.ResolvedObject == null -- otherwise we're about to overwrite - should never be called");
ClientType actualType = MaterializeAtom.GetEntryClientType(entry.TypeName, this.context, expectedEntryType, true);
// We can't assert that actualType.HasKeys, as there are cases where keys won't be defined, and we still
// support deserializing them.
Debug.Assert(actualType != null, "actualType != null -- otherwise ClientType.Create returned a null value");
this.ResolveByCreatingWithType(entry, actualType.ElementType);
}
/// Tries to resolve the object from those created in this materialization session.
/// Entry to resolve.
/// true if the entity was resolved; false otherwise.
private bool TryResolveAsCreated(AtomEntry entry)
{
AtomEntry existingEntry;
if (!this.log.TryResolve(entry, out existingEntry))
{
return false;
}
Debug.Assert(
existingEntry.ResolvedObject != null,
"existingEntry.ResolvedObject != null -- how did it get there otherwise?");
entry.ActualType = existingEntry.ActualType;
entry.ResolvedObject = existingEntry.ResolvedObject;
entry.CreatedByMaterializer = existingEntry.CreatedByMaterializer;
entry.ShouldUpdateFromPayload = existingEntry.ShouldUpdateFromPayload;
entry.EntityHasBeenResolved = true;
return true;
}
/// Resolved or creates an instance on the specified .
/// Entry on which to resolve or create an instance.
/// Expected type for the .
///
/// After invocation, the ResolvedObject value of the
/// will be assigned, along with the ActualType value.
///
private void ResolveOrCreateInstance(AtomEntry entry, Type expectedEntryType)
{
Debug.Assert(entry != null, "entry != null");
Debug.Assert(expectedEntryType != null, "expectedEntryType != null");
Debug.Assert(entry.EntityHasBeenResolved == false, "entry.EntityHasBeenResolved == false");
// This will be the case when TargetInstance has been set.
if (!this.TryResolveAsTarget(entry))
{
if (entry.Identity == null)
{
throw Error.InvalidOperation(Strings.Deserialize_MissingIdElement);
}
if (!this.TryResolveAsCreated(entry))
{
if (!this.TryResolveFromContext(entry, expectedEntryType))
{
this.ResolveByCreating(entry, expectedEntryType);
}
}
}
Debug.Assert(entry.ActualType != null, "entry.ActualType != null");
Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null");
Debug.Assert(entry.EntityHasBeenResolved, "entry.EntityHasBeenResolved");
return;
}
///
/// Applies the values of a nested to the collection
/// of the specified .
///
/// Entry with collection to be modified.
/// Collection property on the entry.
/// Values to apply onto the collection.
/// Whether links that are expanded should be materialized.
private void ApplyFeedToCollection(
AtomEntry entry,
ClientType.ClientProperty property,
AtomFeed feed,
bool includeLinks)
{
Debug.Assert(entry != null, "entry != null");
Debug.Assert(property != null, "property != null");
Debug.Assert(feed != null, "feed != null");
ClientType collectionType = ClientType.Create(property.CollectionType);
foreach (AtomEntry feedEntry in feed.Entries)
{
this.Materialize(feedEntry, collectionType.ElementType, includeLinks);
}
ProjectionPlan continuationPlan = includeLinks ? CreatePlanForDirectMaterialization(property.CollectionType) : CreatePlanForShallowMaterialization(property.CollectionType);
this.ApplyItemsToCollection(entry, property, feed.Entries.Select(e => e.ResolvedObject), feed.NextLink, continuationPlan);
}
///
/// Applies the values of the enumeration to the
/// of the specified .
///
/// Entry with collection to be modified.
/// Collection property on the entry.
/// Values to apply onto the collection.
/// Next link for collection continuation.
/// Projection plan for collection continuation.
private void ApplyItemsToCollection(
AtomEntry entry,
ClientType.ClientProperty property,
IEnumerable items,
Uri nextLink,
ProjectionPlan continuationPlan)
{
Debug.Assert(entry != null, "entry != null");
Debug.Assert(property != null, "property != null");
Debug.Assert(items != null, "items != null");
object collection = entry.ShouldUpdateFromPayload ? GetOrCreateCollectionProperty(entry.ResolvedObject, property, null) : null;
ClientType collectionType = ClientType.Create(property.CollectionType);
foreach (object item in items)
{
if (!collectionType.ElementType.IsAssignableFrom(item.GetType()))
{
string message = Strings.AtomMaterializer_EntryIntoCollectionMismatch(
item.GetType().FullName,
collectionType.ElementType.FullName);
throw new InvalidOperationException(message);
}
if (entry.ShouldUpdateFromPayload)
{
property.SetValue(collection, item, property.PropertyName, true /* allowAdd? */);
this.log.AddedLink(entry, property.PropertyName, item);
}
}
if (entry.ShouldUpdateFromPayload)
{
this.FoundNextLinkForCollection(collection as IEnumerable, nextLink, continuationPlan);
}
else
{
this.FoundNextLinkForUnmodifiedCollection(property.GetValue(entry.ResolvedObject) as IEnumerable);
}
// Remove the extra items from the collection as necessary.
if (this.mergeOption == MergeOption.OverwriteChanges || this.mergeOption == MergeOption.PreserveChanges)
{
var itemsToRemove =
from x in this.context.GetLinks(entry.ResolvedObject, property.PropertyName)
where MergeOption.OverwriteChanges == this.mergeOption || EntityStates.Added != x.State
select x.Target;
itemsToRemove = itemsToRemove.Except(EnumerateAsElementType(items));
foreach (var item in itemsToRemove)
{
if (collection != null)
{
property.RemoveValue(collection, item);
}
this.log.RemovedLink(entry, property.PropertyName, item);
}
}
}
/// Records the fact that a rel='next' link was found for the specified .
/// Collection to add link to.
/// Link (possibly null).
/// Projection plan for the collection (null allowed only if link is null).
private void FoundNextLinkForCollection(IEnumerable collection, Uri link, ProjectionPlan plan)
{
Debug.Assert(plan != null || link == null, "plan != null || link == null");
if (collection != null && !this.nextLinkTable.ContainsKey(collection))
{
DataServiceQueryContinuation continuation = DataServiceQueryContinuation.Create(link, plan);
this.nextLinkTable.Add(collection, continuation);
Util.SetNextLinkForCollection(collection, continuation);
}
}
/// Records the fact that a was found but won't be modified.
/// Collection to add link to.
private void FoundNextLinkForUnmodifiedCollection(IEnumerable collection)
{
if (collection != null && !this.nextLinkTable.ContainsKey(collection))
{
this.nextLinkTable.Add(collection, null);
}
}
/// Materializes the specified .
/// Entry with object to materialize.
/// Expected type for the entry.
/// Whether links that are expanded should be materialized.
/// This is a payload-driven materialization process.
private void Materialize(AtomEntry entry, Type expectedEntryType, bool includeLinks)
{
Debug.Assert(entry != null, "entry != null");
Debug.Assert(entry.DataValues != null, "entry.DataValues != null -- otherwise not correctly initialized");
Debug.Assert(
entry.ResolvedObject == null || entry.ResolvedObject == this.targetInstance,
"entry.ResolvedObject == null || entry.ResolvedObject == this.targetInstance -- otherwise getting called twice");
Debug.Assert(expectedEntryType != null, "expectedType != null");
// ResolvedObject will already be assigned when we have a TargetInstance, for example.
this.ResolveOrCreateInstance(entry, expectedEntryType);
Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null -- otherwise ResolveOrCreateInstnace didn't do its job");
this.MaterializeResolvedEntry(entry, includeLinks);
}
/// Materializes the specified .
/// Entry with object to materialize.
/// Whether links that are expanded should be materialized.
/// This is a payload-driven materialization process.
private void MaterializeResolvedEntry(AtomEntry entry, bool includeLinks)
{
Debug.Assert(entry != null, "entry != null");
Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null -- otherwise not resolved/created!");
ClientType actualType = entry.ActualType;
if (!entry.EntityPropertyMappingsApplied)
{
// EPM should happen only once per entry
ApplyEntityPropertyMappings(entry, entry.ActualType);
}
// Note that even if ShouldUpdateFromPayload is false, we will still be creating
// nested instances (but not their links), so they show up in the data context
// entries. This keeps this code compatible with V1 behavior.
MaterializeDataValues(actualType, entry.DataValues, this.ignoreMissingProperties, this.context);
foreach (var e in entry.DataValues)
{
var prop = actualType.GetProperty(e.Name, this.ignoreMissingProperties);
if (prop == null)
{
continue;
}
if (entry.ShouldUpdateFromPayload == false && e.Entry == null && e.Feed == null)
{
// Skip non-links for ShouldUpdateFromPayload.
continue;
}
if (!includeLinks && (e.Entry != null || e.Feed != null))
{
continue;
}
ValidatePropertyMatch(prop, e);
AtomFeed feedValue = e.Feed;
if (feedValue != null)
{
Debug.Assert(includeLinks, "includeLinks -- otherwise we shouldn't be materializing this entry");
this.ApplyFeedToCollection(entry, prop, feedValue, includeLinks);
}
else if (e.Entry != null)
{
if (!e.IsNull)
{
Debug.Assert(includeLinks, "includeLinks -- otherwise we shouldn't be materializing this entry");
this.Materialize(e.Entry, prop.PropertyType, includeLinks);
}
if (entry.ShouldUpdateFromPayload)
{
prop.SetValue(entry.ResolvedObject, e.Entry.ResolvedObject, e.Name, true /* allowAdd? */);
this.log.SetLink(entry, prop.PropertyName, e.Entry.ResolvedObject);
}
}
else
{
Debug.Assert(entry.ShouldUpdateFromPayload, "entry.ShouldUpdateFromPayload -- otherwise we're about to set a property we shouldn't");
ApplyDataValue(actualType, e, this.ignoreMissingProperties, this.context, entry.ResolvedObject);
}
}
Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null -- otherwise we didn't do any useful work");
if (this.materializedObjectCallback != null)
{
this.materializedObjectCallback(entry.Tag, entry.ResolvedObject);
}
}
#endregion Private methods.
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.