511 lines
18 KiB
C#
511 lines
18 KiB
C#
//Copyright 2010 Microsoft Corporation
|
|
//
|
|
//Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
|
|
//You may obtain a copy of the License at
|
|
//
|
|
//http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
//Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
|
|
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
//See the License for the specific language governing permissions and limitations under the License.
|
|
|
|
|
|
namespace System.Data.Services.Client
|
|
{
|
|
#region Namespaces.
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Xml;
|
|
using System.Xml.Linq;
|
|
using System.Text;
|
|
using System.Linq.Expressions;
|
|
|
|
#endregion Namespaces.
|
|
|
|
internal class MaterializeAtom : IDisposable, IEnumerable, IEnumerator
|
|
{
|
|
internal readonly MergeOption MergeOptionValue;
|
|
|
|
#region Private fields.
|
|
|
|
private const long CountStateInitial = -1;
|
|
|
|
private const long CountStateFailure = -2;
|
|
|
|
private readonly bool ignoreMissingProperties;
|
|
|
|
private readonly DataServiceContext context;
|
|
|
|
private readonly Type elementType;
|
|
|
|
private readonly bool expectingSingleValue;
|
|
|
|
private readonly AtomMaterializer materializer;
|
|
|
|
private readonly AtomParser parser;
|
|
|
|
private XmlReader reader;
|
|
|
|
private object current;
|
|
|
|
private bool calledGetEnumerator;
|
|
|
|
private long countValue;
|
|
|
|
private bool moved;
|
|
|
|
#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
|
|
|
|
#endregion Private fields.
|
|
|
|
internal MaterializeAtom(DataServiceContext context, XmlReader reader, QueryComponents queryComponents, ProjectionPlan plan, MergeOption mergeOption)
|
|
{
|
|
Debug.Assert(queryComponents != null, "queryComponents != null");
|
|
|
|
this.context = context;
|
|
this.elementType = queryComponents.LastSegmentType;
|
|
this.MergeOptionValue = mergeOption;
|
|
this.ignoreMissingProperties = context.IgnoreMissingProperties;
|
|
this.reader = (reader == null) ? null : new System.Data.Services.Client.Xml.XmlAtomErrorReader(reader);
|
|
this.countValue = CountStateInitial;
|
|
this.expectingSingleValue = ClientConvert.IsKnownNullableType(elementType);
|
|
|
|
Debug.Assert(reader != null, "Materializer reader is null! Did you mean to use Materializer.ResultsWrapper/EmptyResults?");
|
|
|
|
reader.Settings.NameTable.Add(context.DataNamespace);
|
|
|
|
string typeScheme = this.context.TypeScheme.OriginalString;
|
|
this.parser = new AtomParser(this.reader, AtomParser.XElementBuilderCallback, typeScheme, context.DataNamespace);
|
|
AtomMaterializerLog log = new AtomMaterializerLog(this.context, mergeOption);
|
|
Type implementationType;
|
|
Type materializerType = GetTypeForMaterializer(this.expectingSingleValue, this.elementType, out implementationType);
|
|
this.materializer = new AtomMaterializer(parser, context, materializerType, this.ignoreMissingProperties, mergeOption, log, this.MaterializedObjectCallback, queryComponents, plan);
|
|
}
|
|
|
|
private void MaterializedObjectCallback(object tag, object entity)
|
|
{
|
|
Debug.Assert(tag != null, "tag != null");
|
|
Debug.Assert(entity != null, "entity != null");
|
|
|
|
XElement data = (XElement)tag;
|
|
if (this.context.HasReadingEntityHandlers)
|
|
{
|
|
XmlUtil.RemoveDuplicateNamespaceAttributes(data);
|
|
this.context.FireReadingEntityEvent(entity, data);
|
|
}
|
|
}
|
|
|
|
private MaterializeAtom()
|
|
{
|
|
}
|
|
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
|
private MaterializeAtom(DataServiceContext context, XmlReader reader, Type type, MergeOption mergeOption)
|
|
: this(context, reader, new QueryComponents(null, Util.DataServiceVersionEmpty, type, null, null), null, mergeOption)
|
|
{
|
|
}
|
|
|
|
#region Current
|
|
|
|
public object Current
|
|
{
|
|
get
|
|
{
|
|
object currentValue = this.current;
|
|
return currentValue;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
internal static MaterializeAtom EmptyResults
|
|
{
|
|
get
|
|
{
|
|
return new ResultsWrapper(null, null);
|
|
}
|
|
}
|
|
|
|
internal bool IsEmptyResults
|
|
{
|
|
get { return this.reader == null; }
|
|
}
|
|
|
|
internal DataServiceContext Context
|
|
{
|
|
get { return this.context; }
|
|
}
|
|
|
|
#region IDisposable
|
|
public void Dispose()
|
|
{
|
|
this.current = null;
|
|
|
|
if (null != this.reader)
|
|
{
|
|
((IDisposable)this.reader).Dispose();
|
|
}
|
|
|
|
if (null != this.writer)
|
|
{
|
|
this.writer.Dispose();
|
|
}
|
|
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IEnumerable
|
|
public virtual IEnumerator GetEnumerator()
|
|
{
|
|
this.CheckGetEnumerator();
|
|
return this;
|
|
}
|
|
#endregion
|
|
|
|
private static Type GetTypeForMaterializer(bool expectingSingleValue, Type elementType, out Type implementationType)
|
|
{
|
|
if (!expectingSingleValue && typeof(IEnumerable).IsAssignableFrom(elementType))
|
|
{
|
|
implementationType = ClientType.GetImplementationType(elementType, typeof(ICollection<>));
|
|
if (implementationType != null)
|
|
{
|
|
Type expectedType = implementationType.GetGenericArguments()[0]; return expectedType;
|
|
}
|
|
}
|
|
|
|
implementationType = null;
|
|
return elementType;
|
|
}
|
|
|
|
public bool MoveNext()
|
|
{
|
|
bool applying = this.context.ApplyingChanges;
|
|
try
|
|
{
|
|
this.context.ApplyingChanges = true;
|
|
return this.MoveNextInternal();
|
|
}
|
|
finally
|
|
{
|
|
this.context.ApplyingChanges = applying;
|
|
}
|
|
}
|
|
|
|
private bool MoveNextInternal()
|
|
{
|
|
if (this.reader == null)
|
|
{
|
|
Debug.Assert(this.current == null, "this.current == null -- otherwise this.reader should have some value.");
|
|
return false;
|
|
}
|
|
|
|
this.current = null;
|
|
this.materializer.Log.Clear();
|
|
|
|
bool result = false;
|
|
Type implementationType;
|
|
GetTypeForMaterializer(this.expectingSingleValue, this.elementType, out implementationType);
|
|
if (implementationType != null)
|
|
{
|
|
if (this.moved)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Type expectedType = implementationType.GetGenericArguments()[0]; implementationType = this.elementType;
|
|
if (implementationType.IsInterface)
|
|
{
|
|
implementationType = typeof(System.Collections.ObjectModel.Collection<>).MakeGenericType(expectedType);
|
|
}
|
|
|
|
IList list = (IList)Activator.CreateInstance(implementationType);
|
|
|
|
while (this.materializer.Read())
|
|
{
|
|
this.moved = true;
|
|
list.Add(this.materializer.CurrentValue);
|
|
}
|
|
|
|
this.current = list;
|
|
result = true;
|
|
}
|
|
|
|
if (null == this.current)
|
|
{
|
|
if (this.expectingSingleValue && this.moved)
|
|
{
|
|
result = false;
|
|
}
|
|
else
|
|
{
|
|
result = this.materializer.Read();
|
|
if (result)
|
|
{
|
|
this.current = this.materializer.CurrentValue;
|
|
}
|
|
|
|
this.moved = true;
|
|
}
|
|
}
|
|
|
|
this.materializer.Log.ApplyToContext();
|
|
|
|
return result;
|
|
}
|
|
|
|
void System.Collections.IEnumerator.Reset()
|
|
{
|
|
throw Error.NotSupported();
|
|
}
|
|
|
|
internal static MaterializeAtom CreateWrapper(IEnumerable results)
|
|
{
|
|
return new ResultsWrapper(results, null);
|
|
}
|
|
|
|
internal static MaterializeAtom CreateWrapper(IEnumerable results, DataServiceQueryContinuation continuation)
|
|
{
|
|
return new ResultsWrapper(results, continuation);
|
|
}
|
|
|
|
internal void SetInsertingObject(object addedObject)
|
|
{
|
|
this.materializer.TargetInstance = addedObject;
|
|
}
|
|
|
|
internal static void SkipToEnd(XmlReader reader)
|
|
{
|
|
Debug.Assert(reader != null, "reader != null");
|
|
Debug.Assert(reader.NodeType == XmlNodeType.Element, "reader.NodeType == XmlNodeType.Element");
|
|
|
|
if (reader.IsEmptyElement)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int readerDepth = reader.Depth;
|
|
while (reader.Read())
|
|
{
|
|
if (reader.NodeType == XmlNodeType.EndElement && reader.Depth == readerDepth)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal long CountValue()
|
|
{
|
|
if (this.countValue == CountStateInitial)
|
|
{
|
|
this.ReadCountValue();
|
|
}
|
|
else if (this.countValue == CountStateFailure)
|
|
{
|
|
throw new InvalidOperationException(Strings.MaterializeFromAtom_CountNotPresent);
|
|
}
|
|
|
|
return this.countValue;
|
|
}
|
|
|
|
internal virtual DataServiceQueryContinuation GetContinuation(IEnumerable key)
|
|
{
|
|
Debug.Assert(this.materializer != null, "Materializer is null!");
|
|
|
|
DataServiceQueryContinuation result;
|
|
if (key == null)
|
|
{
|
|
if ((this.expectingSingleValue && !this.moved) || (!this.expectingSingleValue && !this.materializer.IsEndOfStream))
|
|
{
|
|
throw new InvalidOperationException(Strings.MaterializeFromAtom_TopLevelLinkNotAvailable);
|
|
}
|
|
|
|
if (this.expectingSingleValue || this.materializer.CurrentFeed == null)
|
|
{
|
|
result = null;
|
|
}
|
|
else
|
|
{
|
|
result = DataServiceQueryContinuation.Create(
|
|
this.materializer.CurrentFeed.NextLink,
|
|
this.materializer.MaterializeEntryPlan);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!this.materializer.NextLinkTable.TryGetValue(key, out result))
|
|
{
|
|
throw new ArgumentException(Strings.MaterializeFromAtom_CollectionKeyNotPresentInLinkTable);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private void CheckGetEnumerator()
|
|
{
|
|
if (this.calledGetEnumerator)
|
|
{
|
|
throw Error.NotSupported(Strings.Deserialize_GetEnumerator);
|
|
}
|
|
|
|
this.calledGetEnumerator = true;
|
|
}
|
|
|
|
private void ReadCountValue()
|
|
{
|
|
Debug.Assert(this.countValue == CountStateInitial, "Count value is not in the initial state");
|
|
|
|
if (this.materializer.CurrentFeed != null &&
|
|
this.materializer.CurrentFeed.Count.HasValue)
|
|
{
|
|
this.countValue = this.materializer.CurrentFeed.Count.Value;
|
|
return;
|
|
}
|
|
|
|
while (this.reader.NodeType != XmlNodeType.Element && this.reader.Read())
|
|
{
|
|
}
|
|
|
|
if (this.reader.EOF)
|
|
{
|
|
throw new InvalidOperationException(Strings.MaterializeFromAtom_CountNotPresent);
|
|
}
|
|
|
|
Debug.Assert(
|
|
(Util.AreSame(XmlConstants.AtomNamespace, this.reader.NamespaceURI) &&
|
|
Util.AreSame(XmlConstants.AtomFeedElementName, this.reader.LocalName)) ||
|
|
(Util.AreSame(XmlConstants.DataWebNamespace, this.reader.NamespaceURI) &&
|
|
Util.AreSame(XmlConstants.LinkCollectionElementName, this.reader.LocalName)),
|
|
"<feed> or <links> tag expected");
|
|
|
|
XElement element = XElement.Load(this.reader);
|
|
this.reader.Close();
|
|
|
|
XElement countNode = element.Descendants(XNamespace.Get(XmlConstants.DataWebMetadataNamespace) + XmlConstants.RowCountElement).FirstOrDefault();
|
|
|
|
if (countNode == null)
|
|
{
|
|
throw new InvalidOperationException(Strings.MaterializeFromAtom_CountNotPresent);
|
|
}
|
|
else
|
|
{
|
|
if (!long.TryParse(countNode.Value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out this.countValue))
|
|
{
|
|
throw new FormatException(Strings.MaterializeFromAtom_CountFormatError);
|
|
}
|
|
|
|
if (this.countValue < 0)
|
|
{
|
|
throw new FormatException(Strings.MaterializeFromAtom_CountFormatError);
|
|
}
|
|
}
|
|
|
|
this.reader = new System.Data.Services.Client.Xml.XmlAtomErrorReader(element.CreateReader());
|
|
this.parser.ReplaceReader(this.reader);
|
|
}
|
|
|
|
internal static ClientType GetEntryClientType(string typeName, DataServiceContext context, Type expectedType, bool checkAssignable)
|
|
{
|
|
Debug.Assert(context != null, "context != null");
|
|
Type resolvedType = context.ResolveTypeFromName(typeName, expectedType, checkAssignable);
|
|
ClientType result = ClientType.Create(resolvedType);
|
|
Debug.Assert(result != null, "result != null -- otherwise ClientType.Create returned null");
|
|
return result;
|
|
}
|
|
|
|
internal static string ReadElementString(XmlReader reader, bool checkNullAttribute)
|
|
{
|
|
Debug.Assert(reader != null, "reader != null");
|
|
Debug.Assert(
|
|
reader.NodeType == XmlNodeType.Element,
|
|
"reader.NodeType == XmlNodeType.Element -- otherwise caller is confused as to where the reader is");
|
|
|
|
string result = null;
|
|
bool empty = checkNullAttribute && !Util.DoesNullAttributeSayTrue(reader);
|
|
|
|
if (reader.IsEmptyElement)
|
|
{
|
|
return (empty ? String.Empty : null);
|
|
}
|
|
|
|
while (reader.Read())
|
|
{
|
|
switch (reader.NodeType)
|
|
{
|
|
case XmlNodeType.EndElement:
|
|
return result ?? (empty ? String.Empty : null);
|
|
case XmlNodeType.CDATA:
|
|
case XmlNodeType.Text:
|
|
case XmlNodeType.SignificantWhitespace:
|
|
if (null != result)
|
|
{
|
|
throw Error.InvalidOperation(Strings.Deserialize_MixedTextWithComment);
|
|
}
|
|
|
|
result = reader.Value;
|
|
break;
|
|
case XmlNodeType.Comment:
|
|
case XmlNodeType.Whitespace:
|
|
break;
|
|
|
|
#region XmlNodeType error
|
|
case XmlNodeType.Element:
|
|
goto default;
|
|
|
|
default:
|
|
throw Error.InvalidOperation(Strings.Deserialize_ExpectingSimpleValue);
|
|
#endregion
|
|
}
|
|
}
|
|
|
|
throw Error.InvalidOperation(Strings.Deserialize_ExpectingSimpleValue);
|
|
}
|
|
|
|
private class ResultsWrapper : MaterializeAtom
|
|
{
|
|
#region Private fields.
|
|
|
|
private readonly IEnumerable results;
|
|
|
|
private readonly DataServiceQueryContinuation continuation;
|
|
|
|
#endregion Private fields.
|
|
|
|
internal ResultsWrapper(IEnumerable results, DataServiceQueryContinuation continuation)
|
|
{
|
|
this.results = results ?? new object[0];
|
|
this.continuation = continuation;
|
|
}
|
|
|
|
internal override DataServiceQueryContinuation GetContinuation(IEnumerable key)
|
|
{
|
|
if (key == null)
|
|
{
|
|
return this.continuation;
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidOperationException(Strings.MaterializeFromAtom_GetNestLinkForFlatCollection);
|
|
}
|
|
}
|
|
|
|
public override IEnumerator GetEnumerator()
|
|
{
|
|
return this.results.GetEnumerator();
|
|
}
|
|
}
|
|
}
|
|
}
|