Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / ndp / fx / src / DataWeb / Client / System / Data / Services / Client / MaterializeFromAtom.cs / 4 / MaterializeFromAtom.cs
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// materialize objects from an xml stream
//
//---------------------------------------------------------------------
namespace System.Data.Services.Client
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
///
/// materialize objects from an application/atom+xml stream
///
internal class MaterializeAtom : IDisposable, IEnumerable, IEnumerator
{
/// MergeOption captured from context so its static for the entire materialization
internal readonly MergeOption MergeOptionValue;
/// Options when deserializing properties to the target type.
private readonly bool ignoreMissingProperties;
/// Backreference to the context to allow type resolution.
private readonly DataServiceContext context;
/// DataNamespace captured from context so its static for the entire materialization
/// reference equality is important here
private readonly string dataNamespace;
/// base type of the object to create from the stream.
private readonly Type elementType;
/// when materializing a known type (like string)
/// <property> text-value </property>
private readonly bool expectingSingleValue;
/// source reader (may be running on top of a buffer or be the real reader)
private XmlReader reader;
/// untyped current object
private object current;
/// object being updated
private object inserting;
/// identity of new objects and associated etag per enumerator.MoveNext()
private Dictionary identityStack;
/// list of links to add
private List links;
/// debugging tool, # calls to ReadNext()
private int version;
/// has GetEnumerator been called?
private bool calledGetEnumerator;
/// stack of readers used when buffering intermediate entries (to create XElements and fire events)
private Stack stackedReaders;
///
/// output writer, set using reflection
///
#if DEBUG && !ASTORIA_LIGHT
private System.IO.TextWriter writer = new System.IO.StringWriter(System.Globalization.CultureInfo.InvariantCulture);
#else
#pragma warning disable 649
private System.IO.TextWriter writer;
#pragma warning restore 649
#endif
///
/// constructor
///
/// originating context
/// reader
/// element base type
/// merge option to use for this materialization pass
internal MaterializeAtom(DataServiceContext context, XmlReader reader, Type elementType, MergeOption mergeOption)
{
Debug.Assert(null != reader, "null XmlReader");
this.context = context;
this.dataNamespace = reader.Settings.NameTable.Add(context.DataNamespace);
this.elementType = elementType;
this.MergeOptionValue = mergeOption;
this.ignoreMissingProperties = context.IgnoreMissingProperties;
this.reader = reader;
this.expectingSingleValue = ClientConvert.IsKnownType(Nullable.GetUnderlyingType(elementType) ?? elementType);
}
/// atom parser state machine positions
private enum AtomParseState
{
/// None
None,
/// Feed
Feed,
/// Entry
Entry,
/// Content
Content,
/// Complex
Complex,
/// Property
PropertyContent,
/// Property
PropertyComplex, // SQLBUDT 572585
/// LinkFeed
LinkFeed,
/// LinkEntry
LinkEntry,
}
#region Current
///
/// untyped current object property
///
public object Current
{
get
{
return ClientConvert.VerifyCast(this.elementType, this.current);
}
}
#endregion
#region IDisposable
///
/// dispose
///
public void Dispose()
{
this.current = null;
this.inserting = null;
if (null != this.reader)
{
((IDisposable)this.reader).Dispose();
}
if (null != this.writer)
{
this.writer.Dispose();
}
}
#endregion
#region IEnumerable
///
/// as IEnumerable
///
/// this
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
this.CheckGetEnumerator();
return this;
}
#endregion
///
/// create the next object from the stream
///
/// false if stream is finished
public bool MoveNext()
{
this.current = null;
if (null != this.identityStack)
{
this.identityStack.Clear();
}
if (null != this.links)
{
this.links.Clear();
}
bool result = false;
if ((0 == this.version) && !this.expectingSingleValue && typeof(IEnumerable).IsAssignableFrom(this.elementType))
{
Type implementationType = ClientType.GetImplementationType(this.elementType, typeof(ICollection<>));
if (null != implementationType)
{
Type expectedType = implementationType.GetGenericArguments()[0]; // already know its IList<>
implementationType = this.elementType;
if (implementationType.IsInterface)
{
implementationType = typeof(System.Collections.ObjectModel.Collection<>).MakeGenericType(expectedType);
}
IList list = (IList)Activator.CreateInstance(implementationType);
object local = null;
EntityStates localState = 0;
while ((0 <= this.version) &&
this.reader.Read() &&
this.ReadNext(null, expectedType, AtomParseState.None, ref localState, ref local))
{
list.Add(local);
local = null;
}
Debug.Assert(this.version < 0, "should have completed the read");
this.current = list;
result = true;
}
}
if (null == this.current)
{
EntityStates localState = 0;
result = (0 <= this.version) &&
this.reader.Read() &&
this.ReadNext(null, this.elementType, AtomParseState.None, ref localState, ref this.current);
}
if (MergeOption.NoTracking != this.MergeOptionValue)
{
if (null != this.identityStack)
{
foreach (KeyValuePair entity in this.identityStack)
{
if (entity.Value.AttachToContext)
{
this.context.AttachTo(entity.Key, entity.Value.EditLink, entity.Value.ETag, entity.Value.Entity, false);
}
}
}
if (null != this.links)
{
foreach (LinkDescriptor link in this.links)
{
if (EntityStates.Added == link.State)
{ // Added implies collection
if ((EntityStates.Deleted == this.context.GetEntity(link.Target).State) ||
(EntityStates.Deleted == this.context.GetEntity(link.Source).State))
{
this.context.DeleteLink(link.Source, link.SourceProperty, link.Target);
}
else
{
this.context.AttachLink(link.Source, link.SourceProperty, link.Target, this.MergeOptionValue);
}
}
else if (EntityStates.Modified == link.State)
{ // Modified implies reference
object target = link.Target;
if (MergeOption.PreserveChanges == this.MergeOptionValue)
{
DataServiceContext.RelatedEnd end = this.context.GetLinks(link.Source, link.SourceProperty).FirstOrDefault();
if (null != end && null == end.TargetResouce)
{ // leave the SetLink(link.Source, link.SourceProperty, null)
continue;
}
if ((null != target) && (EntityStates.Deleted == this.context.GetEntity(target).State) ||
(EntityStates.Deleted == this.context.GetEntity(link.Source).State))
{
target = null;
}
}
this.context.AttachLink(link.Source, link.SourceProperty, target, this.MergeOptionValue);
}
else
{ // detach link
// Debug.Assert(
// (MergeOption.OverwriteChanges == this.MergeOptionValue) ||
// (MergeOption.PreserveChanges == this.MergeOptionValue &&
// EntityStates.Added != this.context.GetLink(link.Source, link.SourceProperty, link.Target).State),
// "only OverwriteChanges or PreserveChanges should remove existing links");
Debug.Assert(EntityStates.Detached == link.State, "not detached link");
this.context.DetachLink(link.Source, link.SourceProperty, link.Target);
}
}
}
}
return result;
}
///
/// Not supported.
///
/// Always thrown
void System.Collections.IEnumerator.Reset()
{
throw Error.NotSupported();
}
/// set the inserted object expected in the response
/// object being inserted that the response is looking for
internal void SetInsertingObject(object addedObject)
{
this.inserting = addedObject;
}
/// are the two values the same reference
/// value1
/// value2
/// true if they are the same reference
private static bool AreSame(string value1, string value2)
{
// bool result = Object.ReferenceEquals(value1, value2);
// Debug.Assert(result == (value1 == value2), "!NameTable - unable to do reference comparison on '" + value1 + "'");
// XElement uses a global name table which may have encountered
// our strings first and return a different instance than what was expected
bool result = (value1 == value2);
return result;
}
/// is the reader on contentNode where the localName and namespaceUri match
/// reader
/// localName
/// namespaceUri
/// true if localName and namespaceUri match reader current element
private static bool AreSame(XmlReader reader, string localName, string namespaceUri)
{
Debug.Assert((null != reader) && (null != localName) && (null != namespaceUri), "null");
return ((XmlNodeType.Element == reader.NodeType) || (XmlNodeType.EndElement == reader.NodeType)) &&
AreSame(reader.LocalName, localName) && AreSame(reader.NamespaceURI, namespaceUri);
}
/// verify the GetEnumerator can only be called once
private void CheckGetEnumerator()
{
if (this.calledGetEnumerator)
{
throw Error.NotSupported(Strings.DataServiceException_GeneralError);
}
this.calledGetEnumerator = true;
}
///
/// build up a single object result
///
/// what is the nested element type (collection support)
/// what is the expected base type for current object
/// what is the initial parse state
/// entity state
/// current object being populated
/// true if not at end of object
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506", Justification = "eventually need to rewrite")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "multipurpose variable")]
private bool ReadNext(ClientType currentType, Type expectedType, AtomParseState atom, ref EntityStates entityState, ref object currentValue)
{
Debug.Assert((null != this.reader) && !this.reader.EOF && (this.reader.NodeType != XmlNodeType.None), "bad XmlReader");
this.version++;
// object level variables
// elementType (this is our return point until the next MoveNext call)
string elementName = null;
string namespaceUri = null;
string etag = null;
Uri identity = null;
Uri editLink = null;
bool? mediaLinkEntry = null;
bool hasContentProperties = false;
XElement currentEntry = null;
ArraySet haveSetKeyProperty;
// property level variables
ClientType.ClientProperty property = null;
string propertyName = null;
bool hasValue = false;
ClientType nestedElementType = null;
object nestedValue = null;
bool setNestedValue = false;
#if ASTORIA_OPEN_OBJECT
object openProperties = null; // like currentValue, this doesn't need to be nulled out
#endif
if ((null != currentType) && currentType.HasKeys)
{
haveSetKeyProperty = new ArraySet(currentType.KeyCount);
}
else
{
haveSetKeyProperty = default(ArraySet);
}
do
{ // caller of ReadNext called this.reader.Read()
string propertyValue = null;
bool knownElement = false;
switch (this.reader.NodeType)
{
#region Element
case XmlNodeType.Element:
this.TraceElement();
this.CheckAgainstFailure();
elementName = this.reader.LocalName;
namespaceUri = this.reader.NamespaceURI;
if (this.expectingSingleValue)
{
#region return known primitive/reference typed value
// CreateQuery and the result is value
// we don't really care what the namespace is though with astoria
// it would be XmlConstants.DataWebNamespace ("http://schemas.microsoft.com/ado/2007/08/dataservices")
propertyValue = this.ReadElementString(true);
if (null != propertyValue)
{
currentValue = ClientConvert.ChangeType(propertyValue, Nullable.GetUnderlyingType(expectedType) ?? expectedType);
}
this.version = -this.version;
return true;
#endregion
}
if (((AtomParseState.None == atom) || (AtomParseState.LinkFeed == atom)) &&
AreSame(XmlConstants.AtomNamespace, namespaceUri) &&
AreSame(XmlConstants.AtomFeedElementName, elementName))
{
#region
if (this.reader.IsEmptyElement)
{
this.version = -this.version;
return false;
}
if (AtomParseState.None == atom)
{ // leave state in LinkFeed state so we exit properly when no exist
atom = AtomParseState.Feed;
}
Debug.Assert(AtomParseState.Feed == atom || AtomParseState.LinkFeed == atom, "feed state");
knownElement = true;
#endregion
}
else if (((AtomParseState.LinkEntry == atom) || (AtomParseState.LinkFeed == atom) || (AtomParseState.Feed == atom) || AtomParseState.None == atom) &&
AreSame(XmlConstants.AtomNamespace, namespaceUri) &&
AreSame(XmlConstants.AtomEntryElementName, elementName))
{
#region
atom = AtomParseState.Entry;
this.PushBufferedReader(ref currentEntry);
currentType = this.ReadTypeAttribute(currentEntry, expectedType);
etag = this.reader.GetAttribute(XmlConstants.AtomETagAttributeName, XmlConstants.DataWebMetadataNamespace);
if (currentType.HasKeys)
{
haveSetKeyProperty = new ArraySet(currentType.KeyCount);
}
knownElement = true;
#endregion
}
else if ((AtomParseState.Entry == atom) &&
AreSame(XmlConstants.AtomNamespace, namespaceUri))
{
if (AreSame(XmlConstants.AtomIdElementName, elementName))
{
#region resource uri
// The "atom:id" element conveys a permanent, universally unique
// identifier for an entry or feed.
// Its content MUST be an IRI, as defined by [RFC3987]. Note that the
// definition of "IRI" excludes relative references. Though the IRI
// might use a dereferencable scheme, Atom Processors MUST NOT assume it
// can be dereferenced.
Debug.Assert(null == currentValue, "already has an instance");
Debug.Assert(null == identity, "multiple ");
identity = Util.CreateUri(this.ReadElementString(false), UriKind.RelativeOrAbsolute);
if (!identity.IsAbsoluteUri)
{
throw Error.InvalidOperation(Strings.Context_TrackingExpectsAbsoluteUri);
}
// ReferenceIdentity is a test hook to help verify we dont' use identity instead of editLink
identity = Util.ReferenceIdentity(identity);
knownElement = true;
#endregion
}
else if (AreSame(XmlConstants.AtomContentElementName, elementName))
{
#region
propertyValue = this.reader.GetAttributeEx(XmlConstants.AtomContentSrcAttributeName, XmlConstants.AtomNamespace);
if (propertyValue != null)
{
// This is a media link entry
// Note that in this case we don't actually use this URL (or the link/edit-media URL)
// for editing. We rely on the Astoria URL convention (/propname/$value or just /$value)
if (!this.reader.IsEmptyElement)
{
throw Error.InvalidOperation(Strings.Deserialize_ExpectedEmptyMediaLinkEntryContent);
}
mediaLinkEntry = true;
knownElement = true;
}
else
{
// This is a regular (non-media link) entry
if (mediaLinkEntry.HasValue && mediaLinkEntry.Value)
{
// This means we saw a element but now we have a Content element
// that's not just a media link entry pointer (src)
throw Error.InvalidOperation(Strings.Deserialize_ContentPlusPropertiesNotAllowed);
}
mediaLinkEntry = false;
propertyValue = this.reader.GetAttributeEx(XmlConstants.AtomTypeAttributeName, XmlConstants.AtomNamespace);
if (XmlConstants.MimeApplicationXml == propertyValue || XmlConstants.MimeApplicationAtom == propertyValue)
{
if (this.ReadChildElement(XmlConstants.AtomPropertiesElementName, XmlConstants.DataWebMetadataNamespace))
{
this.TraceElement();
hasContentProperties = true;
atom = AtomParseState.Content;
}
else if (!AreSame(this.reader, XmlConstants.AtomContentElementName, XmlConstants.AtomNamespace))
{
throw Error.InvalidOperation(Strings.Deserialize_NotApplicationXml);
}
knownElement = true;
}
}
#endregion
}
else if (AreSame(XmlConstants.AtomLinkElementName, elementName))
{
string rel = this.reader.GetAttributeEx(XmlConstants.AtomLinkRelationAttributeName, XmlConstants.AtomNamespace);
if (XmlConstants.AtomEditRelationAttributeValue == rel)
{
#region uri
editLink = Util.CreateUri(this.reader.GetAttributeEx(XmlConstants.AtomHRefAttributeName, XmlConstants.AtomNamespace), UriKind.RelativeOrAbsolute);
#endregion
}
else if (!this.reader.IsEmptyElement)
{
propertyName = UriUtil.GetNameFromAtomLinkRelationAttribute(rel);
#region
if (null != propertyName)
{
Debug.Assert(!setNestedValue, "should not be set going in link");
property = currentType.GetProperty(propertyName, this.ignoreMissingProperties); // may throw
if (null == property)
{
propertyName = null;
}
else
{
if (property.IsKnownType)
{
throw Error.InvalidOperation(Strings.Deserialize_MismatchAtomLinkLocalSimple);
}
if (this.ResolveOrCreateInstance(currentType, identity, editLink, etag, ref currentValue, ref entityState))
{
atom = AtomParseState.Feed;
this.FireEventAndPopBufferedReader(currentValue, ref currentEntry);
return true;
}
object parent = currentValue;
Type nestedType = property.PropertyType;
propertyValue = this.reader.GetAttribute(XmlConstants.AtomTypeAttributeName);
if (String.Equals("application/atom+xml;type=feed", propertyValue))
{ // parent becomes the collection, not the currentValue
#region LinkFeed
#if ASTORIA_OPEN_OBJECT
if (property.OpenObjectProperty)
{ // get the nested IList from the dictionary
nestedType = typeof(OpenObject);
((IDictionary)property.GetValue(currentValue)).TryGetValue(propertyName, out parent);
if ((null != parent) && !(parent is IList))
{
throw Error.InvalidOperation(Strings.Deserialize_MismatchAtomLinkFeedPropertyNotCollection(propertyName));
}
}
else
#endif
if (null == (nestedType = property.CollectionType))
{
throw Error.InvalidOperation(Strings.Deserialize_MismatchAtomLinkFeedPropertyNotCollection(propertyName));
}
else
{ // get the nested IList from the current value
parent = property.GetValue(currentValue);
}
if (null == parent)
{
setNestedValue = true;
// if the current property is not settable
// we fail after populating contents into a collection
Type implementationType;
#if ASTORIA_OPEN_OBJECT
if (property.OpenObjectProperty)
{
implementationType = typeof(System.Collections.ObjectModel.Collection);
}
else
#endif
{
implementationType = property.PropertyType;
if (implementationType.IsInterface)
{
implementationType = typeof(System.Collections.ObjectModel.Collection<>).MakeGenericType(nestedType);
}
}
parent = Activator.CreateInstance(implementationType);
}
atom = AtomParseState.LinkFeed;
#endregion
}
else if (String.Equals("application/atom+xml;type=entry", propertyValue))
{
#region LinkEntry
#if ASTORIA_OPEN_OBJECT
if (property.OpenObjectProperty)
{
nestedType = typeof(OpenObject);
}
else
#endif
if (null != property.CollectionType)
{
throw Error.InvalidOperation(Strings.Deserialize_MismatchAtomLinkEntryPropertyIsCollection(propertyName));
}
atom = AtomParseState.LinkEntry;
#endregion
}
int linkCount = 0;
AtomParseState linkStart = atom;
#region read
// Check for empty inline element - that indicates that the value is null
// This is only true for reference properties
if (this.ReadChildElement(XmlConstants.AtomInlineElementName, XmlConstants.DataWebMetadataNamespace))
{
this.TraceElement();
Debug.Assert(
(currentValue == parent && AtomParseState.LinkEntry == atom) ||
(currentValue != parent && AtomParseState.LinkFeed == atom && parent is IEnumerable),
"unexpected parent");
Debug.Assert(null == nestedValue, "nestedValue should be null before reading links");
Debug.Assert(AtomParseState.LinkFeed == atom || AtomParseState.LinkEntry == atom, "expecting LinkEntry or LinkFeed");
if (!this.reader.IsEmptyElement)
{
#region read
EntityStates nestedState = 0;
while ((0 <= this.version) &&
this.reader.Read() &&
this.ReadNext(null, nestedType, atom, ref nestedState, ref nestedValue))
{
// will currentValue.Collection.Add or currentValue.set_Reference
if ((MergeOption.AppendOnly != this.MergeOptionValue) ||
(EntityStates.Detached == entityState))
{
#if ASTORIA_OPEN_OBJECT
if ((setNestedValue || parent is IList) && property.OpenObjectProperty)
{
((IList)parent).Add((OpenObject)nestedValue);
}
else
{
property.SetValue(parent, nestedValue, propertyName, ref openProperties, true);
}
#else
property.SetValue(parent, nestedValue, propertyName, true);
#endif
// if null == nestedValue, that implies an existing reference may now be gone
// however, since we don't "refresh" - we leave that previous relationship intact
if ((null != nestedValue) &&
#if ASTORIA_OPEN_OBJECT
!property.OpenObjectProperty &&
#endif
currentType.HasKeys && ClientType.Create(nestedValue.GetType()).HasKeys)
{
if (null == this.links)
{
this.links = new List();
}
// cache the links we want to add - but can't until after both ends exist in the context
EntityStates linkState = (AtomParseState.LinkEntry == linkStart) ? EntityStates.Modified : EntityStates.Added;
this.links.Add(new LinkDescriptor(currentValue, property.PropertyName, nestedValue, linkState));
}
}
linkCount++;
nestedValue = null;
atom = AtomParseState.LinkEntry;
if ((AtomParseState.LinkEntry == linkStart) && (0 != linkCount))
{ //
break; // while
}
}
#endregion
}
#region no links
if ((0 == linkCount) && (AtomParseState.LinkEntry == linkStart))
{ // set null nested value
if ((MergeOption.AppendOnly != this.MergeOptionValue) ||
(EntityStates.Detached == entityState))
{
if (null == this.links)
{
this.links = new List();
}
// AppendOnly & NoTracking - only if creating new object
// OverwriteChanges - server wins
// PreserveChanges - new object, non-null target -> unchanged, null target -> modified
Debug.Assert(
(MergeOption.AppendOnly == this.MergeOptionValue && EntityStates.Detached == entityState) ||
(MergeOption.NoTracking == this.MergeOptionValue && EntityStates.Detached == entityState) ||
(MergeOption.OverwriteChanges == this.MergeOptionValue) ||
(MergeOption.PreserveChanges == this.MergeOptionValue),
"unexpected merge of link reference");
Debug.Assert(null == nestedValue, "expecting null nestedValue");
this.links.Add(new LinkDescriptor(currentValue, property.PropertyName, null, EntityStates.Modified));
parent = null;
setNestedValue = true;
}
}
else if ((AtomParseState.LinkFeed == linkStart) &&
(MergeOption.OverwriteChanges == this.MergeOptionValue || MergeOption.PreserveChanges == this.MergeOptionValue))
{ // detach extra remaining links
object collection = null;
var q = (from x in this.context.GetLinks(currentValue, property.PropertyName)
where MergeOption.OverwriteChanges == this.MergeOptionValue || EntityStates.Added != x.State
select new LinkDescriptor(x.SourceResource, x.SourceProperty, x.TargetResouce, EntityStates.Detached))
.Except(this.links, LinkDescriptor.EquivalentComparer);
foreach (LinkDescriptor p in q)
{
if (null == collection)
{
collection = property.GetValue(currentValue);
}
if (null != collection)
{
property.RemoveValue(collection, p.Target);
}
this.links.Add(p);
}
}
#endregion
if (setNestedValue)
{
nestedValue = parent;
}
Debug.Assert(this.reader.IsEmptyElement || XmlNodeType.EndElement == this.reader.NodeType, "expected EndElement");
Debug.Assert(
(this.reader.IsEmptyElement && AreSame(XmlConstants.AtomInlineElementName, this.reader.LocalName)) ||
(AtomParseState.LinkFeed == linkStart && AreSame(XmlConstants.AtomFeedElementName, this.reader.LocalName)) ||
(AtomParseState.LinkEntry == linkStart && AreSame(XmlConstants.AtomEntryElementName, this.reader.LocalName)),
"link didn't end on expected element");
}
this.SkipToEnd(XmlConstants.AtomLinkElementName, XmlConstants.AtomNamespace);
#endregion
Debug.Assert(((XmlNodeType.Element == this.reader.NodeType && this.reader.IsEmptyElement) ||
(XmlNodeType.EndElement == this.reader.NodeType)) &&
AreSame(this.reader, XmlConstants.AtomLinkElementName, XmlConstants.AtomNamespace),
"didn't land on ");
#region Set collection / reference instance
if (setNestedValue)
{
#if ASTORIA_OPEN_OBJECT
property.SetValue(currentValue, nestedValue, propertyName, ref openProperties, false);
#else
property.SetValue(currentValue, nestedValue, propertyName, false);
#endif
nestedValue = null;
}
#endregion
setNestedValue = false;
propertyName = null;
property = null;
Debug.Assert(null == nestedValue, "null == nestedValue");
knownElement = true;
}
}
atom = AtomParseState.Entry;
#endregion
}
}
}
else if ((AtomParseState.Entry == atom) &&
AreSame(XmlConstants.DataWebMetadataNamespace, namespaceUri) &&
AreSame(XmlConstants.AtomPropertiesElementName, elementName))
{
#region
if (mediaLinkEntry.HasValue && !mediaLinkEntry.Value)
{
// This means we saw a non-empty element but now we have a Properties element
// that also carries properties
throw Error.InvalidOperation(Strings.Deserialize_ContentPlusPropertiesNotAllowed);
}
mediaLinkEntry = true;
if (!this.reader.IsEmptyElement)
{
atom = AtomParseState.Content; // Semantically this is equivalent to content, so treat it the same
}
knownElement = true;
#endregion
}
else if (((AtomParseState.Content == atom) || (AtomParseState.Complex == atom)) &&
AreSame(this.dataNamespace, namespaceUri))
{
if (this.ResolveOrCreateInstance(currentType, identity, editLink, etag, ref currentValue, ref entityState))
{
atom = AtomParseState.Feed;
this.FireEventAndPopBufferedReader(currentValue, ref currentEntry);
return true;
}
#region MergeOption checks
Debug.Assert(null != currentValue, "null currentValue");
Debug.Assert(0 != entityState, "expected entitystate");
if (((MergeOption.AppendOnly == this.MergeOptionValue) && (EntityStates.Detached != entityState)) ||
((MergeOption.PreserveChanges == this.MergeOptionValue) && (EntityStates.Added == entityState || EntityStates.Modified == entityState)))
{ // do not change content in this state
this.SkipToEnd(this.reader.LocalName, this.reader.NamespaceURI);
break;
}
Debug.Assert(
(MergeOption.NoTracking == this.MergeOptionValue && EntityStates.Detached == entityState) ||
(MergeOption.AppendOnly == this.MergeOptionValue && EntityStates.Detached == entityState) ||
(MergeOption.PreserveChanges == this.MergeOptionValue && (EntityStates.Detached == entityState || EntityStates.Unchanged == entityState || EntityStates.Deleted == entityState)) ||
(MergeOption.OverwriteChanges == this.MergeOptionValue),
"NoTracking and AppendOnly, PreserveChanges should not change content of existing objects");
#endregion
#region
property = currentType.GetProperty(elementName, this.ignoreMissingProperties); // may throw
if (null != property)
{
propertyName = elementName;
atom = (AtomParseState.Content == atom) ? AtomParseState.PropertyContent : AtomParseState.PropertyComplex;
if (property.KeyProperty)
{
haveSetKeyProperty.Add(propertyName, null);
}
#if ASTORIA_OPEN_OBJECT
if (property.IsKnownType ||
ClientConvert.IsKnownType((nestedElementType = this.ReadTypeAttribute(property.OpenObjectProperty ? typeof(OpenObject) : property.PropertyType)).ElementType))
#else
if (property.IsKnownType ||
ClientConvert.IsKnownType((nestedElementType = this.ReadTypeAttribute(property.PropertyType)).ElementType))
#endif
{
// propertyValue
propertyValue = this.ReadElementString(true);
object value = propertyValue;
if (null != propertyValue)
{
value = ClientConvert.ChangeType(propertyValue, (null != nestedElementType ? nestedElementType.ElementType : property.PropertyType));
}
#if ASTORIA_OPEN_OBJECT
property.SetValue(currentValue, value, propertyName, ref openProperties, false);
#else
property.SetValue(currentValue, value, propertyName, false);
#endif
atom = (AtomParseState.PropertyContent == atom) ? AtomParseState.Content : AtomParseState.Complex;
property = null;
propertyName = null;
nestedElementType = null;
hasValue = false;
}
else if (this.reader.IsEmptyElement)
{
#if ASTORIA_OPEN_OBJECT
property.SetValue(currentValue, null, propertyName, ref openProperties, false);
#else
property.SetValue(currentValue, null, propertyName, false);
#endif
}
else
{
#if ASTORIA_OPEN_OBJECT
if (!property.OpenObjectProperty && (null != property.CollectionType))
#else
if (null != property.CollectionType)
#endif
{ // client object property is collection implying nested collection of complex objects
throw Error.NotSupported(Strings.ClientType_CollectionOfNonEntities);
}
#region recurse into complex type
if (null == nestedValue)
{ // retreived only once per collection or nested object
nestedValue = property.GetValue(currentValue);
#if ASTORIA_OPEN_OBJECT
if (property.OpenObjectProperty)
{
if (null != nestedValue)
{
((IDictionary)nestedValue).TryGetValue(propertyName, out nestedValue);
}
else
{
throw Error.InvalidOperation(Strings.Collection_NullCollectionReference(currentType.ElementTypeName, property.PropertyName));
}
}
#endif
}
if (null == nestedValue)
{ // when the value is null, assume the recursion will create the object
hasValue = true;
}
if (this.reader.Read() &&
this.ReadNext(nestedElementType, nestedElementType.ElementType, AtomParseState.Complex, ref entityState, ref nestedValue))
{
setNestedValue = (hasValue = (nestedValue != null));
hasValue = true;
goto case XmlNodeType.EndElement;
}
#endregion
}
knownElement = true;
}
#endregion
}
else if ((AtomParseState.None == atom) && AreSame(this.dataNamespace, namespaceUri) && (null == currentType))
{ // complex type?
Debug.Assert(0 == entityState, "shouldn't have existing entity");
ClientType tmp = this.ReadTypeAttribute(expectedType);
if (!tmp.HasKeys)
{
currentType = tmp;
if (this.reader.IsEmptyElement || this.DoesNullAttributeSayTrue())
{
this.SkipToEnd(this.reader.LocalName, this.reader.NamespaceURI);
return true;
}
atom = AtomParseState.Complex;
entityState = EntityStates.Detached;
knownElement = true;
}
}
if (!knownElement)
{
this.SkipToEnd(this.reader.LocalName, this.reader.NamespaceURI);
}
break;
#endregion
#region Text
case XmlNodeType.Text:
case XmlNodeType.SignificantWhitespace:
Debug.Assert(!this.expectingSingleValue, "!this.expectingSingleValue, Text");
// throw an exception that we hit mixed-content
// propertyValue elementText
throw Error.InvalidOperation(Strings.Deserialize_MixedContent(currentType.ElementTypeName));
#endregion
#region EndElement
case XmlNodeType.EndElement:
Debug.Assert(!this.expectingSingleValue, "!this.expectingSingleValue");
this.TraceEndElement(true);
if (null == currentType)
{ //
// or reader started in a bad state (which is okay)
Debug.Assert(!hasValue, "EndElement w/ haveValue");
Debug.Assert(!setNestedValue, "EndElement w/ setNestedValue");
Debug.Assert(null == property, "EndElement w/ property");
Debug.Assert(null == propertyName, "EndElement w/ propertyName");
Debug.Assert(null == nestedValue, "EndElement w/ nestedValue");
Debug.Assert(null == nestedElementType, "EndElement w/ nestedElementType");
// SQLBUDT 565573
if (AtomParseState.LinkEntry != atom && AtomParseState.LinkFeed != atom)
{
atom = AtomParseState.None;
this.version = -this.version;
}
return false;
}
if (null == property)
{ //
Debug.Assert(!hasValue, "EndElement property w/ haveValue");
Debug.Assert(!setNestedValue, "EndElement property w/ setNestedValue");
Debug.Assert(null == property, "EndElement property w/ propertyName");
Debug.Assert(null == nestedValue, "EndElement property w/ nestedValue");
Debug.Assert(null == nestedElementType, "EndElement property w/ nestedElementType");
Debug.Assert(null != currentType, "Should have derived a type from the entry or from the query");
if (AtomParseState.Content == atom)
{
if (hasContentProperties)
{
this.SkipToEnd(XmlConstants.AtomContentElementName, XmlConstants.AtomNamespace);
}
atom = AtomParseState.Entry;
continue;
}
else if (AtomParseState.Complex == atom)
{
return true;
}
else if (AtomParseState.Entry == atom)
{
this.SkipToEnd(XmlConstants.AtomEntryElementName, XmlConstants.AtomNamespace);
atom = AtomParseState.Feed;
// it might happen that we see no data properties in an entry, just
// metadata such as the ID. In the case were no properties where present
// we just create an uninitialized entry and attach it if needed
this.ResolveOrCreateInstance(currentType, identity, editLink, etag, ref currentValue, ref entityState);
// validate all keys have been set
if (currentType.HasKeys && (EntityStates.Added == entityState || EntityStates.Detached == entityState))
{
foreach (ClientType.ClientProperty p in currentType.Properties)
{
// if property is a key and not set
if (p.KeyProperty && (null == haveSetKeyProperty.Remove(p.PropertyName, String.Equals)))
{
throw Error.InvalidOperation(Strings.BatchStream_ContentExpected(p.PropertyName));
}
}
}
this.FireEventAndPopBufferedReader(currentValue, ref currentEntry);
return true;
}
Debug.Assert(false, "invalid Atom state");
this.version = -this.version;
Error.ThrowInternalError(InternalError.UnexpectedReadState);
}
//
Debug.Assert(null != currentValue, "null currentValue");
Debug.Assert(null != propertyName, "null propertyName");
Debug.Assert(!setNestedValue || (null != property), "null property w/ nestedValue");
if ((null != nestedValue) &&
(setNestedValue ||
#if ASTORIA_OPEN_OBJECT
property.OpenObjectProperty ||
#endif
nestedValue.GetType().IsValueType))
{
Debug.Assert(null != nestedValue, "EndElement after nested w/o nestedValue");
// if hasValue is false, we still want to set the nestedValue instance
//
// which is different than
// which would have set null for nested complex property
#if ASTORIA_OPEN_OBJECT
property.SetValue(currentValue, nestedValue, propertyName, ref openProperties, false);
#else
property.SetValue(currentValue, nestedValue, propertyName, false);
#endif
}
else if (!hasValue)
{ //
Debug.Assert(null != property, "null property without value");
#if ASTORIA_OPEN_OBJECT
property.SetValue(currentValue, null, propertyName, ref openProperties, false);
#else
property.SetValue(currentValue, null, propertyName, false);
#endif
}
// finished reading property, continue parsing element
Debug.Assert(
AtomParseState.PropertyContent == atom ||
AtomParseState.PropertyComplex == atom,
"expected atom property state");
atom = (AtomParseState.PropertyContent == atom) ? AtomParseState.Content : AtomParseState.Complex;
property = null;
propertyName = null;
hasValue = false;
nestedElementType = null;
nestedValue = null;
setNestedValue = false;
break;
#endregion
#region fail on unhandled content nodes, similar to XmlReader.MoveToContent()
case XmlNodeType.CDATA:
case XmlNodeType.EntityReference:
case XmlNodeType.EndEntity:
// Checks whether the current node is a content (non-whitespace text, CDATA, Element, EndElement, EntityReference
// or EndEntity) node. If the node is not a content node, then the method skips ahead to the next content node or
// end of file. Skips over nodes of type ProcessingInstruction, DocumentType, Comment, Whitespace and SignificantWhitespace.
this.version = -this.version;
Error.ThrowInternalError(InternalError.UnexpectedXmlNodeTypeWhenReading);
break;
#endregion
}
}
while (this.reader.Read());
Debug.Assert(null == currentValue, "non-null currentValue");
this.version = -this.version;
return false;
}
///
/// create an instance and associate its identity for callstack identity resolution
///
/// type to create
/// identity of instance
/// editLink of instance
/// etag of identity
/// current value instance
/// entity state
/// new instance of type
private bool ResolveOrCreateInstance(ClientType type, Uri identity, Uri editLink, string etag, ref object currentValue, ref EntityStates state)
{
if (null == currentValue)
{
if (null != identity)
{
#region handle POST entity response
if (null != this.inserting)
{
Debug.Assert(MergeOption.OverwriteChanges == this.MergeOptionValue, "only expected value during SaveChanges");
// always attach an identity, so that inserted relationships can also happen
// however, if NoTracking then its left in the inserted bucket
this.context.AttachIdentity(identity, editLink, this.inserting, etag);
}
#endregion
if (type.HasKeys)
{
Debug.Assert(null != identity, "missing identity");
if (MergeOption.NoTracking != this.MergeOptionValue)
{
#region handle POST entity response
if (null != this.inserting)
{
Debug.Assert(null == currentValue, "it already has a value?");
currentValue = this.inserting;
state = EntityStates.Unchanged;
this.inserting = null;
Debug.Assert(type.HasKeys, "!HasKeys");
Debug.Assert(type.ElementType.IsInstanceOfType(currentValue), "!IsInstanceOfType");
}
#endregion
if (null != (currentValue ?? (currentValue = this.context.TryGetEntity(identity, etag, this.MergeOptionValue, out state))))
{
if (!type.ElementType.IsInstanceOfType(currentValue))
{
throw Error.InvalidOperation(Strings.Deserialize_Current(type.ElementType, currentValue.GetType()));
}
}
}
}
if ((null == currentValue) && (null != this.identityStack))
{ // always do identity resolution on the callstack within a top-level entity
// but avoid the AppendOnly behavior for deep expanded items
EntityWithTag pair;
this.identityStack.TryGetValue(identity, out pair);
currentValue = pair.Entity;
state = pair.State;
}
}
else
{
Debug.Assert(!type.HasKeys, "entity without identity");
}
if ((null == currentValue) || (this.MergeOptionValue == MergeOption.OverwriteChanges))
{
if (null == currentValue)
{
currentValue = type.CreateInstance();
state = EntityStates.Detached;
}
if (null != identity)
{
if (null == this.identityStack)
{
this.identityStack = new Dictionary();
}
this.identityStack.Add(identity, new EntityWithTag(currentValue, editLink, etag, state, type.HasKeys));
}
}
}
return false;
}
///
/// Read subtree into an XElement, push the reader into the stack and set the current
/// reader to a reader on top of the XElement
///
/// Current XElement being processed
private void PushBufferedReader(ref XElement currentEntry)
{
XmlReader subtree = this.reader.ReadSubtree();
try
{
currentEntry = XElement.Load(subtree);
}
catch (XmlException ex)
{
throw new DataServiceClientException(Strings.Deserialize_ServerException1, ex);
}
finally
{
subtree.Close();
}
if (this.stackedReaders == null)
{
this.stackedReaders = new Stack();
}
this.stackedReaders.Push(this.reader);
this.reader = currentEntry.CreateReader();
this.reader.Read();
}
///
/// Fire the reader event with the current buffered subtree reader and then pop
/// the next reader in the stack
///
/// Current .NET object being read
/// Buffered entry with data from the input stream
private void FireEventAndPopBufferedReader(object currentValue, ref XElement currentEntry)
{
if (this.context.HasReadingEntityHandlers)
{
Debug.Assert(currentEntry != null && currentValue != null, "Event present but did not create buffered entry?");
Debug.Assert(this.stackedReaders.Count > 0, "Event present but previous reader not pushed?");
this.context.FireReadingEntityEvent(currentValue, currentEntry);
}
this.reader = this.stackedReaders.Pop();
currentEntry = null;
}
///
/// Gets the type attribte and resolves the type from the current XmlReader.
///
/// expected base type
/// resolved type
private ClientType ReadTypeAttribute(Type expectedType)
{
string typeName = this.reader.GetAttribute(XmlConstants.AtomTypeAttributeName, XmlConstants.DataWebMetadataNamespace);
Type resolvedType = this.context.ResolveTypeFromName(typeName, expectedType);
return ClientType.Create(resolvedType);
}
///
/// Gets the type attribte and resolves the type from the specified .
///
/// XML element for the ATOM entry.
/// expected base type
/// resolved type
private ClientType ReadTypeAttribute(XElement entryElement, Type expectedType)
{
Debug.Assert(entryElement != null, "entryElement != null");
// XNamespace.None is used for attribute because they are not namespace-qualified in the payloads.
XNamespace atomNs = XmlConstants.AtomNamespace;
XName categoryName = atomNs + XmlConstants.AtomCategoryElementName;
XName schemeName = XNamespace.None + XmlConstants.AtomCategorySchemeAttributeName;
XName termName = XNamespace.None + XmlConstants.AtomCategoryTermAttributeName;
string typeSchemeText = this.context.TypeScheme.OriginalString;
var categories = entryElement.Elements(categoryName);
var category = categories.Where(c => c.Attributes(schemeName).Any(a => a.Value == typeSchemeText)).FirstOrDefault();
var categoryTitle = (category == null) ? null : category.Attributes(termName).FirstOrDefault();
string typeName = (categoryTitle == null) ? null : categoryTitle.Value;
Type resolvedType = this.context.ResolveTypeFromName(typeName, expectedType);
return ClientType.Create(resolvedType);
}
///
/// it can concatenate multiple adjacent text and CDATA blocks
/// ignores comments and whitespace
/// will leave reader on EndElement
///
/// check for null attribute or not
/// text contained in the element. An null string if the element was empty
private string ReadElementString(bool checkNullAttribute)
{
Debug.Assert(XmlNodeType.Element == this.reader.NodeType, "not positioned on Element");
string result = null;
bool empty = checkNullAttribute && !this.DoesNullAttributeSayTrue();
if (this.reader.IsEmptyElement)
{
return (empty ? String.Empty : null);
}
while (this.reader.Read())
{
switch (this.reader.NodeType)
{
case XmlNodeType.EndElement:
this.TraceEndElement(false);
return result ?? (empty ? String.Empty : null);
case XmlNodeType.CDATA:
case XmlNodeType.Text:
case XmlNodeType.SignificantWhitespace:
if (null != result)
{
throw Error.InvalidOperation(Strings.Deserialize_MixedTextWithComment);
}
this.TraceText(this.reader.Value);
result = this.reader.Value;
break;
case XmlNodeType.Comment:
case XmlNodeType.Whitespace:
break;
#region XmlNodeType error
case XmlNodeType.Element:
this.CheckAgainstFailure();
goto default;
default:
this.version = -this.version;
throw Error.InvalidOperation(Strings.Deserialize_ExpectingSimpleValue);
#endregion
}
}
// xml ended before EndElement?
this.version = -this.version;
throw Error.InvalidOperation(Strings.Deserialize_ExpectingSimpleValue);
}
/// Move to top-level child element of the expected type
/// localName of expected child content-node
/// namespaceUri of expected child content-node
/// true if moved to expected child content node
private bool ReadChildElement(string localName, string namespaceUri)
{
return (!this.reader.IsEmptyElement &&
(this.reader.NodeType != XmlNodeType.EndElement) &&
this.reader.Read() &&
this.reader.IsStartElement(localName, namespaceUri));
}
///
/// Skips the children of the current node.
///
/// local name of the node we search to
/// namespace of the node we search to
private void SkipToEnd(string localName, string namespaceURI)
{
//
//
//
XmlReader xmlReader = this.reader;
int readerDepth = xmlReader.Depth;
do
{
switch (xmlReader.NodeType)
{
case XmlNodeType.Element:
this.CheckAgainstFailure();
if (xmlReader.IsEmptyElement &&
xmlReader.Depth == readerDepth &&
AreSame(xmlReader, localName, namespaceURI))
{
return;
}
break;
case XmlNodeType.EndElement:
if (xmlReader.Depth <= readerDepth)
{ // new end element
this.TraceEndElement(true);
readerDepth--;
if (AreSame(xmlReader, localName, namespaceURI))
{
return;
}
}
break;
}
}
while (xmlReader.Read());
}
/// throw if current element is Exception
/// if current element is exception
private void CheckAgainstFailure()
{
Debug.Assert(XmlNodeType.Element == this.reader.NodeType, "not Element");
if (AreSame(this.reader, XmlConstants.XmlErrorElementName, XmlConstants.DataWebMetadataNamespace))
{
string message = this.ReadErrorMessage();
this.version = -this.version;
// In case of instream errors, the status code should be 500 (which is the default)
throw new DataServiceClientException(Strings.Deserialize_ServerException(message));
}
}
/// With the reader positioned on an 'error' element, reads the text of the 'message' child.
/// The text of the 'message' child element, empty if not found.
private string ReadErrorMessage()
{
Debug.Assert(AreSame(this.reader, XmlConstants.XmlErrorElementName, XmlConstants.DataWebMetadataNamespace), "AreSame(this.reader, XmlConstants.XmlErrorElementName, XmlConstants.DataWebMetadataNamespace)");
int depth = 1;
while (depth > 0 && this.reader.Read())
{
if (this.reader.NodeType == XmlNodeType.Element)
{
if (!this.reader.IsEmptyElement)
{
depth++;
}
if (depth == 2 &&
AreSame(this.reader, XmlConstants.XmlErrorMessageElementName, XmlConstants.DataWebMetadataNamespace))
{
return this.ReadElementString(false);
}
}
else if (this.reader.NodeType == XmlNodeType.EndElement)
{
depth--;
}
}
return String.Empty;
}
///
/// check the atom:null="true" attribute
///
/// true of null is true
private bool DoesNullAttributeSayTrue()
{
string attributeValue = this.reader.GetAttribute(XmlConstants.AtomNullAttributeName, XmlConstants.DataWebMetadataNamespace);
return ((null != attributeValue) && XmlConvert.ToBoolean(attributeValue));
}
#region Tracing
///
/// trace Element node
///
[Conditional("TRACE")]
private void TraceElement()
{
Debug.Assert(XmlNodeType.Element == this.reader.NodeType, "not positioned on Element");
if (null != this.writer)
{
this.writer.Write(Util.GetWhitespaceForTracing(2 + this.reader.Depth), 0, 2 + this.reader.Depth);
this.writer.Write("<{0}", this.reader.Name);
if (this.reader.MoveToFirstAttribute())
{
do
{
this.writer.Write(" {0}=\"{1}\"", this.reader.Name, this.reader.Value);
}
while (this.reader.MoveToNextAttribute());
this.reader.MoveToElement();
}
this.writer.Write(this.reader.IsEmptyElement ? " />" : ">");
}
}
///
/// trace EndElement node
///
/// indent or not
[Conditional("TRACE")]
private void TraceEndElement(bool indent)
{
if (null != this.writer)
{
if (indent)
{
this.writer.Write(Util.GetWhitespaceForTracing(2 + this.reader.Depth), 0, 2 + this.reader.Depth);
}
this.writer.Write("{0}>", this.reader.Name);
}
}
///
/// trace string value
///
/// value
[Conditional("TRACE")]
private void TraceText(string value)
{
if (null != this.writer)
{
this.writer.Write(value);
}
}
#endregion
///
/// hold information about a newly constructed object until the very end of MoveNext
///
private struct EntityWithTag
{
/// entity being attached
internal readonly object Entity;
/// uri to edit the entity
/// <atom:link rel="edit" href="editLink" />
internal readonly Uri EditLink;
/// etag of entity being attached
internal readonly string ETag;
/// state of the entitiy
internal readonly EntityStates State;
/// should we actually attach the entity, i.e. false if OpenType
internal readonly bool AttachToContext;
///
/// ctor
///
/// entity
/// editLink
/// etag
/// state
/// attach
internal EntityWithTag(object entity, Uri editLink, string etag, EntityStates state, bool attach)
{
this.Entity = entity;
this.EditLink = editLink;
this.ETag = etag;
this.State = state;
this.AttachToContext = attach;
}
}
///
/// materialize objects from an application/atom+xml stream
///
/// base type of the object to create from the stream
internal sealed class MaterializeAtomT : MaterializeAtom, IEnumerable, IEnumerator
{
///
/// constructor
///
/// originating context
/// reader
internal MaterializeAtomT(DataServiceContext context, XmlReader reader)
: base(context, reader, typeof(T), context.MergeOption)
{
}
#region Current
///
/// typed current object
///
[DebuggerHidden]
T IEnumerator.Current
{
get { return (T)this.Current; }
}
#endregion
#region IEnumerable
///
/// as IEnumerable
///
/// this
IEnumerator IEnumerable.GetEnumerator()
{
this.CheckGetEnumerator();
return this;
}
#endregion
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// materialize objects from an xml stream
//
//---------------------------------------------------------------------
namespace System.Data.Services.Client
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
///
/// materialize objects from an application/atom+xml stream
///
internal class MaterializeAtom : IDisposable, IEnumerable, IEnumerator
{
/// MergeOption captured from context so its static for the entire materialization
internal readonly MergeOption MergeOptionValue;
/// Options when deserializing properties to the target type.
private readonly bool ignoreMissingProperties;
/// Backreference to the context to allow type resolution.
private readonly DataServiceContext context;
/// DataNamespace captured from context so its static for the entire materialization
/// reference equality is important here
private readonly string dataNamespace;
/// base type of the object to create from the stream.
private readonly Type elementType;
/// when materializing a known type (like string)
/// <property> text-value </property>
private readonly bool expectingSingleValue;
/// source reader (may be running on top of a buffer or be the real reader)
private XmlReader reader;
/// untyped current object
private object current;
/// object being updated
private object inserting;
/// identity of new objects and associated etag per enumerator.MoveNext()
private Dictionary identityStack;
/// list of links to add
private List links;
/// debugging tool, # calls to ReadNext()
private int version;
/// has GetEnumerator been called?
private bool calledGetEnumerator;
/// stack of readers used when buffering intermediate entries (to create XElements and fire events)
private Stack stackedReaders;
///
/// output writer, set using reflection
///
#if DEBUG && !ASTORIA_LIGHT
private System.IO.TextWriter writer = new System.IO.StringWriter(System.Globalization.CultureInfo.InvariantCulture);
#else
#pragma warning disable 649
private System.IO.TextWriter writer;
#pragma warning restore 649
#endif
///
/// constructor
///
/// originating context
/// reader
/// element base type
/// merge option to use for this materialization pass
internal MaterializeAtom(DataServiceContext context, XmlReader reader, Type elementType, MergeOption mergeOption)
{
Debug.Assert(null != reader, "null XmlReader");
this.context = context;
this.dataNamespace = reader.Settings.NameTable.Add(context.DataNamespace);
this.elementType = elementType;
this.MergeOptionValue = mergeOption;
this.ignoreMissingProperties = context.IgnoreMissingProperties;
this.reader = reader;
this.expectingSingleValue = ClientConvert.IsKnownType(Nullable.GetUnderlyingType(elementType) ?? elementType);
}
/// atom parser state machine positions
private enum AtomParseState
{
/// None
None,
/// Feed
Feed,
/// Entry
Entry,
/// Content
Content,
/// Complex
Complex,
/// Property
PropertyContent,
/// Property
PropertyComplex, // SQLBUDT 572585
/// LinkFeed
LinkFeed,
/// LinkEntry
LinkEntry,
}
#region Current
///
/// untyped current object property
///
public object Current
{
get
{
return ClientConvert.VerifyCast(this.elementType, this.current);
}
}
#endregion
#region IDisposable
///
/// dispose
///
public void Dispose()
{
this.current = null;
this.inserting = null;
if (null != this.reader)
{
((IDisposable)this.reader).Dispose();
}
if (null != this.writer)
{
this.writer.Dispose();
}
}
#endregion
#region IEnumerable
///
/// as IEnumerable
///
/// this
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
this.CheckGetEnumerator();
return this;
}
#endregion
///
/// create the next object from the stream
///
/// false if stream is finished
public bool MoveNext()
{
this.current = null;
if (null != this.identityStack)
{
this.identityStack.Clear();
}
if (null != this.links)
{
this.links.Clear();
}
bool result = false;
if ((0 == this.version) && !this.expectingSingleValue && typeof(IEnumerable).IsAssignableFrom(this.elementType))
{
Type implementationType = ClientType.GetImplementationType(this.elementType, typeof(ICollection<>));
if (null != implementationType)
{
Type expectedType = implementationType.GetGenericArguments()[0]; // already know its IList<>
implementationType = this.elementType;
if (implementationType.IsInterface)
{
implementationType = typeof(System.Collections.ObjectModel.Collection<>).MakeGenericType(expectedType);
}
IList list = (IList)Activator.CreateInstance(implementationType);
object local = null;
EntityStates localState = 0;
while ((0 <= this.version) &&
this.reader.Read() &&
this.ReadNext(null, expectedType, AtomParseState.None, ref localState, ref local))
{
list.Add(local);
local = null;
}
Debug.Assert(this.version < 0, "should have completed the read");
this.current = list;
result = true;
}
}
if (null == this.current)
{
EntityStates localState = 0;
result = (0 <= this.version) &&
this.reader.Read() &&
this.ReadNext(null, this.elementType, AtomParseState.None, ref localState, ref this.current);
}
if (MergeOption.NoTracking != this.MergeOptionValue)
{
if (null != this.identityStack)
{
foreach (KeyValuePair entity in this.identityStack)
{
if (entity.Value.AttachToContext)
{
this.context.AttachTo(entity.Key, entity.Value.EditLink, entity.Value.ETag, entity.Value.Entity, false);
}
}
}
if (null != this.links)
{
foreach (LinkDescriptor link in this.links)
{
if (EntityStates.Added == link.State)
{ // Added implies collection
if ((EntityStates.Deleted == this.context.GetEntity(link.Target).State) ||
(EntityStates.Deleted == this.context.GetEntity(link.Source).State))
{
this.context.DeleteLink(link.Source, link.SourceProperty, link.Target);
}
else
{
this.context.AttachLink(link.Source, link.SourceProperty, link.Target, this.MergeOptionValue);
}
}
else if (EntityStates.Modified == link.State)
{ // Modified implies reference
object target = link.Target;
if (MergeOption.PreserveChanges == this.MergeOptionValue)
{
DataServiceContext.RelatedEnd end = this.context.GetLinks(link.Source, link.SourceProperty).FirstOrDefault();
if (null != end && null == end.TargetResouce)
{ // leave the SetLink(link.Source, link.SourceProperty, null)
continue;
}
if ((null != target) && (EntityStates.Deleted == this.context.GetEntity(target).State) ||
(EntityStates.Deleted == this.context.GetEntity(link.Source).State))
{
target = null;
}
}
this.context.AttachLink(link.Source, link.SourceProperty, target, this.MergeOptionValue);
}
else
{ // detach link
// Debug.Assert(
// (MergeOption.OverwriteChanges == this.MergeOptionValue) ||
// (MergeOption.PreserveChanges == this.MergeOptionValue &&
// EntityStates.Added != this.context.GetLink(link.Source, link.SourceProperty, link.Target).State),
// "only OverwriteChanges or PreserveChanges should remove existing links");
Debug.Assert(EntityStates.Detached == link.State, "not detached link");
this.context.DetachLink(link.Source, link.SourceProperty, link.Target);
}
}
}
}
return result;
}
///
/// Not supported.
///
/// Always thrown
void System.Collections.IEnumerator.Reset()
{
throw Error.NotSupported();
}
/// set the inserted object expected in the response
/// object being inserted that the response is looking for
internal void SetInsertingObject(object addedObject)
{
this.inserting = addedObject;
}
/// are the two values the same reference
/// value1
/// value2
/// true if they are the same reference
private static bool AreSame(string value1, string value2)
{
// bool result = Object.ReferenceEquals(value1, value2);
// Debug.Assert(result == (value1 == value2), "!NameTable - unable to do reference comparison on '" + value1 + "'");
// XElement uses a global name table which may have encountered
// our strings first and return a different instance than what was expected
bool result = (value1 == value2);
return result;
}
/// is the reader on contentNode where the localName and namespaceUri match
/// reader
/// localName
/// namespaceUri
/// true if localName and namespaceUri match reader current element
private static bool AreSame(XmlReader reader, string localName, string namespaceUri)
{
Debug.Assert((null != reader) && (null != localName) && (null != namespaceUri), "null");
return ((XmlNodeType.Element == reader.NodeType) || (XmlNodeType.EndElement == reader.NodeType)) &&
AreSame(reader.LocalName, localName) && AreSame(reader.NamespaceURI, namespaceUri);
}
/// verify the GetEnumerator can only be called once
private void CheckGetEnumerator()
{
if (this.calledGetEnumerator)
{
throw Error.NotSupported(Strings.DataServiceException_GeneralError);
}
this.calledGetEnumerator = true;
}
///
/// build up a single object result
///
/// what is the nested element type (collection support)
/// what is the expected base type for current object
/// what is the initial parse state
/// entity state
/// current object being populated
/// true if not at end of object
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506", Justification = "eventually need to rewrite")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "multipurpose variable")]
private bool ReadNext(ClientType currentType, Type expectedType, AtomParseState atom, ref EntityStates entityState, ref object currentValue)
{
Debug.Assert((null != this.reader) && !this.reader.EOF && (this.reader.NodeType != XmlNodeType.None), "bad XmlReader");
this.version++;
// object level variables
// elementType (this is our return point until the next MoveNext call)
string elementName = null;
string namespaceUri = null;
string etag = null;
Uri identity = null;
Uri editLink = null;
bool? mediaLinkEntry = null;
bool hasContentProperties = false;
XElement currentEntry = null;
ArraySet haveSetKeyProperty;
// property level variables
ClientType.ClientProperty property = null;
string propertyName = null;
bool hasValue = false;
ClientType nestedElementType = null;
object nestedValue = null;
bool setNestedValue = false;
#if ASTORIA_OPEN_OBJECT
object openProperties = null; // like currentValue, this doesn't need to be nulled out
#endif
if ((null != currentType) && currentType.HasKeys)
{
haveSetKeyProperty = new ArraySet(currentType.KeyCount);
}
else
{
haveSetKeyProperty = default(ArraySet);
}
do
{ // caller of ReadNext called this.reader.Read()
string propertyValue = null;
bool knownElement = false;
switch (this.reader.NodeType)
{
#region Element
case XmlNodeType.Element:
this.TraceElement();
this.CheckAgainstFailure();
elementName = this.reader.LocalName;
namespaceUri = this.reader.NamespaceURI;
if (this.expectingSingleValue)
{
#region return known primitive/reference typed value
// CreateQuery and the result is value
// we don't really care what the namespace is though with astoria
// it would be XmlConstants.DataWebNamespace ("http://schemas.microsoft.com/ado/2007/08/dataservices")
propertyValue = this.ReadElementString(true);
if (null != propertyValue)
{
currentValue = ClientConvert.ChangeType(propertyValue, Nullable.GetUnderlyingType(expectedType) ?? expectedType);
}
this.version = -this.version;
return true;
#endregion
}
if (((AtomParseState.None == atom) || (AtomParseState.LinkFeed == atom)) &&
AreSame(XmlConstants.AtomNamespace, namespaceUri) &&
AreSame(XmlConstants.AtomFeedElementName, elementName))
{
#region
if (this.reader.IsEmptyElement)
{
this.version = -this.version;
return false;
}
if (AtomParseState.None == atom)
{ // leave state in LinkFeed state so we exit properly when no exist
atom = AtomParseState.Feed;
}
Debug.Assert(AtomParseState.Feed == atom || AtomParseState.LinkFeed == atom, "feed state");
knownElement = true;
#endregion
}
else if (((AtomParseState.LinkEntry == atom) || (AtomParseState.LinkFeed == atom) || (AtomParseState.Feed == atom) || AtomParseState.None == atom) &&
AreSame(XmlConstants.AtomNamespace, namespaceUri) &&
AreSame(XmlConstants.AtomEntryElementName, elementName))
{
#region
atom = AtomParseState.Entry;
this.PushBufferedReader(ref currentEntry);
currentType = this.ReadTypeAttribute(currentEntry, expectedType);
etag = this.reader.GetAttribute(XmlConstants.AtomETagAttributeName, XmlConstants.DataWebMetadataNamespace);
if (currentType.HasKeys)
{
haveSetKeyProperty = new ArraySet(currentType.KeyCount);
}
knownElement = true;
#endregion
}
else if ((AtomParseState.Entry == atom) &&
AreSame(XmlConstants.AtomNamespace, namespaceUri))
{
if (AreSame(XmlConstants.AtomIdElementName, elementName))
{
#region resource uri
// The "atom:id" element conveys a permanent, universally unique
// identifier for an entry or feed.
// Its content MUST be an IRI, as defined by [RFC3987]. Note that the
// definition of "IRI" excludes relative references. Though the IRI
// might use a dereferencable scheme, Atom Processors MUST NOT assume it
// can be dereferenced.
Debug.Assert(null == currentValue, "already has an instance");
Debug.Assert(null == identity, "multiple ");
identity = Util.CreateUri(this.ReadElementString(false), UriKind.RelativeOrAbsolute);
if (!identity.IsAbsoluteUri)
{
throw Error.InvalidOperation(Strings.Context_TrackingExpectsAbsoluteUri);
}
// ReferenceIdentity is a test hook to help verify we dont' use identity instead of editLink
identity = Util.ReferenceIdentity(identity);
knownElement = true;
#endregion
}
else if (AreSame(XmlConstants.AtomContentElementName, elementName))
{
#region
propertyValue = this.reader.GetAttributeEx(XmlConstants.AtomContentSrcAttributeName, XmlConstants.AtomNamespace);
if (propertyValue != null)
{
// This is a media link entry
// Note that in this case we don't actually use this URL (or the link/edit-media URL)
// for editing. We rely on the Astoria URL convention (/propname/$value or just /$value)
if (!this.reader.IsEmptyElement)
{
throw Error.InvalidOperation(Strings.Deserialize_ExpectedEmptyMediaLinkEntryContent);
}
mediaLinkEntry = true;
knownElement = true;
}
else
{
// This is a regular (non-media link) entry
if (mediaLinkEntry.HasValue && mediaLinkEntry.Value)
{
// This means we saw a element but now we have a Content element
// that's not just a media link entry pointer (src)
throw Error.InvalidOperation(Strings.Deserialize_ContentPlusPropertiesNotAllowed);
}
mediaLinkEntry = false;
propertyValue = this.reader.GetAttributeEx(XmlConstants.AtomTypeAttributeName, XmlConstants.AtomNamespace);
if (XmlConstants.MimeApplicationXml == propertyValue || XmlConstants.MimeApplicationAtom == propertyValue)
{
if (this.ReadChildElement(XmlConstants.AtomPropertiesElementName, XmlConstants.DataWebMetadataNamespace))
{
this.TraceElement();
hasContentProperties = true;
atom = AtomParseState.Content;
}
else if (!AreSame(this.reader, XmlConstants.AtomContentElementName, XmlConstants.AtomNamespace))
{
throw Error.InvalidOperation(Strings.Deserialize_NotApplicationXml);
}
knownElement = true;
}
}
#endregion
}
else if (AreSame(XmlConstants.AtomLinkElementName, elementName))
{
string rel = this.reader.GetAttributeEx(XmlConstants.AtomLinkRelationAttributeName, XmlConstants.AtomNamespace);
if (XmlConstants.AtomEditRelationAttributeValue == rel)
{
#region uri
editLink = Util.CreateUri(this.reader.GetAttributeEx(XmlConstants.AtomHRefAttributeName, XmlConstants.AtomNamespace), UriKind.RelativeOrAbsolute);
#endregion
}
else if (!this.reader.IsEmptyElement)
{
propertyName = UriUtil.GetNameFromAtomLinkRelationAttribute(rel);
#region
if (null != propertyName)
{
Debug.Assert(!setNestedValue, "should not be set going in link");
property = currentType.GetProperty(propertyName, this.ignoreMissingProperties); // may throw
if (null == property)
{
propertyName = null;
}
else
{
if (property.IsKnownType)
{
throw Error.InvalidOperation(Strings.Deserialize_MismatchAtomLinkLocalSimple);
}
if (this.ResolveOrCreateInstance(currentType, identity, editLink, etag, ref currentValue, ref entityState))
{
atom = AtomParseState.Feed;
this.FireEventAndPopBufferedReader(currentValue, ref currentEntry);
return true;
}
object parent = currentValue;
Type nestedType = property.PropertyType;
propertyValue = this.reader.GetAttribute(XmlConstants.AtomTypeAttributeName);
if (String.Equals("application/atom+xml;type=feed", propertyValue))
{ // parent becomes the collection, not the currentValue
#region LinkFeed
#if ASTORIA_OPEN_OBJECT
if (property.OpenObjectProperty)
{ // get the nested IList from the dictionary
nestedType = typeof(OpenObject);
((IDictionary)property.GetValue(currentValue)).TryGetValue(propertyName, out parent);
if ((null != parent) && !(parent is IList))
{
throw Error.InvalidOperation(Strings.Deserialize_MismatchAtomLinkFeedPropertyNotCollection(propertyName));
}
}
else
#endif
if (null == (nestedType = property.CollectionType))
{
throw Error.InvalidOperation(Strings.Deserialize_MismatchAtomLinkFeedPropertyNotCollection(propertyName));
}
else
{ // get the nested IList from the current value
parent = property.GetValue(currentValue);
}
if (null == parent)
{
setNestedValue = true;
// if the current property is not settable
// we fail after populating contents into a collection
Type implementationType;
#if ASTORIA_OPEN_OBJECT
if (property.OpenObjectProperty)
{
implementationType = typeof(System.Collections.ObjectModel.Collection);
}
else
#endif
{
implementationType = property.PropertyType;
if (implementationType.IsInterface)
{
implementationType = typeof(System.Collections.ObjectModel.Collection<>).MakeGenericType(nestedType);
}
}
parent = Activator.CreateInstance(implementationType);
}
atom = AtomParseState.LinkFeed;
#endregion
}
else if (String.Equals("application/atom+xml;type=entry", propertyValue))
{
#region LinkEntry
#if ASTORIA_OPEN_OBJECT
if (property.OpenObjectProperty)
{
nestedType = typeof(OpenObject);
}
else
#endif
if (null != property.CollectionType)
{
throw Error.InvalidOperation(Strings.Deserialize_MismatchAtomLinkEntryPropertyIsCollection(propertyName));
}
atom = AtomParseState.LinkEntry;
#endregion
}
int linkCount = 0;
AtomParseState linkStart = atom;
#region read
// Check for empty inline element - that indicates that the value is null
// This is only true for reference properties
if (this.ReadChildElement(XmlConstants.AtomInlineElementName, XmlConstants.DataWebMetadataNamespace))
{
this.TraceElement();
Debug.Assert(
(currentValue == parent && AtomParseState.LinkEntry == atom) ||
(currentValue != parent && AtomParseState.LinkFeed == atom && parent is IEnumerable),
"unexpected parent");
Debug.Assert(null == nestedValue, "nestedValue should be null before reading links");
Debug.Assert(AtomParseState.LinkFeed == atom || AtomParseState.LinkEntry == atom, "expecting LinkEntry or LinkFeed");
if (!this.reader.IsEmptyElement)
{
#region read
EntityStates nestedState = 0;
while ((0 <= this.version) &&
this.reader.Read() &&
this.ReadNext(null, nestedType, atom, ref nestedState, ref nestedValue))
{
// will currentValue.Collection.Add or currentValue.set_Reference
if ((MergeOption.AppendOnly != this.MergeOptionValue) ||
(EntityStates.Detached == entityState))
{
#if ASTORIA_OPEN_OBJECT
if ((setNestedValue || parent is IList) && property.OpenObjectProperty)
{
((IList)parent).Add((OpenObject)nestedValue);
}
else
{
property.SetValue(parent, nestedValue, propertyName, ref openProperties, true);
}
#else
property.SetValue(parent, nestedValue, propertyName, true);
#endif
// if null == nestedValue, that implies an existing reference may now be gone
// however, since we don't "refresh" - we leave that previous relationship intact
if ((null != nestedValue) &&
#if ASTORIA_OPEN_OBJECT
!property.OpenObjectProperty &&
#endif
currentType.HasKeys && ClientType.Create(nestedValue.GetType()).HasKeys)
{
if (null == this.links)
{
this.links = new List();
}
// cache the links we want to add - but can't until after both ends exist in the context
EntityStates linkState = (AtomParseState.LinkEntry == linkStart) ? EntityStates.Modified : EntityStates.Added;
this.links.Add(new LinkDescriptor(currentValue, property.PropertyName, nestedValue, linkState));
}
}
linkCount++;
nestedValue = null;
atom = AtomParseState.LinkEntry;
if ((AtomParseState.LinkEntry == linkStart) && (0 != linkCount))
{ //
break; // while
}
}
#endregion
}
#region no links
if ((0 == linkCount) && (AtomParseState.LinkEntry == linkStart))
{ // set null nested value
if ((MergeOption.AppendOnly != this.MergeOptionValue) ||
(EntityStates.Detached == entityState))
{
if (null == this.links)
{
this.links = new List();
}
// AppendOnly & NoTracking - only if creating new object
// OverwriteChanges - server wins
// PreserveChanges - new object, non-null target -> unchanged, null target -> modified
Debug.Assert(
(MergeOption.AppendOnly == this.MergeOptionValue && EntityStates.Detached == entityState) ||
(MergeOption.NoTracking == this.MergeOptionValue && EntityStates.Detached == entityState) ||
(MergeOption.OverwriteChanges == this.MergeOptionValue) ||
(MergeOption.PreserveChanges == this.MergeOptionValue),
"unexpected merge of link reference");
Debug.Assert(null == nestedValue, "expecting null nestedValue");
this.links.Add(new LinkDescriptor(currentValue, property.PropertyName, null, EntityStates.Modified));
parent = null;
setNestedValue = true;
}
}
else if ((AtomParseState.LinkFeed == linkStart) &&
(MergeOption.OverwriteChanges == this.MergeOptionValue || MergeOption.PreserveChanges == this.MergeOptionValue))
{ // detach extra remaining links
object collection = null;
var q = (from x in this.context.GetLinks(currentValue, property.PropertyName)
where MergeOption.OverwriteChanges == this.MergeOptionValue || EntityStates.Added != x.State
select new LinkDescriptor(x.SourceResource, x.SourceProperty, x.TargetResouce, EntityStates.Detached))
.Except(this.links, LinkDescriptor.EquivalentComparer);
foreach (LinkDescriptor p in q)
{
if (null == collection)
{
collection = property.GetValue(currentValue);
}
if (null != collection)
{
property.RemoveValue(collection, p.Target);
}
this.links.Add(p);
}
}
#endregion
if (setNestedValue)
{
nestedValue = parent;
}
Debug.Assert(this.reader.IsEmptyElement || XmlNodeType.EndElement == this.reader.NodeType, "expected EndElement");
Debug.Assert(
(this.reader.IsEmptyElement && AreSame(XmlConstants.AtomInlineElementName, this.reader.LocalName)) ||
(AtomParseState.LinkFeed == linkStart && AreSame(XmlConstants.AtomFeedElementName, this.reader.LocalName)) ||
(AtomParseState.LinkEntry == linkStart && AreSame(XmlConstants.AtomEntryElementName, this.reader.LocalName)),
"link didn't end on expected element");
}
this.SkipToEnd(XmlConstants.AtomLinkElementName, XmlConstants.AtomNamespace);
#endregion
Debug.Assert(((XmlNodeType.Element == this.reader.NodeType && this.reader.IsEmptyElement) ||
(XmlNodeType.EndElement == this.reader.NodeType)) &&
AreSame(this.reader, XmlConstants.AtomLinkElementName, XmlConstants.AtomNamespace),
"didn't land on ");
#region Set collection / reference instance
if (setNestedValue)
{
#if ASTORIA_OPEN_OBJECT
property.SetValue(currentValue, nestedValue, propertyName, ref openProperties, false);
#else
property.SetValue(currentValue, nestedValue, propertyName, false);
#endif
nestedValue = null;
}
#endregion
setNestedValue = false;
propertyName = null;
property = null;
Debug.Assert(null == nestedValue, "null == nestedValue");
knownElement = true;
}
}
atom = AtomParseState.Entry;
#endregion
}
}
}
else if ((AtomParseState.Entry == atom) &&
AreSame(XmlConstants.DataWebMetadataNamespace, namespaceUri) &&
AreSame(XmlConstants.AtomPropertiesElementName, elementName))
{
#region
if (mediaLinkEntry.HasValue && !mediaLinkEntry.Value)
{
// This means we saw a non-empty element but now we have a Properties element
// that also carries properties
throw Error.InvalidOperation(Strings.Deserialize_ContentPlusPropertiesNotAllowed);
}
mediaLinkEntry = true;
if (!this.reader.IsEmptyElement)
{
atom = AtomParseState.Content; // Semantically this is equivalent to content, so treat it the same
}
knownElement = true;
#endregion
}
else if (((AtomParseState.Content == atom) || (AtomParseState.Complex == atom)) &&
AreSame(this.dataNamespace, namespaceUri))
{
if (this.ResolveOrCreateInstance(currentType, identity, editLink, etag, ref currentValue, ref entityState))
{
atom = AtomParseState.Feed;
this.FireEventAndPopBufferedReader(currentValue, ref currentEntry);
return true;
}
#region MergeOption checks
Debug.Assert(null != currentValue, "null currentValue");
Debug.Assert(0 != entityState, "expected entitystate");
if (((MergeOption.AppendOnly == this.MergeOptionValue) && (EntityStates.Detached != entityState)) ||
((MergeOption.PreserveChanges == this.MergeOptionValue) && (EntityStates.Added == entityState || EntityStates.Modified == entityState)))
{ // do not change content in this state
this.SkipToEnd(this.reader.LocalName, this.reader.NamespaceURI);
break;
}
Debug.Assert(
(MergeOption.NoTracking == this.MergeOptionValue && EntityStates.Detached == entityState) ||
(MergeOption.AppendOnly == this.MergeOptionValue && EntityStates.Detached == entityState) ||
(MergeOption.PreserveChanges == this.MergeOptionValue && (EntityStates.Detached == entityState || EntityStates.Unchanged == entityState || EntityStates.Deleted == entityState)) ||
(MergeOption.OverwriteChanges == this.MergeOptionValue),
"NoTracking and AppendOnly, PreserveChanges should not change content of existing objects");
#endregion
#region
property = currentType.GetProperty(elementName, this.ignoreMissingProperties); // may throw
if (null != property)
{
propertyName = elementName;
atom = (AtomParseState.Content == atom) ? AtomParseState.PropertyContent : AtomParseState.PropertyComplex;
if (property.KeyProperty)
{
haveSetKeyProperty.Add(propertyName, null);
}
#if ASTORIA_OPEN_OBJECT
if (property.IsKnownType ||
ClientConvert.IsKnownType((nestedElementType = this.ReadTypeAttribute(property.OpenObjectProperty ? typeof(OpenObject) : property.PropertyType)).ElementType))
#else
if (property.IsKnownType ||
ClientConvert.IsKnownType((nestedElementType = this.ReadTypeAttribute(property.PropertyType)).ElementType))
#endif
{
// propertyValue
propertyValue = this.ReadElementString(true);
object value = propertyValue;
if (null != propertyValue)
{
value = ClientConvert.ChangeType(propertyValue, (null != nestedElementType ? nestedElementType.ElementType : property.PropertyType));
}
#if ASTORIA_OPEN_OBJECT
property.SetValue(currentValue, value, propertyName, ref openProperties, false);
#else
property.SetValue(currentValue, value, propertyName, false);
#endif
atom = (AtomParseState.PropertyContent == atom) ? AtomParseState.Content : AtomParseState.Complex;
property = null;
propertyName = null;
nestedElementType = null;
hasValue = false;
}
else if (this.reader.IsEmptyElement)
{
#if ASTORIA_OPEN_OBJECT
property.SetValue(currentValue, null, propertyName, ref openProperties, false);
#else
property.SetValue(currentValue, null, propertyName, false);
#endif
}
else
{
#if ASTORIA_OPEN_OBJECT
if (!property.OpenObjectProperty && (null != property.CollectionType))
#else
if (null != property.CollectionType)
#endif
{ // client object property is collection implying nested collection of complex objects
throw Error.NotSupported(Strings.ClientType_CollectionOfNonEntities);
}
#region recurse into complex type
if (null == nestedValue)
{ // retreived only once per collection or nested object
nestedValue = property.GetValue(currentValue);
#if ASTORIA_OPEN_OBJECT
if (property.OpenObjectProperty)
{
if (null != nestedValue)
{
((IDictionary)nestedValue).TryGetValue(propertyName, out nestedValue);
}
else
{
throw Error.InvalidOperation(Strings.Collection_NullCollectionReference(currentType.ElementTypeName, property.PropertyName));
}
}
#endif
}
if (null == nestedValue)
{ // when the value is null, assume the recursion will create the object
hasValue = true;
}
if (this.reader.Read() &&
this.ReadNext(nestedElementType, nestedElementType.ElementType, AtomParseState.Complex, ref entityState, ref nestedValue))
{
setNestedValue = (hasValue = (nestedValue != null));
hasValue = true;
goto case XmlNodeType.EndElement;
}
#endregion
}
knownElement = true;
}
#endregion
}
else if ((AtomParseState.None == atom) && AreSame(this.dataNamespace, namespaceUri) && (null == currentType))
{ // complex type?
Debug.Assert(0 == entityState, "shouldn't have existing entity");
ClientType tmp = this.ReadTypeAttribute(expectedType);
if (!tmp.HasKeys)
{
currentType = tmp;
if (this.reader.IsEmptyElement || this.DoesNullAttributeSayTrue())
{
this.SkipToEnd(this.reader.LocalName, this.reader.NamespaceURI);
return true;
}
atom = AtomParseState.Complex;
entityState = EntityStates.Detached;
knownElement = true;
}
}
if (!knownElement)
{
this.SkipToEnd(this.reader.LocalName, this.reader.NamespaceURI);
}
break;
#endregion
#region Text
case XmlNodeType.Text:
case XmlNodeType.SignificantWhitespace:
Debug.Assert(!this.expectingSingleValue, "!this.expectingSingleValue, Text");
// throw an exception that we hit mixed-content
// propertyValue elementText
throw Error.InvalidOperation(Strings.Deserialize_MixedContent(currentType.ElementTypeName));
#endregion
#region EndElement
case XmlNodeType.EndElement:
Debug.Assert(!this.expectingSingleValue, "!this.expectingSingleValue");
this.TraceEndElement(true);
if (null == currentType)
{ //
// or reader started in a bad state (which is okay)
Debug.Assert(!hasValue, "EndElement w/ haveValue");
Debug.Assert(!setNestedValue, "EndElement w/ setNestedValue");
Debug.Assert(null == property, "EndElement w/ property");
Debug.Assert(null == propertyName, "EndElement w/ propertyName");
Debug.Assert(null == nestedValue, "EndElement w/ nestedValue");
Debug.Assert(null == nestedElementType, "EndElement w/ nestedElementType");
// SQLBUDT 565573
if (AtomParseState.LinkEntry != atom && AtomParseState.LinkFeed != atom)
{
atom = AtomParseState.None;
this.version = -this.version;
}
return false;
}
if (null == property)
{ //
Debug.Assert(!hasValue, "EndElement property w/ haveValue");
Debug.Assert(!setNestedValue, "EndElement property w/ setNestedValue");
Debug.Assert(null == property, "EndElement property w/ propertyName");
Debug.Assert(null == nestedValue, "EndElement property w/ nestedValue");
Debug.Assert(null == nestedElementType, "EndElement property w/ nestedElementType");
Debug.Assert(null != currentType, "Should have derived a type from the entry or from the query");
if (AtomParseState.Content == atom)
{
if (hasContentProperties)
{
this.SkipToEnd(XmlConstants.AtomContentElementName, XmlConstants.AtomNamespace);
}
atom = AtomParseState.Entry;
continue;
}
else if (AtomParseState.Complex == atom)
{
return true;
}
else if (AtomParseState.Entry == atom)
{
this.SkipToEnd(XmlConstants.AtomEntryElementName, XmlConstants.AtomNamespace);
atom = AtomParseState.Feed;
// it might happen that we see no data properties in an entry, just
// metadata such as the ID. In the case were no properties where present
// we just create an uninitialized entry and attach it if needed
this.ResolveOrCreateInstance(currentType, identity, editLink, etag, ref currentValue, ref entityState);
// validate all keys have been set
if (currentType.HasKeys && (EntityStates.Added == entityState || EntityStates.Detached == entityState))
{
foreach (ClientType.ClientProperty p in currentType.Properties)
{
// if property is a key and not set
if (p.KeyProperty && (null == haveSetKeyProperty.Remove(p.PropertyName, String.Equals)))
{
throw Error.InvalidOperation(Strings.BatchStream_ContentExpected(p.PropertyName));
}
}
}
this.FireEventAndPopBufferedReader(currentValue, ref currentEntry);
return true;
}
Debug.Assert(false, "invalid Atom state");
this.version = -this.version;
Error.ThrowInternalError(InternalError.UnexpectedReadState);
}
//
Debug.Assert(null != currentValue, "null currentValue");
Debug.Assert(null != propertyName, "null propertyName");
Debug.Assert(!setNestedValue || (null != property), "null property w/ nestedValue");
if ((null != nestedValue) &&
(setNestedValue ||
#if ASTORIA_OPEN_OBJECT
property.OpenObjectProperty ||
#endif
nestedValue.GetType().IsValueType))
{
Debug.Assert(null != nestedValue, "EndElement after nested w/o nestedValue");
// if hasValue is false, we still want to set the nestedValue instance
//
// which is different than
// which would have set null for nested complex property
#if ASTORIA_OPEN_OBJECT
property.SetValue(currentValue, nestedValue, propertyName, ref openProperties, false);
#else
property.SetValue(currentValue, nestedValue, propertyName, false);
#endif
}
else if (!hasValue)
{ //
Debug.Assert(null != property, "null property without value");
#if ASTORIA_OPEN_OBJECT
property.SetValue(currentValue, null, propertyName, ref openProperties, false);
#else
property.SetValue(currentValue, null, propertyName, false);
#endif
}
// finished reading property, continue parsing element
Debug.Assert(
AtomParseState.PropertyContent == atom ||
AtomParseState.PropertyComplex == atom,
"expected atom property state");
atom = (AtomParseState.PropertyContent == atom) ? AtomParseState.Content : AtomParseState.Complex;
property = null;
propertyName = null;
hasValue = false;
nestedElementType = null;
nestedValue = null;
setNestedValue = false;
break;
#endregion
#region fail on unhandled content nodes, similar to XmlReader.MoveToContent()
case XmlNodeType.CDATA:
case XmlNodeType.EntityReference:
case XmlNodeType.EndEntity:
// Checks whether the current node is a content (non-whitespace text, CDATA, Element, EndElement, EntityReference
// or EndEntity) node. If the node is not a content node, then the method skips ahead to the next content node or
// end of file. Skips over nodes of type ProcessingInstruction, DocumentType, Comment, Whitespace and SignificantWhitespace.
this.version = -this.version;
Error.ThrowInternalError(InternalError.UnexpectedXmlNodeTypeWhenReading);
break;
#endregion
}
}
while (this.reader.Read());
Debug.Assert(null == currentValue, "non-null currentValue");
this.version = -this.version;
return false;
}
///
/// create an instance and associate its identity for callstack identity resolution
///
/// type to create
/// identity of instance
/// editLink of instance
/// etag of identity
/// current value instance
/// entity state
/// new instance of type
private bool ResolveOrCreateInstance(ClientType type, Uri identity, Uri editLink, string etag, ref object currentValue, ref EntityStates state)
{
if (null == currentValue)
{
if (null != identity)
{
#region handle POST entity response
if (null != this.inserting)
{
Debug.Assert(MergeOption.OverwriteChanges == this.MergeOptionValue, "only expected value during SaveChanges");
// always attach an identity, so that inserted relationships can also happen
// however, if NoTracking then its left in the inserted bucket
this.context.AttachIdentity(identity, editLink, this.inserting, etag);
}
#endregion
if (type.HasKeys)
{
Debug.Assert(null != identity, "missing identity");
if (MergeOption.NoTracking != this.MergeOptionValue)
{
#region handle POST entity response
if (null != this.inserting)
{
Debug.Assert(null == currentValue, "it already has a value?");
currentValue = this.inserting;
state = EntityStates.Unchanged;
this.inserting = null;
Debug.Assert(type.HasKeys, "!HasKeys");
Debug.Assert(type.ElementType.IsInstanceOfType(currentValue), "!IsInstanceOfType");
}
#endregion
if (null != (currentValue ?? (currentValue = this.context.TryGetEntity(identity, etag, this.MergeOptionValue, out state))))
{
if (!type.ElementType.IsInstanceOfType(currentValue))
{
throw Error.InvalidOperation(Strings.Deserialize_Current(type.ElementType, currentValue.GetType()));
}
}
}
}
if ((null == currentValue) && (null != this.identityStack))
{ // always do identity resolution on the callstack within a top-level entity
// but avoid the AppendOnly behavior for deep expanded items
EntityWithTag pair;
this.identityStack.TryGetValue(identity, out pair);
currentValue = pair.Entity;
state = pair.State;
}
}
else
{
Debug.Assert(!type.HasKeys, "entity without identity");
}
if ((null == currentValue) || (this.MergeOptionValue == MergeOption.OverwriteChanges))
{
if (null == currentValue)
{
currentValue = type.CreateInstance();
state = EntityStates.Detached;
}
if (null != identity)
{
if (null == this.identityStack)
{
this.identityStack = new Dictionary();
}
this.identityStack.Add(identity, new EntityWithTag(currentValue, editLink, etag, state, type.HasKeys));
}
}
}
return false;
}
///
/// Read subtree into an XElement, push the reader into the stack and set the current
/// reader to a reader on top of the XElement
///
/// Current XElement being processed
private void PushBufferedReader(ref XElement currentEntry)
{
XmlReader subtree = this.reader.ReadSubtree();
try
{
currentEntry = XElement.Load(subtree);
}
catch (XmlException ex)
{
throw new DataServiceClientException(Strings.Deserialize_ServerException1, ex);
}
finally
{
subtree.Close();
}
if (this.stackedReaders == null)
{
this.stackedReaders = new Stack();
}
this.stackedReaders.Push(this.reader);
this.reader = currentEntry.CreateReader();
this.reader.Read();
}
///
/// Fire the reader event with the current buffered subtree reader and then pop
/// the next reader in the stack
///
/// Current .NET object being read
/// Buffered entry with data from the input stream
private void FireEventAndPopBufferedReader(object currentValue, ref XElement currentEntry)
{
if (this.context.HasReadingEntityHandlers)
{
Debug.Assert(currentEntry != null && currentValue != null, "Event present but did not create buffered entry?");
Debug.Assert(this.stackedReaders.Count > 0, "Event present but previous reader not pushed?");
this.context.FireReadingEntityEvent(currentValue, currentEntry);
}
this.reader = this.stackedReaders.Pop();
currentEntry = null;
}
///
/// Gets the type attribte and resolves the type from the current XmlReader.
///
/// expected base type
/// resolved type
private ClientType ReadTypeAttribute(Type expectedType)
{
string typeName = this.reader.GetAttribute(XmlConstants.AtomTypeAttributeName, XmlConstants.DataWebMetadataNamespace);
Type resolvedType = this.context.ResolveTypeFromName(typeName, expectedType);
return ClientType.Create(resolvedType);
}
///
/// Gets the type attribte and resolves the type from the specified .
///
/// XML element for the ATOM entry.
/// expected base type
/// resolved type
private ClientType ReadTypeAttribute(XElement entryElement, Type expectedType)
{
Debug.Assert(entryElement != null, "entryElement != null");
// XNamespace.None is used for attribute because they are not namespace-qualified in the payloads.
XNamespace atomNs = XmlConstants.AtomNamespace;
XName categoryName = atomNs + XmlConstants.AtomCategoryElementName;
XName schemeName = XNamespace.None + XmlConstants.AtomCategorySchemeAttributeName;
XName termName = XNamespace.None + XmlConstants.AtomCategoryTermAttributeName;
string typeSchemeText = this.context.TypeScheme.OriginalString;
var categories = entryElement.Elements(categoryName);
var category = categories.Where(c => c.Attributes(schemeName).Any(a => a.Value == typeSchemeText)).FirstOrDefault();
var categoryTitle = (category == null) ? null : category.Attributes(termName).FirstOrDefault();
string typeName = (categoryTitle == null) ? null : categoryTitle.Value;
Type resolvedType = this.context.ResolveTypeFromName(typeName, expectedType);
return ClientType.Create(resolvedType);
}
///
/// it can concatenate multiple adjacent text and CDATA blocks
/// ignores comments and whitespace
/// will leave reader on EndElement
///
/// check for null attribute or not
/// text contained in the element. An null string if the element was empty
private string ReadElementString(bool checkNullAttribute)
{
Debug.Assert(XmlNodeType.Element == this.reader.NodeType, "not positioned on Element");
string result = null;
bool empty = checkNullAttribute && !this.DoesNullAttributeSayTrue();
if (this.reader.IsEmptyElement)
{
return (empty ? String.Empty : null);
}
while (this.reader.Read())
{
switch (this.reader.NodeType)
{
case XmlNodeType.EndElement:
this.TraceEndElement(false);
return result ?? (empty ? String.Empty : null);
case XmlNodeType.CDATA:
case XmlNodeType.Text:
case XmlNodeType.SignificantWhitespace:
if (null != result)
{
throw Error.InvalidOperation(Strings.Deserialize_MixedTextWithComment);
}
this.TraceText(this.reader.Value);
result = this.reader.Value;
break;
case XmlNodeType.Comment:
case XmlNodeType.Whitespace:
break;
#region XmlNodeType error
case XmlNodeType.Element:
this.CheckAgainstFailure();
goto default;
default:
this.version = -this.version;
throw Error.InvalidOperation(Strings.Deserialize_ExpectingSimpleValue);
#endregion
}
}
// xml ended before EndElement?
this.version = -this.version;
throw Error.InvalidOperation(Strings.Deserialize_ExpectingSimpleValue);
}
/// Move to top-level child element of the expected type
/// localName of expected child content-node
/// namespaceUri of expected child content-node
/// true if moved to expected child content node
private bool ReadChildElement(string localName, string namespaceUri)
{
return (!this.reader.IsEmptyElement &&
(this.reader.NodeType != XmlNodeType.EndElement) &&
this.reader.Read() &&
this.reader.IsStartElement(localName, namespaceUri));
}
///
/// Skips the children of the current node.
///
/// local name of the node we search to
/// namespace of the node we search to
private void SkipToEnd(string localName, string namespaceURI)
{
//
//
//
XmlReader xmlReader = this.reader;
int readerDepth = xmlReader.Depth;
do
{
switch (xmlReader.NodeType)
{
case XmlNodeType.Element:
this.CheckAgainstFailure();
if (xmlReader.IsEmptyElement &&
xmlReader.Depth == readerDepth &&
AreSame(xmlReader, localName, namespaceURI))
{
return;
}
break;
case XmlNodeType.EndElement:
if (xmlReader.Depth <= readerDepth)
{ // new end element
this.TraceEndElement(true);
readerDepth--;
if (AreSame(xmlReader, localName, namespaceURI))
{
return;
}
}
break;
}
}
while (xmlReader.Read());
}
/// throw if current element is Exception
/// if current element is exception
private void CheckAgainstFailure()
{
Debug.Assert(XmlNodeType.Element == this.reader.NodeType, "not Element");
if (AreSame(this.reader, XmlConstants.XmlErrorElementName, XmlConstants.DataWebMetadataNamespace))
{
string message = this.ReadErrorMessage();
this.version = -this.version;
// In case of instream errors, the status code should be 500 (which is the default)
throw new DataServiceClientException(Strings.Deserialize_ServerException(message));
}
}
/// With the reader positioned on an 'error' element, reads the text of the 'message' child.
/// The text of the 'message' child element, empty if not found.
private string ReadErrorMessage()
{
Debug.Assert(AreSame(this.reader, XmlConstants.XmlErrorElementName, XmlConstants.DataWebMetadataNamespace), "AreSame(this.reader, XmlConstants.XmlErrorElementName, XmlConstants.DataWebMetadataNamespace)");
int depth = 1;
while (depth > 0 && this.reader.Read())
{
if (this.reader.NodeType == XmlNodeType.Element)
{
if (!this.reader.IsEmptyElement)
{
depth++;
}
if (depth == 2 &&
AreSame(this.reader, XmlConstants.XmlErrorMessageElementName, XmlConstants.DataWebMetadataNamespace))
{
return this.ReadElementString(false);
}
}
else if (this.reader.NodeType == XmlNodeType.EndElement)
{
depth--;
}
}
return String.Empty;
}
///
/// check the atom:null="true" attribute
///
/// true of null is true
private bool DoesNullAttributeSayTrue()
{
string attributeValue = this.reader.GetAttribute(XmlConstants.AtomNullAttributeName, XmlConstants.DataWebMetadataNamespace);
return ((null != attributeValue) && XmlConvert.ToBoolean(attributeValue));
}
#region Tracing
///
/// trace Element node
///
[Conditional("TRACE")]
private void TraceElement()
{
Debug.Assert(XmlNodeType.Element == this.reader.NodeType, "not positioned on Element");
if (null != this.writer)
{
this.writer.Write(Util.GetWhitespaceForTracing(2 + this.reader.Depth), 0, 2 + this.reader.Depth);
this.writer.Write("<{0}", this.reader.Name);
if (this.reader.MoveToFirstAttribute())
{
do
{
this.writer.Write(" {0}=\"{1}\"", this.reader.Name, this.reader.Value);
}
while (this.reader.MoveToNextAttribute());
this.reader.MoveToElement();
}
this.writer.Write(this.reader.IsEmptyElement ? " />" : ">");
}
}
///
/// trace EndElement node
///
/// indent or not
[Conditional("TRACE")]
private void TraceEndElement(bool indent)
{
if (null != this.writer)
{
if (indent)
{
this.writer.Write(Util.GetWhitespaceForTracing(2 + this.reader.Depth), 0, 2 + this.reader.Depth);
}
this.writer.Write("{0}>", this.reader.Name);
}
}
///
/// trace string value
///
/// value
[Conditional("TRACE")]
private void TraceText(string value)
{
if (null != this.writer)
{
this.writer.Write(value);
}
}
#endregion
///
/// hold information about a newly constructed object until the very end of MoveNext
///
private struct EntityWithTag
{
/// entity being attached
internal readonly object Entity;
/// uri to edit the entity
/// <atom:link rel="edit" href="editLink" />
internal readonly Uri EditLink;
/// etag of entity being attached
internal readonly string ETag;
/// state of the entitiy
internal readonly EntityStates State;
/// should we actually attach the entity, i.e. false if OpenType
internal readonly bool AttachToContext;
///
/// ctor
///
/// entity
/// editLink
/// etag
/// state
/// attach
internal EntityWithTag(object entity, Uri editLink, string etag, EntityStates state, bool attach)
{
this.Entity = entity;
this.EditLink = editLink;
this.ETag = etag;
this.State = state;
this.AttachToContext = attach;
}
}
///
/// materialize objects from an application/atom+xml stream
///
/// base type of the object to create from the stream
internal sealed class MaterializeAtomT : MaterializeAtom, IEnumerable, IEnumerator
{
///
/// constructor
///
/// originating context
/// reader
internal MaterializeAtomT(DataServiceContext context, XmlReader reader)
: base(context, reader, typeof(T), context.MergeOption)
{
}
#region Current
///
/// typed current object
///
[DebuggerHidden]
T IEnumerator.Current
{
get { return (T)this.Current; }
}
#endregion
#region IEnumerable
///
/// as IEnumerable
///
/// this
IEnumerator IEnumerable.GetEnumerator()
{
this.CheckGetEnumerator();
return this;
}
#endregion
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- DataObjectPastingEventArgs.cs
- BitmapFrameDecode.cs
- ObjectMemberMapping.cs
- StyleCollection.cs
- BypassElement.cs
- ViewStateModeByIdAttribute.cs
- CustomAttribute.cs
- ContractCodeDomInfo.cs
- HttpSessionStateWrapper.cs
- NamedPipeConnectionPoolSettingsElement.cs
- Int32Converter.cs
- CalendarDataBindingHandler.cs
- TextDecorationCollection.cs
- FixedDSBuilder.cs
- TopClause.cs
- SmiRecordBuffer.cs
- AnnotationDocumentPaginator.cs
- ButtonColumn.cs
- TypeToken.cs
- MDIControlStrip.cs
- EdmProviderManifest.cs
- FullTextLine.cs
- TraceHandlerErrorFormatter.cs
- TextServicesProperty.cs
- ChtmlPhoneCallAdapter.cs
- UInt64Storage.cs
- TextTreePropertyUndoUnit.cs
- DispatcherFrame.cs
- IgnoreSectionHandler.cs
- _Rfc2616CacheValidators.cs
- ServiceDesigner.cs
- ListViewGroupConverter.cs
- PropertyMapper.cs
- ConfigurationCollectionAttribute.cs
- TypeGeneratedEventArgs.cs
- Transform3DGroup.cs
- FrameworkTemplate.cs
- OleDbConnectionFactory.cs
- securitycriticaldata.cs
- HttpModuleActionCollection.cs
- ComPlusTypeLoader.cs
- SqlNodeTypeOperators.cs
- KerberosSecurityTokenProvider.cs
- ObjRef.cs
- AppLevelCompilationSectionCache.cs
- Listbox.cs
- SmiRequestExecutor.cs
- ChangeInterceptorAttribute.cs
- Sql8ConformanceChecker.cs
- NullableLongSumAggregationOperator.cs
- ProgressPage.cs
- DodSequenceMerge.cs
- SimpleWebHandlerParser.cs
- ToolStripDropDown.cs
- Calendar.cs
- SponsorHelper.cs
- DataChangedEventManager.cs
- OutputCacheProfile.cs
- CustomValidator.cs
- AdPostCacheSubstitution.cs
- TextFindEngine.cs
- ObjectListCommand.cs
- DefaultDiscoveryServiceExtension.cs
- PluggableProtocol.cs
- ListViewGroupItemCollection.cs
- CodeRegionDirective.cs
- StylusCollection.cs
- XMLSchema.cs
- NativeActivityFaultContext.cs
- UserControl.cs
- OperationParameterInfo.cs
- NavigationCommands.cs
- indexingfiltermarshaler.cs
- XmlAtomicValue.cs
- RegexEditorDialog.cs
- LocationReferenceEnvironment.cs
- ConfigXmlElement.cs
- SspiWrapper.cs
- Context.cs
- LocalizableAttribute.cs
- EnumUnknown.cs
- FragmentQuery.cs
- DataGridTextColumn.cs
- FontFamily.cs
- XmlnsDictionary.cs
- DecoderReplacementFallback.cs
- UriSectionReader.cs
- ExtensionWindowResizeGrip.cs
- DynamicILGenerator.cs
- objectresult_tresulttype.cs
- ProxySimple.cs
- StateFinalizationActivity.cs
- ObjectDataSourceSelectingEventArgs.cs
- DataStreams.cs
- DataGrid.cs
- StreamGeometry.cs
- DependencyPropertyHelper.cs
- IndexedDataBuffer.cs
- ScrollableControlDesigner.cs
- TypeLoadException.cs