AtomMaterializer.cs source code in C# .NET

Source code for the .NET framework in C#

                        

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.
                        

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK