Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / ndp / fx / src / DataWeb / Client / System / Data / Services / Client / ALinq / ResourceBinder.cs / 2 / ResourceBinder.cs
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Expression visitor that binds expression tree to resources
//
//
// @owner [....]
//---------------------------------------------------------------------
namespace System.Data.Services.Client
{
#region Namespaces.
using System;
using System.Collections.Generic;
using System.Data.Services.Common;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
#endregion Namespaces.
///
/// This class provides a Bind method that analyzes an input and returns a bound tree.
///
internal class ResourceBinder : DataServiceExpressionVisitor
{
///
/// Sets of that require a key predicate that hasn't been satisfied yet.
///
private readonly HashSet setsRequiringKeyPredicate = new HashSet(EqualityComparer.Default);
/// Analyzes and binds the specified expression.
/// Expression to bind.
/// The expression with bound nodes.
internal static Expression Bind(Expression e)
{
ResourceBinder rb = new ResourceBinder();
Expression boundExpression = rb.Visit(e);
// Post Process bound tree
// check to see if path segment ends in multi-valued property with no key predicate
ResourceSetExpression rse = boundExpression as ResourceSetExpression;
if (rse != null)
{
rse.IsLeafResource = true;
}
// If there are missing key predicates, only throw if translation was successful and the missing
// key predicate is the only error. Otherwise, allow the expression that could not be translated
// to propagate to the UriWriter which will throw to indicate that it is not supported.
if ((boundExpression is ResourceExpression) &&
rb.setsRequiringKeyPredicate.Count > 0)
{
throw new NotSupportedException(Strings.ALinq_CantNavigateWithoutKeyPredicate);
}
return boundExpression;
}
private Expression AnalyzePredicate(MethodCallExpression mce)
{
// Validate that the input is a resource set and retrieve the Lambda that defines the predicate
ResourceSetExpression input;
LambdaExpression le;
if (!TryGetResourceSetMethodArguments(mce, out input, out le))
{
// might have failed because of singleton, so throw better error if so.
ValidationRules.RequireNonSingleton(mce.Arguments[0]);
return mce;
}
//
// Valid predicate patterns are as follows:
// 1. A URI-compatible filter applied to the input resource set
// 2. A key-predicate filter applied to the input resource set
// - Additionally, key-predicate filters may be applied to any resource path component
// that does not already have a key-predicate filter, regardless of any filter applied
// to the current input resource set, if transparent scopes are present.
// - It is not valid to apply a key-predicate or a filter to a resource set
// for which key-predicate is already present.
// - It is valid to apply a filter to a resource set for which a filter already exists;
// such filters are AND'd together.
//
// [Key-predicate that targets a path component AND]* [Key-predicate over input | URI-compatible filter over input]+
List conjuncts = new List();
AddConjuncts(le.Body, conjuncts);
Dictionary> predicatesByTarget = new Dictionary>();
List referencedInputs = new List();
foreach (Expression e in conjuncts)
{
Expression reboundPredicate = InputBinder.Bind(e, input, le.Parameters[0], referencedInputs);
if (referencedInputs.Count > 1)
{
// UNSUPPORTED: A single clause cannot refer to more than one resource set
return mce;
}
ResourceSetExpression boundTarget = (referencedInputs.Count == 0 ? input : referencedInputs[0] as ResourceSetExpression);
if (boundTarget == null)
{
// UNSUPPORTED: Each clause must refer to a path component that is a resource set, not a singleton navigation property
return mce;
}
List targetPredicates = null;
if (!predicatesByTarget.TryGetValue(boundTarget, out targetPredicates))
{
targetPredicates = new List();
predicatesByTarget[boundTarget] = targetPredicates;
}
targetPredicates.Add(reboundPredicate);
referencedInputs.Clear();
}
conjuncts = null;
List inputPredicates;
if (predicatesByTarget.TryGetValue(input, out inputPredicates))
{
predicatesByTarget.Remove(input);
}
else
{
inputPredicates = null;
}
foreach (KeyValuePair> predicates in predicatesByTarget)
{
ResourceSetExpression target = predicates.Key;
List clauses = predicates.Value;
Dictionary keyValues;
if (!ExtractKeyPredicate(target, clauses, out keyValues) ||
clauses.Count > 0)
{
// UNSUPPORTED: Only key predicates may be applied to earlier path components
return mce;
}
// Earlier path components must be navigation sources, and navigation sources cannot have query options.
Debug.Assert(!target.HasQueryOptions, "Navigation source had query options?");
SetKeyPredicate(target, keyValues);
}
if (inputPredicates != null)
{
Dictionary inputKeyValues;
if (ExtractKeyPredicate(input, inputPredicates, out inputKeyValues))
{
if (input.HasSequenceQueryOptions)
{
// SQLBUDT 616297: A key predicate cannot be applied if query options other than 'Expand' are present,
// so merge the key predicate into the filter query option instead.
inputPredicates.Add(BuildKeyPredicateFilter(input.CreateReference(), inputKeyValues));
}
else
{
SetKeyPredicate(input, inputKeyValues);
}
}
if (inputPredicates.Count > 0)
{
if (input.KeyPredicate != null)
{
// SQLBUDT 616297: a filter query option cannot be specified if a key predicate is present,
// so remove the existing key predicate and add it to inputPredicates as a filter. Note that
// this can only be done on the current resource set, where the filter query option is being
// applied, since earlier sets in the resource path are navigations that require a singleton,
// enforced by the presence of a key predicate.
inputPredicates.Add(BuildKeyPredicateFilter(input.CreateReference(), input.KeyPredicate));
input.KeyPredicate = null;
}
int start;
Expression newFilter;
if (input.Filter != null)
{
start = 0;
newFilter = input.Filter.Predicate;
}
else
{
start = 1;
newFilter = inputPredicates[0];
}
for (int idx = start; idx < inputPredicates.Count; idx++)
{
newFilter = Expression.And(newFilter, inputPredicates[idx]);
}
AddSequenceQueryOption(input, new FilterQueryOptionExpression(mce.Method.ReturnType, newFilter));
}
}
return input; // No need to adjust this.currentResource - filters are merged in all cases
}
private void SetKeyPredicate(ResourceSetExpression rse, Dictionary keyValues)
{
Debug.Assert(rse != null, "rse != null");
Debug.Assert(keyValues != null, "keyValues != null");
if (rse.KeyPredicate == null)
{
rse.KeyPredicate = new Dictionary();
}
foreach(var g in keyValues)
{
if (rse.KeyPredicate.Keys.Contains(g.Key))
{
//UNSUPPORTED: = AND = are multiple key predicates and
//cannot be represented as a resource path.
throw Error.NotSupported(Strings.ALinq_CanOnlyApplyOneKeyPredicate);
}
rse.KeyPredicate.Add(g.Key, g.Value);
}
this.setsRequiringKeyPredicate.Remove(rse);
}
private static bool ExtractKeyPredicate(ResourceSetExpression target, List predicates, out Dictionary keyValues)
{
keyValues = null;
List nonKeyPredicates = null;
foreach (Expression predicate in predicates)
{
PropertyInfo property;
ConstantExpression constantValue;
if (PatternRules.MatchKeyComparison(predicate, out property, out constantValue))
{
if (keyValues == null)
{
keyValues = new Dictionary();
}
else if (keyValues.ContainsKey(property))
{
// UNSUPPORTED: = AND = are multiple key predicates and
// cannot be represented as a resource path.
throw Error.NotSupported(Strings.ALinq_CanOnlyApplyOneKeyPredicate);
}
keyValues.Add(property, constantValue);
}
else
{
if (nonKeyPredicates == null)
{
nonKeyPredicates = new List();
}
nonKeyPredicates.Add(predicate);
}
}
Debug.Assert(keyValues != null || nonKeyPredicates != null, "No key predicates or non-key predicates found?");
if (keyValues != null &&
!PatternRules.GetKeyProperties(target.CreateReference().Type).SequenceEqual(keyValues.Keys, PropertyInfoEqualityComparer.Instance))
{
keyValues = null;
return false;
}
// If keyValues is non-null then at least one expression from predicates was a key comparison
// and should no longer be present in the predicates list since it is now in the key values dictionary.
if (keyValues != null)
{
// Remove all predicates.
predicates.Clear();
// If any non-key predicates were found, add them back
if (nonKeyPredicates != null)
{
predicates.AddRange(nonKeyPredicates);
}
}
return (keyValues != null);
}
private static Expression BuildKeyPredicateFilter(InputReferenceExpression input, Dictionary keyValuesDictionary)
{
Debug.Assert(keyValuesDictionary.Count > 0, "At least one key property is required in a key predicate");
Expression retExpr = null;
foreach (KeyValuePair keyValue in keyValuesDictionary)
{
Expression clause = Expression.Equal(Expression.Property(input, keyValue.Key), keyValue.Value);
if (retExpr == null)
{
retExpr = clause;
}
else
{
retExpr = Expression.And(retExpr, clause);
}
}
return retExpr;
}
private static void AddConjuncts(Expression e, List conjuncts)
{
if (PatternRules.MatchAnd(e))
{
BinaryExpression be = (BinaryExpression)e;
AddConjuncts(be.Left, conjuncts);
AddConjuncts(be.Right, conjuncts);
}
else
{
conjuncts.Add(e);
}
}
internal Expression AnalyzeProjection(MethodCallExpression mce)
{
Expression input = mce.Arguments[0];
LambdaExpression le;
if(!PatternRules.MatchSingleArgumentLambda(mce.Arguments[1], out le))
{
return mce;
}
else if (PatternRules.MatchIdentitySelector(le))
{
return input;
}
else if (PatternRules.MatchTransparentIdentitySelector(input, le))
{
return RemoveTransparentScope(mce.Method.ReturnType, (ResourceSetExpression)input);
}
ResourceExpression navSource;
Expression boundProjection;
if (IsValidNavigationSource(input, out navSource) &&
TryBindToInput(navSource, le, out boundProjection) &&
PatternRules.MatchPropertyProjectionSingleton(navSource, boundProjection))
{
return CreateNavigationPropertySingletonExpression(mce.Method.ReturnType, navSource, boundProjection);
}
return mce;
}
private bool IsValidNavigationSource(Expression input, out ResourceExpression sourceExpression)
{
ValidationRules.RequireCanNavigate(input);
sourceExpression = input as ResourceExpression;
if (sourceExpression != null && !sourceExpression.IsSingleton)
{
ResourceSetExpression rse = (ResourceSetExpression)sourceExpression;
this.setsRequiringKeyPredicate.Add(rse);
}
return (sourceExpression != null);
}
internal Expression AnalyzeSelectMany(MethodCallExpression mce)
{
if (mce.Arguments.Count != 2 && mce.Arguments.Count != 3)
{
return mce;
}
ResourceExpression input;
if (!IsValidNavigationSource(mce.Arguments[0], out input))
{
return mce;
}
LambdaExpression le;
if(!PatternRules.MatchSingleArgumentLambda(mce.Arguments[1], out le))
{
return mce;
}
List referencedInputs = new List();
Expression navPropRef = InputBinder.Bind(le.Body, input, le.Parameters[0], referencedInputs);
Type resourceType = TypeSystem.GetElementType(navPropRef.Type);
// could be a Cast sequence operator here that can be optimized out.
SequenceMethod sm;
MethodCallExpression mce2 = navPropRef as MethodCallExpression;
while (mce2 != null && ReflectionUtil.TryIdentifySequenceMethod(mce2.Method, out sm) && sm == SequenceMethod.Cast)
{
navPropRef = mce2.Arguments[0];
mce2 = navPropRef as MethodCallExpression;
}
ResourceSetExpression rse = null;
if (PatternRules.MatchPropertyProjectionSet(input, navPropRef))
{
rse = CreateResourceSetExpression(mce.Method.ReturnType, input, navPropRef, resourceType);
}
else
{
return mce;
}
if (mce.Arguments.Count == 3)
{
LambdaExpression selector = StripQuotes(mce.Arguments[2]) as LambdaExpression;
// now examine selector
// Check for transparent scope result - projects the input and the selector
ResourceSetExpression.TransparentAccessors transparentScope;
if (PatternRules.MatchTransparentScopeSelector(selector, out transparentScope))
{
rse.TransparentScope = transparentScope;
}
else if (!PatternRules.MatchIdentityProjectionResultSelector(selector))
{
return mce;
}
}
return rse;
}
internal static Expression ApplyOrdering(MethodCallExpression mce, ResourceSetExpression input, Expression selector, bool descending, bool thenBy)
{
List selectors;
if (!thenBy)
{
selectors = new List();
AddSequenceQueryOption(input, new OrderByQueryOptionExpression(mce.Type, selectors));
}
else
{
Debug.Assert(input.OrderBy != null);
selectors = input.OrderBy.Selectors;
}
selectors.Add(new OrderByQueryOptionExpression.Selector(selector, descending));
return input;
}
#if !ASTORIA_LIGHT // Silverlight doesn't support synchronous operators.
/// Ensures that there's a limit on the cardinality of a query.
/// for the method to limit First/Single(OrDefault).
/// Maximum cardinality to allow.
///
/// An expression that limits to no more than elements.
///
/// This method is used by .First(OrDefault) and .Single(OrDefault) to limit cardinality.
private static Expression LimitCardinality(MethodCallExpression mce, int maxCardinality)
{
Debug.Assert(mce != null, "mce != null");
Debug.Assert(maxCardinality > 0, "Cardinality must be at least 1");
if (mce.Arguments.Count != 1)
{
// Don't support methods with predicates.
return mce;
}
ResourceSetExpression rse = mce.Arguments[0] as ResourceSetExpression;
if (rse != null)
{
if (!rse.HasKeyPredicate && // no-op, already returns a singleton
(ResourceExpressionType)rse.NodeType != ResourceExpressionType.ResourceNavigationProperty)
{
if (rse.Take == null || (int)rse.Take.TakeAmount.Value > maxCardinality)
{
AddSequenceQueryOption(rse, new TakeQueryOptionExpression(mce.Type, Expression.Constant(maxCardinality)));
}
}
return mce.Arguments[0];
}
else if (mce.Arguments[0] is NavigationPropertySingletonExpression)
{
// no-op, already returns a singleton
return mce.Arguments[0];
}
return mce;
}
#endif
private static Expression AnalyzeCast(MethodCallExpression mce)
{
ResourceExpression rse = mce.Arguments[0] as ResourceExpression;
if (rse != null)
{
return rse.Cast(mce.Method.ReturnType);
}
return mce;
}
private static Expression StripConvert(Expression e)
{
UnaryExpression ue = e as UnaryExpression;
if (ue != null &&
ue.NodeType == ExpressionType.Convert &&
ue.Type.IsGenericType &&
ue.Type.GetGenericTypeDefinition() == typeof(DataServiceQuery<>))
{
e = ue.Operand;
ResourceExpression re = e as ResourceExpression;
if (re != null)
{
e = re.Cast(ue.Type);
}
}
return e;
}
private static Expression AnalyzeExpand(MethodCallExpression mce)
{
Expression obj = StripConvert(mce.Object);
ResourceExpression re = obj as ResourceExpression;
if (re == null)
{
return mce;
}
ValidationRules.RequireCanExpand(re);
ConstantExpression ce = mce.Arguments[0] as ConstantExpression;
re.ExpandPaths.Add(ce);
return re;
}
private static Expression AnalyzeAddCustomQueryOption(MethodCallExpression mce)
{
Expression obj = StripConvert(mce.Object);
ResourceExpression re = obj as ResourceExpression;
if (re == null)
{
return mce;
}
ValidationRules.RequireCanAddCustomQueryOption(re);
ValidationRules.RequireLegalCustomQueryOption(mce.Arguments[0], re);
ConstantExpression name = mce.Arguments[0] as ConstantExpression;
ConstantExpression value = mce.Arguments[1] as ConstantExpression;
re.CustomQueryOptions.Add(name, value);
return re;
}
private static ResourceSetExpression CreateResourceSetExpression(Type type, ResourceExpression source, Expression memberExpression, Type resourceType)
{
ResourceSetExpression newResource = new ResourceSetExpression(type, source, memberExpression, resourceType, source.ExpandPaths.ToList(), source.CustomQueryOptions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
source.ExpandPaths.Clear();
source.CustomQueryOptions.Clear();
return newResource;
}
private static NavigationPropertySingletonExpression CreateNavigationPropertySingletonExpression(Type type, ResourceExpression source, Expression memberExpression)
{
NavigationPropertySingletonExpression newResource = new NavigationPropertySingletonExpression(type, source, memberExpression, source.ExpandPaths.ToList(), source.CustomQueryOptions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
source.ExpandPaths.Clear();
source.CustomQueryOptions.Clear();
return newResource;
}
///
/// Produces a new that is a clone of in all respects,
/// other than its result type - which will be - and it's transparent scope,
/// which will be null . This is a shallow clone operation - sequence query options, key predicate, etc are
/// not cloned, but are reassigned to the new instance. The resource expression should be
/// discarded after being used with this method.
///
/// The result type - - that the new resource set expression should have.
/// The resource set expression from which the transparent scope is being removed
/// A new resource set expression without an enclosing transparent scope and with the specified result type.
private static ResourceSetExpression RemoveTransparentScope(Type expectedResultType, ResourceSetExpression input)
{
// Create a new resource set expression based on the input
ResourceSetExpression newResource = new ResourceSetExpression(expectedResultType, input.Source, input.MemberExpression, input.ResourceType, input.ExpandPaths, input.CustomQueryOptions);
// Reassign state items that are not constructor arguments - query options + key predicate
newResource.KeyPredicate = input.KeyPredicate;
foreach (QueryOptionExpression queryOption in input.SequenceQueryOptions)
{
newResource.AddSequenceQueryOption(queryOption);
}
// Instruct the new resource set expression to use input's Input Reference instead of creating its own.
// This will also update the Input Reference to specify the new resource set expression as it's target,
// so that any usage of it in query option expressions is consistent with the new resource set expression.
newResource.OverrideInputReference(input);
return newResource;
}
private static Expression StripQuotes(Expression e)
{
return e.NodeType == ExpressionType.Quote ? ((UnaryExpression)e).Operand : e;
}
internal override Expression VisitResourceSetExpression(ResourceSetExpression rse)
{
if ((ResourceExpressionType)rse.NodeType == ResourceExpressionType.RootResourceSet)
{
Debug.Assert(!rse.HasQueryOptions);
// since we could be adding query options to the root, create a new one which can be mutable.
return new ResourceSetExpression(rse.Type, rse.Source, rse.MemberExpression, rse.ResourceType, null, null);
}
return rse;
}
private static bool TryGetResourceSetMethodArguments(MethodCallExpression mce, out ResourceSetExpression input, out LambdaExpression lambda)
{
input = null;
lambda = null;
input = mce.Arguments[0] as ResourceSetExpression;
if (input != null &&
PatternRules.MatchSingleArgumentLambda(mce.Arguments[1], out lambda))
{
return true;
}
return false;
}
private static bool TryBindToInput(ResourceExpression input, LambdaExpression le, out Expression bound)
{
List referencedInputs = new List();
bound = InputBinder.Bind(le.Body, input, le.Parameters[0], referencedInputs);
if (referencedInputs.Count > 1 || (referencedInputs.Count == 1 && referencedInputs[0] != input))
{
bound = null;
}
return (bound != null);
}
private static Expression AnalyzeResourceSetMethod(MethodCallExpression mce, Func sequenceMethodAnalyzer)
{
ResourceSetExpression input;
LambdaExpression le;
if (!TryGetResourceSetMethodArguments(mce, out input, out le))
{
// UNSUPPORTED: Expected LambdaExpression as second argument to sequence method
return mce;
}
Expression lambdaBody;
if (!TryBindToInput(input, le, out lambdaBody))
{
// UNSUPPORTED: Lambda should reference the input, and only the input
return mce;
}
return sequenceMethodAnalyzer(mce, input, lambdaBody);
}
private static Expression AnalyzeResourceSetConstantMethod(MethodCallExpression mce, Func constantMethodAnalyzer)
{
ResourceExpression input = (ResourceExpression)mce.Arguments[0];
ConstantExpression constantArg = mce.Arguments[1] as ConstantExpression;
if (null == constantArg)
{
// UNSUPPORTED: A ConstantExpression is expected
return mce;
}
return constantMethodAnalyzer(mce, input, constantArg);
}
private static void AddSequenceQueryOption(ResourceExpression target, QueryOptionExpression qoe)
{
ValidationRules.RequireNonSingleton(target);
ResourceSetExpression rse = (ResourceSetExpression)target;
// Validation that can add option
switch (qoe.NodeType)
{
case (ExpressionType)ResourceExpressionType.FilterQueryOption:
if (rse.Skip != null)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("filter", "skip"));
}
else if (rse.Take != null)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("filter", "top"));
}
break;
case (ExpressionType)ResourceExpressionType.OrderByQueryOption:
if (rse.Skip != null)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("orderby", "skip"));
}
else if (rse.Take != null)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("orderby", "top"));
}
break;
case (ExpressionType)ResourceExpressionType.SkipQueryOption:
if (rse.Take != null)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("skip", "top"));
}
break;
default:
break;
}
rse.AddSequenceQueryOption(qoe);
}
internal override Expression VisitBinary(BinaryExpression b)
{
Expression e = base.VisitBinary(b);
if (PatternRules.MatchStringAddition(e))
{
BinaryExpression be = e as BinaryExpression;
MethodInfo mi = typeof(string).GetMethod("Concat", new Type[] { typeof(string), typeof(string)});
return Expression.Call(mi, new Expression[] {be.Left, be.Right});
}
return e;
}
internal override Expression VisitMemberAccess(MemberExpression m)
{
Expression e = base.VisitMemberAccess(m);
MemberExpression me = e as MemberExpression;
PropertyInfo pi;
MethodInfo mi;
if (me != null &&
PatternRules.MatchNonPrivateReadableProperty(me, out pi) &&
TypeSystem.TryGetPropertyAsMethod(pi, out mi))
{
return Expression.Call(me.Expression, mi);
}
return e;
}
internal override Expression VisitMethodCall(MethodCallExpression mce)
{
Expression e = base.VisitMethodCall(mce);
mce = e as MethodCallExpression;
if (mce != null)
{
SequenceMethod sequenceMethod;
if (ReflectionUtil.TryIdentifySequenceMethod(mce.Method, out sequenceMethod))
{
switch (sequenceMethod)
{
case SequenceMethod.Where:
return AnalyzePredicate(mce);
case SequenceMethod.Select:
return AnalyzeProjection(mce);
case SequenceMethod.SelectMany:
case SequenceMethod.SelectManyResultSelector:
return AnalyzeSelectMany(mce);
case SequenceMethod.Take:
return AnalyzeResourceSetConstantMethod(mce, (callExp, resource, takeCount) => { AddSequenceQueryOption(resource, new TakeQueryOptionExpression(callExp.Type, takeCount)); return resource; });
case SequenceMethod.Skip:
return AnalyzeResourceSetConstantMethod(mce, (callExp, resource, skipCount) => { AddSequenceQueryOption(resource, new SkipQueryOptionExpression(callExp.Type, skipCount)); return resource; });
case SequenceMethod.OrderBy:
return AnalyzeResourceSetMethod(mce, (callExp, resource, selector) => ApplyOrdering(callExp, resource, selector, /*descending=*/false, /*thenBy=*/false));
case SequenceMethod.ThenBy:
return AnalyzeResourceSetMethod(mce, (callExp, resource, selector) => ApplyOrdering(callExp, resource, selector, /*descending=*/false, /*thenBy=*/true));
case SequenceMethod.OrderByDescending:
return AnalyzeResourceSetMethod(mce, (callExp, resource, selector) => ApplyOrdering(callExp, resource, selector, /*descending=*/true, /*thenBy=*/false));
case SequenceMethod.ThenByDescending:
return AnalyzeResourceSetMethod(mce, (callExp, resource, selector) => ApplyOrdering(callExp, resource, selector, /*descending=*/true, /*thenBy=*/true));
#if !ASTORIA_LIGHT // Silverlight doesn't support synchronous operators.
case SequenceMethod.First:
case SequenceMethod.FirstOrDefault:
return LimitCardinality(mce, 1);
case SequenceMethod.Single:
case SequenceMethod.SingleOrDefault:
return LimitCardinality(mce, 2);
#endif
case SequenceMethod.Cast:
return AnalyzeCast(mce);
default:
throw Error.MethodNotSupported(mce);
}
}
else if (mce.Method.DeclaringType.IsGenericType &&
mce.Method.DeclaringType.GetGenericTypeDefinition() == typeof(DataServiceQuery<>))
{
Type t = typeof(DataServiceQuery<>).MakeGenericType(mce.Method.DeclaringType.GetGenericArguments()[0]);
if (mce.Method == t.GetMethod("Expand", new Type[] { typeof(string) }))
{
return AnalyzeExpand(mce);
}
else if (mce.Method == t.GetMethod("AddQueryOption", new Type[] { typeof(string), typeof(object) }))
{
return AnalyzeAddCustomQueryOption(mce);
}
else
{
throw Error.MethodNotSupported(mce);
}
}
return mce;
}
return e;
}
private static class PatternRules
{
internal static bool MatchPropertyAccess(Expression e, out Expression instance, out List propertyPath)
{
instance = null;
propertyPath = null;
MemberExpression me = e as MemberExpression;
while (me != null)
{
PropertyInfo pi;
if (MatchNonPrivateReadableProperty(me, out pi))
{
if (propertyPath == null)
{
propertyPath = new List();
}
propertyPath.Insert(0, pi.Name);
e = me.Expression;
me = e as MemberExpression;
}
else
{
me = null;
}
}
if (propertyPath != null)
{
instance = e;
return true;
}
return false;
}
// is constant
internal static bool MatchConstant(Expression e, out ConstantExpression constExpr)
{
constExpr = e as ConstantExpression;
return (constExpr != null);
}
internal static bool MatchAnd(Expression e)
{
BinaryExpression be = e as BinaryExpression;
return (be != null && (be.NodeType == ExpressionType.And || be.NodeType == ExpressionType.AndAlso));
}
internal static bool MatchNonPrivateReadableProperty(Expression e, out PropertyInfo propInfo)
{
MemberExpression me = e as MemberExpression;
if (me == null)
{
propInfo = null;
return false;
}
return MatchNonPrivateReadableProperty(me, out propInfo);
}
internal static bool MatchNonPrivateReadableProperty(MemberExpression me, out PropertyInfo propInfo)
{
Debug.Assert(me != null);
propInfo = null;
if (me.Member.MemberType == MemberTypes.Property)
{
PropertyInfo pi = (PropertyInfo)me.Member;
if (pi.CanRead && !TypeSystem.IsPrivate(pi))
{
propInfo = pi;
return true;
}
}
return false;
}
///
/// Checks whether the specified is a member access to a key.
///
/// Expression to check.
/// If this is a key access, the property for the key.
/// true if is a member access to a key; false otherwise.
internal static bool MatchKeyProperty(Expression expression, out PropertyInfo property)
{
property = null;
// make sure member is property, it is public and has a Getter.
PropertyInfo pi;
if (!PatternRules.MatchNonPrivateReadableProperty(expression, out pi))
{
return false;
}
if (GetKeyProperties(pi.ReflectedType).Contains(pi, PropertyInfoEqualityComparer.Instance))
{
property = pi;
return true;
}
return false;
}
/// Gets the key properties from the specified .
/// Type to get properties from.
/// A list of properties that are key for the type; possibly an empty list.
internal static List GetKeyProperties(Type type)
{
Debug.Assert(type != null, "type != null");
ClientType clientType = ClientType.Create(type, false /* expectModelType */);
var result = new List();
foreach (var property in clientType.Properties)
{
if (property.KeyProperty)
{
result.Add(property.DeclaringType.GetProperty(property.PropertyName));
}
}
return result;
}
internal static bool MatchKeyComparison(Expression e, out PropertyInfo keyProperty, out ConstantExpression keyValue)
{
if (PatternRules.MatchBinaryEquality(e))
{
BinaryExpression be = (BinaryExpression)e;
if ((PatternRules.MatchKeyProperty(be.Left, out keyProperty) && PatternRules.MatchConstant(be.Right, out keyValue)) ||
(PatternRules.MatchKeyProperty(be.Right, out keyProperty) && PatternRules.MatchConstant(be.Left, out keyValue)))
{
// if property is compared to null, expression is not key predicate comparison
return (keyValue.Value != null);
}
}
keyProperty = null;
keyValue = null;
return false;
}
internal static bool MatchResourceSet(Expression e)
{
return (e is ResourceSetExpression);
}
internal static bool MatchResource(Expression e)
{
return (e is ResourceExpression);
}
internal static bool MatchSingleArgumentLambda(Expression e, out LambdaExpression lambda)
{
lambda = null;
LambdaExpression le = StripQuotes(e) as LambdaExpression;
if (le != null && le.Parameters.Count == 1)
{
lambda = le;
}
return (lambda != null);
}
internal static bool MatchIdentitySelector(Expression e)
{
LambdaExpression le = (LambdaExpression)e;
return (le.Body == le.Parameters[0]);
}
internal static bool MatchTransparentIdentitySelector(Expression input, LambdaExpression selector)
{
if (selector.Parameters.Count != 1)
{
return false;
}
return MatchTransparentScopeAccess(input, selector.Body, selector.Parameters[0]);
}
private static bool MatchTransparentScopeAccess(Expression input, Expression potentialRef, ParameterExpression expectedTarget)
{
ResourceSetExpression rse = input as ResourceSetExpression;
if (rse == null || rse.TransparentScope == null)
{
return false;
}
Expression paramRef;
List refPath;
return (MatchPropertyAccess(potentialRef, out paramRef, out refPath) &&
paramRef == expectedTarget &&
refPath != null && refPath.Count == 1 &&
string.Equals(refPath[0], rse.TransparentScope.Accessor, StringComparison.Ordinal));
}
internal static bool MatchIdentityProjectionResultSelector(Expression e)
{
LambdaExpression le = (LambdaExpression)e;
return (le.Body == le.Parameters[1]);
}
internal static bool MatchTransparentScopeSelector(Expression e, out ResourceSetExpression.TransparentAccessors transparentScope)
{
transparentScope = null;
LambdaExpression le = (LambdaExpression)e;
if (le.Body.NodeType == ExpressionType.New)
{
NewExpression ne = (NewExpression)le.Body;
if (ne.Constructor.GetParameters().Length == 2 &&
ne.Arguments[0] == le.Parameters[0] &&
ne.Arguments[1] == le.Parameters[1])
{
PropertyInfo[] publicProps = le.Body.Type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
if (publicProps.Length == 2)
{
if (le.Body.Type.BaseType.Equals(typeof(object)) &&
publicProps[0].Name.Equals(le.Parameters[0].Name, StringComparison.Ordinal) &&
publicProps[1].Name.Equals(le.Parameters[1].Name, StringComparison.Ordinal))
{
transparentScope =
new ResourceSetExpression.TransparentAccessors(
publicProps[1].Name,
publicProps[0].Name
);
return true;
}
}
}
}
return false;
}
internal static bool MatchPropertyProjectionSet(ResourceExpression input, Expression potentialPropertyRef)
{
return MatchNavigationPropertyProjection(input, potentialPropertyRef, true);
}
internal static bool MatchPropertyProjectionSingleton(ResourceExpression input, Expression potentialPropertyRef)
{
return MatchNavigationPropertyProjection(input, potentialPropertyRef, false);
}
private static bool MatchNavigationPropertyProjection(ResourceExpression input, Expression potentialPropertyRef, bool requireSet)
{
Expression foundInstance;
List propertyNames;
return (PatternRules.MatchNonSingletonProperty(potentialPropertyRef) == requireSet &&
MatchPropertyAccess(potentialPropertyRef, out foundInstance, out propertyNames) &&
foundInstance == input.CreateReference());
}
internal static bool MatchNonSingletonProperty(Expression e)
{
// byte[] and char[] ares ok - other IEnums are not.
return (TypeSystem.FindIEnumerable(e.Type) != null) &&
e.Type != typeof(char[]) &&
e.Type != typeof(byte[]);
}
internal static bool MatchBinaryExpression(Expression e)
{
return (e is BinaryExpression);
}
internal static bool MatchBinaryEquality(Expression e)
{
return (PatternRules.MatchBinaryExpression(e) && ((BinaryExpression)e).NodeType == ExpressionType.Equal);
}
internal static bool MatchStringAddition(Expression e)
{
if (e.NodeType == ExpressionType.Add)
{
BinaryExpression be = e as BinaryExpression;
return be != null &&
be.Left.Type == typeof(string) &&
be.Right.Type == typeof(string);
}
return false;
}
}
private static class ValidationRules
{
internal static void RequireCanNavigate(Expression e)
{
if (PatternRules.MatchResourceSet(e)
&& ((ResourceSetExpression)e).HasSequenceQueryOptions)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionsOnlyAllowedOnLeafNodes);
}
}
internal static void RequireCanExpand(Expression e)
{
if (!PatternRules.MatchResource(e))
{
throw new NotSupportedException(Strings.ALinq_CantExpand);
}
}
internal static void RequireCanAddCustomQueryOption(Expression e)
{
if (!PatternRules.MatchResource(e))
{
throw new NotSupportedException(Strings.ALinq_CantAddQueryOption);
}
}
internal static void RequireNonSingleton(Expression e)
{
ResourceExpression re = e as ResourceExpression;
if (re != null && re.IsSingleton)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionsOnlyAllowedOnSingletons);
}
}
internal static void RequireLegalCustomQueryOption(Expression e, ResourceExpression target)
{
string name = ((string)(e as ConstantExpression).Value).Trim();
if (name[0] == UriHelper.DOLLARSIGN)
{
if (target.CustomQueryOptions.Any(c => (string)c.Key.Value == name))
{
// don't allow dups in Astoria $ namespace.
throw new NotSupportedException(Strings.ALinq_CantAddDuplicateQueryOption(name));
}
ResourceSetExpression rse = target as ResourceSetExpression;
if (rse != null)
{
switch (name.Substring(1))
{
case UriHelper.OPTIONFILTER:
if (rse.Filter != null)
{
throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name));
}
break;
case UriHelper.OPTIONORDERBY:
if (rse.OrderBy != null)
throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name));
break;
case UriHelper.OPTIONEXPAND:
if (rse.ExpandPaths.Count > 0)
throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name));
break;
case UriHelper.OPTIONSKIP:
if (rse.Skip != null)
throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name));
break;
case UriHelper.OPTIONTOP:
if (rse.Take != null)
throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name));
break;
default:
throw new NotSupportedException(Strings.ALinq_CantAddQueryOptionStartingWithDollarSign(name));
}
}
}
}
}
// Devdiv Bug#489444: By default, C#/Vb compilers uses declaring type for property expression when
// we pass property name while creating the property expression. But its totally fine to use
// property info reflected from any subtype while creating property expressions.
// The problem is when one creates the property expression from a property info reflected from one
// of the subtype, then we don't recognize the key properties and instead of generating a key predicate, we generate
// a filter predicate. This limits a bunch of scenarios, since we can't navigate further once
// we generate a filter predicate.
// To fix this issue, we use a PropertyInfoEqualityComparer, which checks for the name and DeclaringType
// of the property and if they are the same, then it considers them equal.
///
/// Equality and comparison implementation for PropertyInfo.
///
private sealed class PropertyInfoEqualityComparer : IEqualityComparer
{
///
/// private constructor for the singleton pattern
///
private PropertyInfoEqualityComparer() { }
///
/// Static property which returns the single instance of the EqualityComparer
///
internal static readonly PropertyInfoEqualityComparer Instance = new PropertyInfoEqualityComparer();
#region IEqualityComparer Members
///
/// Checks whether the given property info's refers to the same property or not.
///
/// first property info
/// second property info
/// true if they refer to the same property, otherwise returns false.
public bool Equals(PropertyInfo left, PropertyInfo right)
{
// Short circuit the comparison if we know the other reference is equivalent
if (object.ReferenceEquals(left, right)) { return true; }
// If either side is null, return false order (both can't be null because of
// the previous check)
if (null == left || null == right) { return false; }
// If the declaring type and the name of the property are the same,
// both the property infos refer to the same property.
return object.ReferenceEquals(left.DeclaringType, right.DeclaringType) && left.Name.Equals(right.Name);
}
///
/// Computes the hashcode for the given property info
///
/// property info whose hash code needs to be computed.
/// the hashcode for the given property info.
public int GetHashCode(PropertyInfo obj)
{
Debug.Assert(obj != null, "obj != null");
return obj.GetHashCode();
}
#endregion
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Expression visitor that binds expression tree to resources
//
//
// @owner [....]
//---------------------------------------------------------------------
namespace System.Data.Services.Client
{
#region Namespaces.
using System;
using System.Collections.Generic;
using System.Data.Services.Common;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
#endregion Namespaces.
///
/// This class provides a Bind method that analyzes an input and returns a bound tree.
///
internal class ResourceBinder : DataServiceExpressionVisitor
{
///
/// Sets of that require a key predicate that hasn't been satisfied yet.
///
private readonly HashSet setsRequiringKeyPredicate = new HashSet(EqualityComparer.Default);
/// Analyzes and binds the specified expression.
/// Expression to bind.
/// The expression with bound nodes.
internal static Expression Bind(Expression e)
{
ResourceBinder rb = new ResourceBinder();
Expression boundExpression = rb.Visit(e);
// Post Process bound tree
// check to see if path segment ends in multi-valued property with no key predicate
ResourceSetExpression rse = boundExpression as ResourceSetExpression;
if (rse != null)
{
rse.IsLeafResource = true;
}
// If there are missing key predicates, only throw if translation was successful and the missing
// key predicate is the only error. Otherwise, allow the expression that could not be translated
// to propagate to the UriWriter which will throw to indicate that it is not supported.
if ((boundExpression is ResourceExpression) &&
rb.setsRequiringKeyPredicate.Count > 0)
{
throw new NotSupportedException(Strings.ALinq_CantNavigateWithoutKeyPredicate);
}
return boundExpression;
}
private Expression AnalyzePredicate(MethodCallExpression mce)
{
// Validate that the input is a resource set and retrieve the Lambda that defines the predicate
ResourceSetExpression input;
LambdaExpression le;
if (!TryGetResourceSetMethodArguments(mce, out input, out le))
{
// might have failed because of singleton, so throw better error if so.
ValidationRules.RequireNonSingleton(mce.Arguments[0]);
return mce;
}
//
// Valid predicate patterns are as follows:
// 1. A URI-compatible filter applied to the input resource set
// 2. A key-predicate filter applied to the input resource set
// - Additionally, key-predicate filters may be applied to any resource path component
// that does not already have a key-predicate filter, regardless of any filter applied
// to the current input resource set, if transparent scopes are present.
// - It is not valid to apply a key-predicate or a filter to a resource set
// for which key-predicate is already present.
// - It is valid to apply a filter to a resource set for which a filter already exists;
// such filters are AND'd together.
//
// [Key-predicate that targets a path component AND]* [Key-predicate over input | URI-compatible filter over input]+
List conjuncts = new List();
AddConjuncts(le.Body, conjuncts);
Dictionary> predicatesByTarget = new Dictionary>();
List referencedInputs = new List();
foreach (Expression e in conjuncts)
{
Expression reboundPredicate = InputBinder.Bind(e, input, le.Parameters[0], referencedInputs);
if (referencedInputs.Count > 1)
{
// UNSUPPORTED: A single clause cannot refer to more than one resource set
return mce;
}
ResourceSetExpression boundTarget = (referencedInputs.Count == 0 ? input : referencedInputs[0] as ResourceSetExpression);
if (boundTarget == null)
{
// UNSUPPORTED: Each clause must refer to a path component that is a resource set, not a singleton navigation property
return mce;
}
List targetPredicates = null;
if (!predicatesByTarget.TryGetValue(boundTarget, out targetPredicates))
{
targetPredicates = new List();
predicatesByTarget[boundTarget] = targetPredicates;
}
targetPredicates.Add(reboundPredicate);
referencedInputs.Clear();
}
conjuncts = null;
List inputPredicates;
if (predicatesByTarget.TryGetValue(input, out inputPredicates))
{
predicatesByTarget.Remove(input);
}
else
{
inputPredicates = null;
}
foreach (KeyValuePair> predicates in predicatesByTarget)
{
ResourceSetExpression target = predicates.Key;
List clauses = predicates.Value;
Dictionary keyValues;
if (!ExtractKeyPredicate(target, clauses, out keyValues) ||
clauses.Count > 0)
{
// UNSUPPORTED: Only key predicates may be applied to earlier path components
return mce;
}
// Earlier path components must be navigation sources, and navigation sources cannot have query options.
Debug.Assert(!target.HasQueryOptions, "Navigation source had query options?");
SetKeyPredicate(target, keyValues);
}
if (inputPredicates != null)
{
Dictionary inputKeyValues;
if (ExtractKeyPredicate(input, inputPredicates, out inputKeyValues))
{
if (input.HasSequenceQueryOptions)
{
// SQLBUDT 616297: A key predicate cannot be applied if query options other than 'Expand' are present,
// so merge the key predicate into the filter query option instead.
inputPredicates.Add(BuildKeyPredicateFilter(input.CreateReference(), inputKeyValues));
}
else
{
SetKeyPredicate(input, inputKeyValues);
}
}
if (inputPredicates.Count > 0)
{
if (input.KeyPredicate != null)
{
// SQLBUDT 616297: a filter query option cannot be specified if a key predicate is present,
// so remove the existing key predicate and add it to inputPredicates as a filter. Note that
// this can only be done on the current resource set, where the filter query option is being
// applied, since earlier sets in the resource path are navigations that require a singleton,
// enforced by the presence of a key predicate.
inputPredicates.Add(BuildKeyPredicateFilter(input.CreateReference(), input.KeyPredicate));
input.KeyPredicate = null;
}
int start;
Expression newFilter;
if (input.Filter != null)
{
start = 0;
newFilter = input.Filter.Predicate;
}
else
{
start = 1;
newFilter = inputPredicates[0];
}
for (int idx = start; idx < inputPredicates.Count; idx++)
{
newFilter = Expression.And(newFilter, inputPredicates[idx]);
}
AddSequenceQueryOption(input, new FilterQueryOptionExpression(mce.Method.ReturnType, newFilter));
}
}
return input; // No need to adjust this.currentResource - filters are merged in all cases
}
private void SetKeyPredicate(ResourceSetExpression rse, Dictionary keyValues)
{
Debug.Assert(rse != null, "rse != null");
Debug.Assert(keyValues != null, "keyValues != null");
if (rse.KeyPredicate == null)
{
rse.KeyPredicate = new Dictionary();
}
foreach(var g in keyValues)
{
if (rse.KeyPredicate.Keys.Contains(g.Key))
{
//UNSUPPORTED: = AND = are multiple key predicates and
//cannot be represented as a resource path.
throw Error.NotSupported(Strings.ALinq_CanOnlyApplyOneKeyPredicate);
}
rse.KeyPredicate.Add(g.Key, g.Value);
}
this.setsRequiringKeyPredicate.Remove(rse);
}
private static bool ExtractKeyPredicate(ResourceSetExpression target, List predicates, out Dictionary keyValues)
{
keyValues = null;
List nonKeyPredicates = null;
foreach (Expression predicate in predicates)
{
PropertyInfo property;
ConstantExpression constantValue;
if (PatternRules.MatchKeyComparison(predicate, out property, out constantValue))
{
if (keyValues == null)
{
keyValues = new Dictionary();
}
else if (keyValues.ContainsKey(property))
{
// UNSUPPORTED: = AND = are multiple key predicates and
// cannot be represented as a resource path.
throw Error.NotSupported(Strings.ALinq_CanOnlyApplyOneKeyPredicate);
}
keyValues.Add(property, constantValue);
}
else
{
if (nonKeyPredicates == null)
{
nonKeyPredicates = new List();
}
nonKeyPredicates.Add(predicate);
}
}
Debug.Assert(keyValues != null || nonKeyPredicates != null, "No key predicates or non-key predicates found?");
if (keyValues != null &&
!PatternRules.GetKeyProperties(target.CreateReference().Type).SequenceEqual(keyValues.Keys, PropertyInfoEqualityComparer.Instance))
{
keyValues = null;
return false;
}
// If keyValues is non-null then at least one expression from predicates was a key comparison
// and should no longer be present in the predicates list since it is now in the key values dictionary.
if (keyValues != null)
{
// Remove all predicates.
predicates.Clear();
// If any non-key predicates were found, add them back
if (nonKeyPredicates != null)
{
predicates.AddRange(nonKeyPredicates);
}
}
return (keyValues != null);
}
private static Expression BuildKeyPredicateFilter(InputReferenceExpression input, Dictionary keyValuesDictionary)
{
Debug.Assert(keyValuesDictionary.Count > 0, "At least one key property is required in a key predicate");
Expression retExpr = null;
foreach (KeyValuePair keyValue in keyValuesDictionary)
{
Expression clause = Expression.Equal(Expression.Property(input, keyValue.Key), keyValue.Value);
if (retExpr == null)
{
retExpr = clause;
}
else
{
retExpr = Expression.And(retExpr, clause);
}
}
return retExpr;
}
private static void AddConjuncts(Expression e, List conjuncts)
{
if (PatternRules.MatchAnd(e))
{
BinaryExpression be = (BinaryExpression)e;
AddConjuncts(be.Left, conjuncts);
AddConjuncts(be.Right, conjuncts);
}
else
{
conjuncts.Add(e);
}
}
internal Expression AnalyzeProjection(MethodCallExpression mce)
{
Expression input = mce.Arguments[0];
LambdaExpression le;
if(!PatternRules.MatchSingleArgumentLambda(mce.Arguments[1], out le))
{
return mce;
}
else if (PatternRules.MatchIdentitySelector(le))
{
return input;
}
else if (PatternRules.MatchTransparentIdentitySelector(input, le))
{
return RemoveTransparentScope(mce.Method.ReturnType, (ResourceSetExpression)input);
}
ResourceExpression navSource;
Expression boundProjection;
if (IsValidNavigationSource(input, out navSource) &&
TryBindToInput(navSource, le, out boundProjection) &&
PatternRules.MatchPropertyProjectionSingleton(navSource, boundProjection))
{
return CreateNavigationPropertySingletonExpression(mce.Method.ReturnType, navSource, boundProjection);
}
return mce;
}
private bool IsValidNavigationSource(Expression input, out ResourceExpression sourceExpression)
{
ValidationRules.RequireCanNavigate(input);
sourceExpression = input as ResourceExpression;
if (sourceExpression != null && !sourceExpression.IsSingleton)
{
ResourceSetExpression rse = (ResourceSetExpression)sourceExpression;
this.setsRequiringKeyPredicate.Add(rse);
}
return (sourceExpression != null);
}
internal Expression AnalyzeSelectMany(MethodCallExpression mce)
{
if (mce.Arguments.Count != 2 && mce.Arguments.Count != 3)
{
return mce;
}
ResourceExpression input;
if (!IsValidNavigationSource(mce.Arguments[0], out input))
{
return mce;
}
LambdaExpression le;
if(!PatternRules.MatchSingleArgumentLambda(mce.Arguments[1], out le))
{
return mce;
}
List referencedInputs = new List();
Expression navPropRef = InputBinder.Bind(le.Body, input, le.Parameters[0], referencedInputs);
Type resourceType = TypeSystem.GetElementType(navPropRef.Type);
// could be a Cast sequence operator here that can be optimized out.
SequenceMethod sm;
MethodCallExpression mce2 = navPropRef as MethodCallExpression;
while (mce2 != null && ReflectionUtil.TryIdentifySequenceMethod(mce2.Method, out sm) && sm == SequenceMethod.Cast)
{
navPropRef = mce2.Arguments[0];
mce2 = navPropRef as MethodCallExpression;
}
ResourceSetExpression rse = null;
if (PatternRules.MatchPropertyProjectionSet(input, navPropRef))
{
rse = CreateResourceSetExpression(mce.Method.ReturnType, input, navPropRef, resourceType);
}
else
{
return mce;
}
if (mce.Arguments.Count == 3)
{
LambdaExpression selector = StripQuotes(mce.Arguments[2]) as LambdaExpression;
// now examine selector
// Check for transparent scope result - projects the input and the selector
ResourceSetExpression.TransparentAccessors transparentScope;
if (PatternRules.MatchTransparentScopeSelector(selector, out transparentScope))
{
rse.TransparentScope = transparentScope;
}
else if (!PatternRules.MatchIdentityProjectionResultSelector(selector))
{
return mce;
}
}
return rse;
}
internal static Expression ApplyOrdering(MethodCallExpression mce, ResourceSetExpression input, Expression selector, bool descending, bool thenBy)
{
List selectors;
if (!thenBy)
{
selectors = new List();
AddSequenceQueryOption(input, new OrderByQueryOptionExpression(mce.Type, selectors));
}
else
{
Debug.Assert(input.OrderBy != null);
selectors = input.OrderBy.Selectors;
}
selectors.Add(new OrderByQueryOptionExpression.Selector(selector, descending));
return input;
}
#if !ASTORIA_LIGHT // Silverlight doesn't support synchronous operators.
/// Ensures that there's a limit on the cardinality of a query.
/// for the method to limit First/Single(OrDefault).
/// Maximum cardinality to allow.
///
/// An expression that limits to no more than elements.
///
/// This method is used by .First(OrDefault) and .Single(OrDefault) to limit cardinality.
private static Expression LimitCardinality(MethodCallExpression mce, int maxCardinality)
{
Debug.Assert(mce != null, "mce != null");
Debug.Assert(maxCardinality > 0, "Cardinality must be at least 1");
if (mce.Arguments.Count != 1)
{
// Don't support methods with predicates.
return mce;
}
ResourceSetExpression rse = mce.Arguments[0] as ResourceSetExpression;
if (rse != null)
{
if (!rse.HasKeyPredicate && // no-op, already returns a singleton
(ResourceExpressionType)rse.NodeType != ResourceExpressionType.ResourceNavigationProperty)
{
if (rse.Take == null || (int)rse.Take.TakeAmount.Value > maxCardinality)
{
AddSequenceQueryOption(rse, new TakeQueryOptionExpression(mce.Type, Expression.Constant(maxCardinality)));
}
}
return mce.Arguments[0];
}
else if (mce.Arguments[0] is NavigationPropertySingletonExpression)
{
// no-op, already returns a singleton
return mce.Arguments[0];
}
return mce;
}
#endif
private static Expression AnalyzeCast(MethodCallExpression mce)
{
ResourceExpression rse = mce.Arguments[0] as ResourceExpression;
if (rse != null)
{
return rse.Cast(mce.Method.ReturnType);
}
return mce;
}
private static Expression StripConvert(Expression e)
{
UnaryExpression ue = e as UnaryExpression;
if (ue != null &&
ue.NodeType == ExpressionType.Convert &&
ue.Type.IsGenericType &&
ue.Type.GetGenericTypeDefinition() == typeof(DataServiceQuery<>))
{
e = ue.Operand;
ResourceExpression re = e as ResourceExpression;
if (re != null)
{
e = re.Cast(ue.Type);
}
}
return e;
}
private static Expression AnalyzeExpand(MethodCallExpression mce)
{
Expression obj = StripConvert(mce.Object);
ResourceExpression re = obj as ResourceExpression;
if (re == null)
{
return mce;
}
ValidationRules.RequireCanExpand(re);
ConstantExpression ce = mce.Arguments[0] as ConstantExpression;
re.ExpandPaths.Add(ce);
return re;
}
private static Expression AnalyzeAddCustomQueryOption(MethodCallExpression mce)
{
Expression obj = StripConvert(mce.Object);
ResourceExpression re = obj as ResourceExpression;
if (re == null)
{
return mce;
}
ValidationRules.RequireCanAddCustomQueryOption(re);
ValidationRules.RequireLegalCustomQueryOption(mce.Arguments[0], re);
ConstantExpression name = mce.Arguments[0] as ConstantExpression;
ConstantExpression value = mce.Arguments[1] as ConstantExpression;
re.CustomQueryOptions.Add(name, value);
return re;
}
private static ResourceSetExpression CreateResourceSetExpression(Type type, ResourceExpression source, Expression memberExpression, Type resourceType)
{
ResourceSetExpression newResource = new ResourceSetExpression(type, source, memberExpression, resourceType, source.ExpandPaths.ToList(), source.CustomQueryOptions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
source.ExpandPaths.Clear();
source.CustomQueryOptions.Clear();
return newResource;
}
private static NavigationPropertySingletonExpression CreateNavigationPropertySingletonExpression(Type type, ResourceExpression source, Expression memberExpression)
{
NavigationPropertySingletonExpression newResource = new NavigationPropertySingletonExpression(type, source, memberExpression, source.ExpandPaths.ToList(), source.CustomQueryOptions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
source.ExpandPaths.Clear();
source.CustomQueryOptions.Clear();
return newResource;
}
///
/// Produces a new that is a clone of in all respects,
/// other than its result type - which will be - and it's transparent scope,
/// which will be null . This is a shallow clone operation - sequence query options, key predicate, etc are
/// not cloned, but are reassigned to the new instance. The resource expression should be
/// discarded after being used with this method.
///
/// The result type - - that the new resource set expression should have.
/// The resource set expression from which the transparent scope is being removed
/// A new resource set expression without an enclosing transparent scope and with the specified result type.
private static ResourceSetExpression RemoveTransparentScope(Type expectedResultType, ResourceSetExpression input)
{
// Create a new resource set expression based on the input
ResourceSetExpression newResource = new ResourceSetExpression(expectedResultType, input.Source, input.MemberExpression, input.ResourceType, input.ExpandPaths, input.CustomQueryOptions);
// Reassign state items that are not constructor arguments - query options + key predicate
newResource.KeyPredicate = input.KeyPredicate;
foreach (QueryOptionExpression queryOption in input.SequenceQueryOptions)
{
newResource.AddSequenceQueryOption(queryOption);
}
// Instruct the new resource set expression to use input's Input Reference instead of creating its own.
// This will also update the Input Reference to specify the new resource set expression as it's target,
// so that any usage of it in query option expressions is consistent with the new resource set expression.
newResource.OverrideInputReference(input);
return newResource;
}
private static Expression StripQuotes(Expression e)
{
return e.NodeType == ExpressionType.Quote ? ((UnaryExpression)e).Operand : e;
}
internal override Expression VisitResourceSetExpression(ResourceSetExpression rse)
{
if ((ResourceExpressionType)rse.NodeType == ResourceExpressionType.RootResourceSet)
{
Debug.Assert(!rse.HasQueryOptions);
// since we could be adding query options to the root, create a new one which can be mutable.
return new ResourceSetExpression(rse.Type, rse.Source, rse.MemberExpression, rse.ResourceType, null, null);
}
return rse;
}
private static bool TryGetResourceSetMethodArguments(MethodCallExpression mce, out ResourceSetExpression input, out LambdaExpression lambda)
{
input = null;
lambda = null;
input = mce.Arguments[0] as ResourceSetExpression;
if (input != null &&
PatternRules.MatchSingleArgumentLambda(mce.Arguments[1], out lambda))
{
return true;
}
return false;
}
private static bool TryBindToInput(ResourceExpression input, LambdaExpression le, out Expression bound)
{
List referencedInputs = new List();
bound = InputBinder.Bind(le.Body, input, le.Parameters[0], referencedInputs);
if (referencedInputs.Count > 1 || (referencedInputs.Count == 1 && referencedInputs[0] != input))
{
bound = null;
}
return (bound != null);
}
private static Expression AnalyzeResourceSetMethod(MethodCallExpression mce, Func sequenceMethodAnalyzer)
{
ResourceSetExpression input;
LambdaExpression le;
if (!TryGetResourceSetMethodArguments(mce, out input, out le))
{
// UNSUPPORTED: Expected LambdaExpression as second argument to sequence method
return mce;
}
Expression lambdaBody;
if (!TryBindToInput(input, le, out lambdaBody))
{
// UNSUPPORTED: Lambda should reference the input, and only the input
return mce;
}
return sequenceMethodAnalyzer(mce, input, lambdaBody);
}
private static Expression AnalyzeResourceSetConstantMethod(MethodCallExpression mce, Func constantMethodAnalyzer)
{
ResourceExpression input = (ResourceExpression)mce.Arguments[0];
ConstantExpression constantArg = mce.Arguments[1] as ConstantExpression;
if (null == constantArg)
{
// UNSUPPORTED: A ConstantExpression is expected
return mce;
}
return constantMethodAnalyzer(mce, input, constantArg);
}
private static void AddSequenceQueryOption(ResourceExpression target, QueryOptionExpression qoe)
{
ValidationRules.RequireNonSingleton(target);
ResourceSetExpression rse = (ResourceSetExpression)target;
// Validation that can add option
switch (qoe.NodeType)
{
case (ExpressionType)ResourceExpressionType.FilterQueryOption:
if (rse.Skip != null)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("filter", "skip"));
}
else if (rse.Take != null)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("filter", "top"));
}
break;
case (ExpressionType)ResourceExpressionType.OrderByQueryOption:
if (rse.Skip != null)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("orderby", "skip"));
}
else if (rse.Take != null)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("orderby", "top"));
}
break;
case (ExpressionType)ResourceExpressionType.SkipQueryOption:
if (rse.Take != null)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("skip", "top"));
}
break;
default:
break;
}
rse.AddSequenceQueryOption(qoe);
}
internal override Expression VisitBinary(BinaryExpression b)
{
Expression e = base.VisitBinary(b);
if (PatternRules.MatchStringAddition(e))
{
BinaryExpression be = e as BinaryExpression;
MethodInfo mi = typeof(string).GetMethod("Concat", new Type[] { typeof(string), typeof(string)});
return Expression.Call(mi, new Expression[] {be.Left, be.Right});
}
return e;
}
internal override Expression VisitMemberAccess(MemberExpression m)
{
Expression e = base.VisitMemberAccess(m);
MemberExpression me = e as MemberExpression;
PropertyInfo pi;
MethodInfo mi;
if (me != null &&
PatternRules.MatchNonPrivateReadableProperty(me, out pi) &&
TypeSystem.TryGetPropertyAsMethod(pi, out mi))
{
return Expression.Call(me.Expression, mi);
}
return e;
}
internal override Expression VisitMethodCall(MethodCallExpression mce)
{
Expression e = base.VisitMethodCall(mce);
mce = e as MethodCallExpression;
if (mce != null)
{
SequenceMethod sequenceMethod;
if (ReflectionUtil.TryIdentifySequenceMethod(mce.Method, out sequenceMethod))
{
switch (sequenceMethod)
{
case SequenceMethod.Where:
return AnalyzePredicate(mce);
case SequenceMethod.Select:
return AnalyzeProjection(mce);
case SequenceMethod.SelectMany:
case SequenceMethod.SelectManyResultSelector:
return AnalyzeSelectMany(mce);
case SequenceMethod.Take:
return AnalyzeResourceSetConstantMethod(mce, (callExp, resource, takeCount) => { AddSequenceQueryOption(resource, new TakeQueryOptionExpression(callExp.Type, takeCount)); return resource; });
case SequenceMethod.Skip:
return AnalyzeResourceSetConstantMethod(mce, (callExp, resource, skipCount) => { AddSequenceQueryOption(resource, new SkipQueryOptionExpression(callExp.Type, skipCount)); return resource; });
case SequenceMethod.OrderBy:
return AnalyzeResourceSetMethod(mce, (callExp, resource, selector) => ApplyOrdering(callExp, resource, selector, /*descending=*/false, /*thenBy=*/false));
case SequenceMethod.ThenBy:
return AnalyzeResourceSetMethod(mce, (callExp, resource, selector) => ApplyOrdering(callExp, resource, selector, /*descending=*/false, /*thenBy=*/true));
case SequenceMethod.OrderByDescending:
return AnalyzeResourceSetMethod(mce, (callExp, resource, selector) => ApplyOrdering(callExp, resource, selector, /*descending=*/true, /*thenBy=*/false));
case SequenceMethod.ThenByDescending:
return AnalyzeResourceSetMethod(mce, (callExp, resource, selector) => ApplyOrdering(callExp, resource, selector, /*descending=*/true, /*thenBy=*/true));
#if !ASTORIA_LIGHT // Silverlight doesn't support synchronous operators.
case SequenceMethod.First:
case SequenceMethod.FirstOrDefault:
return LimitCardinality(mce, 1);
case SequenceMethod.Single:
case SequenceMethod.SingleOrDefault:
return LimitCardinality(mce, 2);
#endif
case SequenceMethod.Cast:
return AnalyzeCast(mce);
default:
throw Error.MethodNotSupported(mce);
}
}
else if (mce.Method.DeclaringType.IsGenericType &&
mce.Method.DeclaringType.GetGenericTypeDefinition() == typeof(DataServiceQuery<>))
{
Type t = typeof(DataServiceQuery<>).MakeGenericType(mce.Method.DeclaringType.GetGenericArguments()[0]);
if (mce.Method == t.GetMethod("Expand", new Type[] { typeof(string) }))
{
return AnalyzeExpand(mce);
}
else if (mce.Method == t.GetMethod("AddQueryOption", new Type[] { typeof(string), typeof(object) }))
{
return AnalyzeAddCustomQueryOption(mce);
}
else
{
throw Error.MethodNotSupported(mce);
}
}
return mce;
}
return e;
}
private static class PatternRules
{
internal static bool MatchPropertyAccess(Expression e, out Expression instance, out List propertyPath)
{
instance = null;
propertyPath = null;
MemberExpression me = e as MemberExpression;
while (me != null)
{
PropertyInfo pi;
if (MatchNonPrivateReadableProperty(me, out pi))
{
if (propertyPath == null)
{
propertyPath = new List();
}
propertyPath.Insert(0, pi.Name);
e = me.Expression;
me = e as MemberExpression;
}
else
{
me = null;
}
}
if (propertyPath != null)
{
instance = e;
return true;
}
return false;
}
// is constant
internal static bool MatchConstant(Expression e, out ConstantExpression constExpr)
{
constExpr = e as ConstantExpression;
return (constExpr != null);
}
internal static bool MatchAnd(Expression e)
{
BinaryExpression be = e as BinaryExpression;
return (be != null && (be.NodeType == ExpressionType.And || be.NodeType == ExpressionType.AndAlso));
}
internal static bool MatchNonPrivateReadableProperty(Expression e, out PropertyInfo propInfo)
{
MemberExpression me = e as MemberExpression;
if (me == null)
{
propInfo = null;
return false;
}
return MatchNonPrivateReadableProperty(me, out propInfo);
}
internal static bool MatchNonPrivateReadableProperty(MemberExpression me, out PropertyInfo propInfo)
{
Debug.Assert(me != null);
propInfo = null;
if (me.Member.MemberType == MemberTypes.Property)
{
PropertyInfo pi = (PropertyInfo)me.Member;
if (pi.CanRead && !TypeSystem.IsPrivate(pi))
{
propInfo = pi;
return true;
}
}
return false;
}
///
/// Checks whether the specified is a member access to a key.
///
/// Expression to check.
/// If this is a key access, the property for the key.
/// true if is a member access to a key; false otherwise.
internal static bool MatchKeyProperty(Expression expression, out PropertyInfo property)
{
property = null;
// make sure member is property, it is public and has a Getter.
PropertyInfo pi;
if (!PatternRules.MatchNonPrivateReadableProperty(expression, out pi))
{
return false;
}
if (GetKeyProperties(pi.ReflectedType).Contains(pi, PropertyInfoEqualityComparer.Instance))
{
property = pi;
return true;
}
return false;
}
/// Gets the key properties from the specified .
/// Type to get properties from.
/// A list of properties that are key for the type; possibly an empty list.
internal static List GetKeyProperties(Type type)
{
Debug.Assert(type != null, "type != null");
ClientType clientType = ClientType.Create(type, false /* expectModelType */);
var result = new List();
foreach (var property in clientType.Properties)
{
if (property.KeyProperty)
{
result.Add(property.DeclaringType.GetProperty(property.PropertyName));
}
}
return result;
}
internal static bool MatchKeyComparison(Expression e, out PropertyInfo keyProperty, out ConstantExpression keyValue)
{
if (PatternRules.MatchBinaryEquality(e))
{
BinaryExpression be = (BinaryExpression)e;
if ((PatternRules.MatchKeyProperty(be.Left, out keyProperty) && PatternRules.MatchConstant(be.Right, out keyValue)) ||
(PatternRules.MatchKeyProperty(be.Right, out keyProperty) && PatternRules.MatchConstant(be.Left, out keyValue)))
{
// if property is compared to null, expression is not key predicate comparison
return (keyValue.Value != null);
}
}
keyProperty = null;
keyValue = null;
return false;
}
internal static bool MatchResourceSet(Expression e)
{
return (e is ResourceSetExpression);
}
internal static bool MatchResource(Expression e)
{
return (e is ResourceExpression);
}
internal static bool MatchSingleArgumentLambda(Expression e, out LambdaExpression lambda)
{
lambda = null;
LambdaExpression le = StripQuotes(e) as LambdaExpression;
if (le != null && le.Parameters.Count == 1)
{
lambda = le;
}
return (lambda != null);
}
internal static bool MatchIdentitySelector(Expression e)
{
LambdaExpression le = (LambdaExpression)e;
return (le.Body == le.Parameters[0]);
}
internal static bool MatchTransparentIdentitySelector(Expression input, LambdaExpression selector)
{
if (selector.Parameters.Count != 1)
{
return false;
}
return MatchTransparentScopeAccess(input, selector.Body, selector.Parameters[0]);
}
private static bool MatchTransparentScopeAccess(Expression input, Expression potentialRef, ParameterExpression expectedTarget)
{
ResourceSetExpression rse = input as ResourceSetExpression;
if (rse == null || rse.TransparentScope == null)
{
return false;
}
Expression paramRef;
List refPath;
return (MatchPropertyAccess(potentialRef, out paramRef, out refPath) &&
paramRef == expectedTarget &&
refPath != null && refPath.Count == 1 &&
string.Equals(refPath[0], rse.TransparentScope.Accessor, StringComparison.Ordinal));
}
internal static bool MatchIdentityProjectionResultSelector(Expression e)
{
LambdaExpression le = (LambdaExpression)e;
return (le.Body == le.Parameters[1]);
}
internal static bool MatchTransparentScopeSelector(Expression e, out ResourceSetExpression.TransparentAccessors transparentScope)
{
transparentScope = null;
LambdaExpression le = (LambdaExpression)e;
if (le.Body.NodeType == ExpressionType.New)
{
NewExpression ne = (NewExpression)le.Body;
if (ne.Constructor.GetParameters().Length == 2 &&
ne.Arguments[0] == le.Parameters[0] &&
ne.Arguments[1] == le.Parameters[1])
{
PropertyInfo[] publicProps = le.Body.Type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
if (publicProps.Length == 2)
{
if (le.Body.Type.BaseType.Equals(typeof(object)) &&
publicProps[0].Name.Equals(le.Parameters[0].Name, StringComparison.Ordinal) &&
publicProps[1].Name.Equals(le.Parameters[1].Name, StringComparison.Ordinal))
{
transparentScope =
new ResourceSetExpression.TransparentAccessors(
publicProps[1].Name,
publicProps[0].Name
);
return true;
}
}
}
}
return false;
}
internal static bool MatchPropertyProjectionSet(ResourceExpression input, Expression potentialPropertyRef)
{
return MatchNavigationPropertyProjection(input, potentialPropertyRef, true);
}
internal static bool MatchPropertyProjectionSingleton(ResourceExpression input, Expression potentialPropertyRef)
{
return MatchNavigationPropertyProjection(input, potentialPropertyRef, false);
}
private static bool MatchNavigationPropertyProjection(ResourceExpression input, Expression potentialPropertyRef, bool requireSet)
{
Expression foundInstance;
List propertyNames;
return (PatternRules.MatchNonSingletonProperty(potentialPropertyRef) == requireSet &&
MatchPropertyAccess(potentialPropertyRef, out foundInstance, out propertyNames) &&
foundInstance == input.CreateReference());
}
internal static bool MatchNonSingletonProperty(Expression e)
{
// byte[] and char[] ares ok - other IEnums are not.
return (TypeSystem.FindIEnumerable(e.Type) != null) &&
e.Type != typeof(char[]) &&
e.Type != typeof(byte[]);
}
internal static bool MatchBinaryExpression(Expression e)
{
return (e is BinaryExpression);
}
internal static bool MatchBinaryEquality(Expression e)
{
return (PatternRules.MatchBinaryExpression(e) && ((BinaryExpression)e).NodeType == ExpressionType.Equal);
}
internal static bool MatchStringAddition(Expression e)
{
if (e.NodeType == ExpressionType.Add)
{
BinaryExpression be = e as BinaryExpression;
return be != null &&
be.Left.Type == typeof(string) &&
be.Right.Type == typeof(string);
}
return false;
}
}
private static class ValidationRules
{
internal static void RequireCanNavigate(Expression e)
{
if (PatternRules.MatchResourceSet(e)
&& ((ResourceSetExpression)e).HasSequenceQueryOptions)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionsOnlyAllowedOnLeafNodes);
}
}
internal static void RequireCanExpand(Expression e)
{
if (!PatternRules.MatchResource(e))
{
throw new NotSupportedException(Strings.ALinq_CantExpand);
}
}
internal static void RequireCanAddCustomQueryOption(Expression e)
{
if (!PatternRules.MatchResource(e))
{
throw new NotSupportedException(Strings.ALinq_CantAddQueryOption);
}
}
internal static void RequireNonSingleton(Expression e)
{
ResourceExpression re = e as ResourceExpression;
if (re != null && re.IsSingleton)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionsOnlyAllowedOnSingletons);
}
}
internal static void RequireLegalCustomQueryOption(Expression e, ResourceExpression target)
{
string name = ((string)(e as ConstantExpression).Value).Trim();
if (name[0] == UriHelper.DOLLARSIGN)
{
if (target.CustomQueryOptions.Any(c => (string)c.Key.Value == name))
{
// don't allow dups in Astoria $ namespace.
throw new NotSupportedException(Strings.ALinq_CantAddDuplicateQueryOption(name));
}
ResourceSetExpression rse = target as ResourceSetExpression;
if (rse != null)
{
switch (name.Substring(1))
{
case UriHelper.OPTIONFILTER:
if (rse.Filter != null)
{
throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name));
}
break;
case UriHelper.OPTIONORDERBY:
if (rse.OrderBy != null)
throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name));
break;
case UriHelper.OPTIONEXPAND:
if (rse.ExpandPaths.Count > 0)
throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name));
break;
case UriHelper.OPTIONSKIP:
if (rse.Skip != null)
throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name));
break;
case UriHelper.OPTIONTOP:
if (rse.Take != null)
throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name));
break;
default:
throw new NotSupportedException(Strings.ALinq_CantAddQueryOptionStartingWithDollarSign(name));
}
}
}
}
}
// Devdiv Bug#489444: By default, C#/Vb compilers uses declaring type for property expression when
// we pass property name while creating the property expression. But its totally fine to use
// property info reflected from any subtype while creating property expressions.
// The problem is when one creates the property expression from a property info reflected from one
// of the subtype, then we don't recognize the key properties and instead of generating a key predicate, we generate
// a filter predicate. This limits a bunch of scenarios, since we can't navigate further once
// we generate a filter predicate.
// To fix this issue, we use a PropertyInfoEqualityComparer, which checks for the name and DeclaringType
// of the property and if they are the same, then it considers them equal.
///
/// Equality and comparison implementation for PropertyInfo.
///
private sealed class PropertyInfoEqualityComparer : IEqualityComparer
{
///
/// private constructor for the singleton pattern
///
private PropertyInfoEqualityComparer() { }
///
/// Static property which returns the single instance of the EqualityComparer
///
internal static readonly PropertyInfoEqualityComparer Instance = new PropertyInfoEqualityComparer();
#region IEqualityComparer Members
///
/// Checks whether the given property info's refers to the same property or not.
///
/// first property info
/// second property info
/// true if they refer to the same property, otherwise returns false.
public bool Equals(PropertyInfo left, PropertyInfo right)
{
// Short circuit the comparison if we know the other reference is equivalent
if (object.ReferenceEquals(left, right)) { return true; }
// If either side is null, return false order (both can't be null because of
// the previous check)
if (null == left || null == right) { return false; }
// If the declaring type and the name of the property are the same,
// both the property infos refer to the same property.
return object.ReferenceEquals(left.DeclaringType, right.DeclaringType) && left.Name.Equals(right.Name);
}
///
/// Computes the hashcode for the given property info
///
/// property info whose hash code needs to be computed.
/// the hashcode for the given property info.
public int GetHashCode(PropertyInfo obj)
{
Debug.Assert(obj != null, "obj != null");
return obj.GetHashCode();
}
#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
- CompositeFontInfo.cs
- SessionEndingCancelEventArgs.cs
- CompoundFileStreamReference.cs
- MonitorWrapper.cs
- SerializationInfoEnumerator.cs
- AudioBase.cs
- ReceiveMessageContent.cs
- ClientSettings.cs
- BulletChrome.cs
- DataGridViewColumnConverter.cs
- MultipartContentParser.cs
- ExplicitDiscriminatorMap.cs
- ProtocolsConfigurationEntry.cs
- SendActivityDesignerTheme.cs
- SelectionRange.cs
- ContextTokenTypeConverter.cs
- Int16Converter.cs
- UrlAuthFailureHandler.cs
- SqlDataSourceEnumerator.cs
- RawStylusSystemGestureInputReport.cs
- XmlSignificantWhitespace.cs
- ColorAnimation.cs
- LingerOption.cs
- DataGridViewLinkColumn.cs
- WpfXamlLoader.cs
- WindowsFormsHelpers.cs
- RootNamespaceAttribute.cs
- ControlAdapter.cs
- XPathNavigatorReader.cs
- MarkedHighlightComponent.cs
- VariableQuery.cs
- webproxy.cs
- InputScopeManager.cs
- ContentFilePart.cs
- DatatypeImplementation.cs
- Ray3DHitTestResult.cs
- WmpBitmapEncoder.cs
- SqlXml.cs
- WmlCalendarAdapter.cs
- Metafile.cs
- DynamicControl.cs
- SetterBase.cs
- Function.cs
- CodeVariableDeclarationStatement.cs
- XmlNotation.cs
- DriveInfo.cs
- PropertyTabAttribute.cs
- SchemaComplexType.cs
- XmlSchemaImporter.cs
- CapabilitiesPattern.cs
- RightNameExpirationInfoPair.cs
- RemotingException.cs
- ProtectedUri.cs
- ListParaClient.cs
- ExpressionNode.cs
- LineMetrics.cs
- MergeFilterQuery.cs
- SiteMapProvider.cs
- LineServicesRun.cs
- Baml2006ReaderContext.cs
- IteratorFilter.cs
- InvariantComparer.cs
- SQLSingleStorage.cs
- MessageVersionConverter.cs
- HttpCookie.cs
- ServiceConfigurationTraceRecord.cs
- SqlFileStream.cs
- MachineKeySection.cs
- DeadCharTextComposition.cs
- CodeGotoStatement.cs
- _UriSyntax.cs
- Base64WriteStateInfo.cs
- xmlformatgeneratorstatics.cs
- EntityDataSourceEntityTypeFilterItem.cs
- BookmarkResumptionRecord.cs
- ListViewInsertionMark.cs
- OperationContext.cs
- UnsupportedPolicyOptionsException.cs
- InvariantComparer.cs
- EdmProperty.cs
- PresentationAppDomainManager.cs
- SqlDataSourceStatusEventArgs.cs
- ConfigurationStrings.cs
- Utils.cs
- XhtmlBasicControlAdapter.cs
- DbModificationClause.cs
- Padding.cs
- SecurityToken.cs
- LassoHelper.cs
- ResizeGrip.cs
- Track.cs
- XmlConverter.cs
- SHA256.cs
- ConstraintStruct.cs
- WebDisplayNameAttribute.cs
- RoutingEndpointTrait.cs
- BitmapEffectDrawingContent.cs
- TextElementAutomationPeer.cs
- Assign.cs
- DataGridDesigner.cs