private static bool CanBindTo(this IEdmFunctionImport function, IEdmCollectionType collection) { if (function == null) { throw Error.ArgumentNull("function"); } if (collection == null) { throw Error.ArgumentNull("collection"); } if (!function.IsBindable) { return(false); } // The binding parameter is the first parameter by convention IEdmFunctionParameter bindingParameter = function.Parameters.FirstOrDefault(); if (bindingParameter == null) { return(false); } IEdmCollectionType bindingParameterType = bindingParameter.Type.Definition as IEdmCollectionType; if (bindingParameterType == null) { return(false); } IEdmEntityType bindingParameterElementType = bindingParameterType.ElementType.Definition as IEdmEntityType; IEdmEntityType entity = collection.ElementType.Definition as IEdmEntityType; if (bindingParameterElementType == null || entity == null) { return(false); } return(entity.IsOrInheritsFrom(bindingParameterElementType)); }
/// <summary> /// Verifies that CreateEntryReader or CreateFeedReader or CreateDeltaReader can be called. /// </summary> /// <param name="navigationSource">The navigation source we are going to read entities for.</param> /// <param name="entityType">The expected entity type for the entry/entries to be read.</param> private void VerifyCanCreateODataReader(IEdmNavigationSource navigationSource, IEdmEntityType entityType) { Debug.Assert(navigationSource == null || entityType != null, "If an navigation source is specified, the entity type must be specified as well."); // We require metadata information for reading requests. if (!this.ReadingResponse) { this.VerifyUserModel(); if (navigationSource == null) { throw new ODataException(ODataErrorStrings.ODataJsonLightInputContext_NoEntitySetForRequest); } } // We only check that the base type of the entity set is assignable from the specified entity type. // If no entity set/entity type is specified in the API, we will read it from the context URI. IEdmEntityType entitySetElementType = this.EdmTypeResolver.GetElementType(navigationSource); if (navigationSource != null && entityType != null && !entityType.IsOrInheritsFrom(entitySetElementType)) { throw new ODataException(ODataErrorStrings.ODataJsonLightInputContext_EntityTypeMustBeCompatibleWithEntitySetBaseType(entityType.FullName(), entitySetElementType.FullName(), navigationSource.FullNavigationSourceName())); } }
/// <summary> /// Process the operation candidates using the information. /// </summary> /// <param name="context">The controller and action context.</param> /// <param name="entityType">The Edm entity type.</param> /// <param name="navigationSource">The Edm navigation source.</param> protected void ProcessOperations(ODataControllerActionContext context, IEdmEntityType entityType, IEdmNavigationSource navigationSource) { Contract.Assert(context != null); Contract.Assert(entityType != null); Contract.Assert(navigationSource != null); string actionName = context.Action.ActionMethod.Name; bool hasKeyParameter = context.Action.HasODataKeyParameter(entityType); if (context.Singleton != null && hasKeyParameter) { // Singleton doesn't allow to call action with key. return; } // OperationNameOnCollectionOfEntityType string operationName = SplitActionName(actionName, out string cast, out bool isOnCollection); IEdmEntityType castTypeFromActionName = null; if (cast != null) { castTypeFromActionName = entityType.FindTypeInInheritance(context.Model, cast) as IEdmEntityType; if (castTypeFromActionName == null) { return; } } // TODO: refactor here // If we have mulitple same function defined, we should match the best one? IEnumerable <IEdmOperation> candidates = context.Model.SchemaElements.OfType <IEdmOperation>().Where(f => f.IsBound && f.Name == operationName); foreach (IEdmOperation edmOperation in candidates) { IEdmOperationParameter bindingParameter = edmOperation.Parameters.FirstOrDefault(); if (bindingParameter == null) { // bound operation at least has one parameter which type is the binding type. continue; } IEdmTypeReference bindingType = bindingParameter.Type; bool bindToCollection = bindingType.TypeKind() == EdmTypeKind.Collection; if (bindToCollection) { // if binding to collection the action has key parameter or a singleton, skip if (context.Singleton != null || hasKeyParameter) { continue; } } else { // if binding to non-collection and the action hasn't key parameter, skip if (isOnCollection || (context.EntitySet != null && !hasKeyParameter)) { continue; } } // We only allow the binding type is entity type or collection of entity type. if (!bindingType.Definition.IsEntityOrEntityCollectionType(out IEdmEntityType bindingEntityType)) { continue; } IEdmEntityType castType = null; if (castTypeFromActionName == null) { if (entityType.IsOrInheritsFrom(bindingEntityType)) { // True if and only if the thisType is equivalent to or inherits from otherType. castType = null; } else if (bindingEntityType.InheritsFrom(entityType)) { // True if and only if the type inherits from the potential base type. castType = bindingEntityType; } else { continue; } } else { if (isOnCollection && !bindToCollection) { continue; } if (bindingEntityType != castTypeFromActionName) { continue; } if (castTypeFromActionName != entityType) { castType = castTypeFromActionName; } } // TODO: need discussion ahout: // 1) Do we need to match the whole parameter count? // 2) Do we need to select the best match? So far, i don't think and let it go. if (!IsOperationParameterMeet(edmOperation, context.Action)) { continue; } AddSelector(context, edmOperation, hasKeyParameter, entityType, navigationSource, castType); } }
public static bool CanBindTo(this IEdmFunctionImport function, IEdmEntityType entity) { if (function == null) { throw Error.ArgumentNull("function"); } if (entity == null) { throw Error.ArgumentNull("entity"); } if (!function.IsBindable) { return false; } // The binding parameter is the first parameter by convention IEdmFunctionParameter bindingParameter = function.Parameters.FirstOrDefault(); if (bindingParameter == null) { return false; } IEdmEntityType bindingParameterType = bindingParameter.Type.Definition as IEdmEntityType; if (bindingParameterType == null) { return false; } return entity.IsOrInheritsFrom(bindingParameterType); }
/// <summary> /// Parses the next OData path segment following an entity. /// </summary> /// <param name="model">The model to use for path parsing.</param> /// <param name="previous">The previous path segment.</param> /// <param name="previousEdmType">The EDM type of the OData path up to the previous segment.</param> /// <param name="segment">The value of the segment to parse.</param> /// <param name="segments">The queue of pending segments.</param> /// <returns>A parsed representation of the segment.</returns> protected virtual ODataPathSegment ParseAtEntity(IEdmModel model, ODataPathSegment previous, IEdmType previousEdmType, string segment, Queue <string> segments) { if (previous == null) { throw Error.ArgumentNull("previous"); } if (segments == null) { throw Error.ArgumentNull("segments"); } if (String.IsNullOrEmpty(segment)) { throw Error.Argument(SRResources.SegmentNullOrEmpty); } IEdmEntityType previousType = previousEdmType as IEdmEntityType; if (previousType == null) { throw Error.Argument(SRResources.PreviousSegmentMustBeEntityType, previousEdmType); } // first look for navigation properties IEdmNavigationProperty navigation = previousType.NavigationProperties().SingleOrDefault(np => np.Name == segment); if (navigation != null) { return(new NavigationPathSegment(navigation)); } // next look for properties IEdmProperty property = previousType.Properties().SingleOrDefault(p => p.Name == segment); if (property != null) { return(new PropertyAccessPathSegment(property)); } // next look for type casts IEdmEntityType castType = model.FindDeclaredType(segment) as IEdmEntityType; if (castType != null) { if (!castType.IsOrInheritsFrom(previousType) && !previousType.IsOrInheritsFrom(castType)) { throw new ODataException(Error.Format(SRResources.InvalidCastInPath, castType, previousType)); } return(new CastPathSegment(castType)); } // look for $ref if (segment == ODataSegmentKinds.Ref) { return(new RefPathSegment()); } // finally look for bindable procedures IEdmAction action = model.FindAction(segment, previousType); if (action != null) { return(new BoundActionPathSegment(action)); } // Try to match this to a function call BoundFunctionPathSegment pathSegment = TryMatchBoundFunctionCall(segment, segments, model, bindingType: previousType); if (pathSegment != null) { return(pathSegment); } // Treating as an open property return(new UnresolvedPathSegment(segment)); }
/// <summary> /// Parses the next OData path segment following an entity collection. /// </summary> /// <param name="model">The model to use for path parsing.</param> /// <param name="previous">The previous path segment.</param> /// <param name="previousEdmType">The EDM type of the OData path up to the previous segment.</param> /// <param name="segment">The value of the segment to parse.</param> /// <param name="segments">The queue of pending segments.</param> /// <returns>A parsed representation of the segment.</returns> protected virtual ODataPathSegment ParseAtEntityCollection(IEdmModel model, ODataPathSegment previous, IEdmType previousEdmType, string segment, Queue <string> segments) { if (previous == null) { throw Error.ArgumentNull("previous"); } if (segments == null) { throw Error.ArgumentNull("segments"); } if (String.IsNullOrEmpty(segment)) { throw Error.Argument(SRResources.SegmentNullOrEmpty); } if (previousEdmType == null) { throw Error.InvalidOperation(SRResources.PreviousSegmentEdmTypeCannotBeNull); } IEdmCollectionType collectionType = previousEdmType as IEdmCollectionType; if (collectionType == null) { throw Error.Argument(SRResources.PreviousSegmentMustBeEntityCollectionType, previousEdmType); } IEdmEntityType elementType = collectionType.ElementType.Definition as IEdmEntityType; if (elementType == null) { throw Error.Argument(SRResources.PreviousSegmentMustBeEntityCollectionType, previousEdmType); } // look for keys first. if (segment.StartsWith("(", StringComparison.Ordinal) && segment.EndsWith(")", StringComparison.Ordinal)) { Contract.Assert(segment.Length >= 2); string value = segment.Substring(1, segment.Length - 2); return(new KeyValuePathSegment(value)); } // next look for casts IEdmEntityType castType = model.FindDeclaredType(segment) as IEdmEntityType; if (castType != null) { IEdmType previousElementType = collectionType.ElementType.Definition; if (!castType.IsOrInheritsFrom(previousElementType) && !previousElementType.IsOrInheritsFrom(castType)) { throw new ODataException(Error.Format(SRResources.InvalidCastInPath, castType, previousElementType)); } return(new CastPathSegment(castType)); } // look for $ref if (segment == ODataSegmentKinds.Ref) { return(new RefPathSegment()); } // now look for bindable actions IEdmAction action = model.FindAction(segment, collectionType); if (action != null) { return(new BoundActionPathSegment(action)); } // Try to match this to a function call BoundFunctionPathSegment pathSegment = TryMatchBoundFunctionCall(segment, segments, model, bindingType: collectionType); if (pathSegment != null) { return(pathSegment); } throw new ODataException(Error.Format(SRResources.NoActionFoundForCollection, segment, collectionType.ElementType)); }
internal Expression CreatePropertyValueExpressionWithFilter(IEdmEntityType elementType, IEdmProperty property, Expression source, FilterClause filterClause) { Contract.Assert(elementType != null); Contract.Assert(property != null); Contract.Assert(source != null); IEdmEntityType declaringType = property.DeclaringType as IEdmEntityType; Contract.Assert(declaringType != null, "only entity types are projected."); // derived property using cast if (!elementType.IsOrInheritsFrom(declaringType)) { Type castType = EdmLibHelpers.GetClrType(declaringType, _model); if (castType == null) { throw new ODataException(Error.Format(SRResources.MappingDoesNotContainResourceType, declaringType.FullName())); } source = Expression.TypeAs(source, castType); } string propertyName = EdmLibHelpers.GetClrPropertyName(property, _model); Expression propertyValue = Expression.Property(source, propertyName); Type nullablePropertyType = TypeHelper.ToNullable(propertyValue.Type); Expression nullablePropertyValue = ExpressionHelpers.ToNullable(propertyValue); if (filterClause != null) { bool isCollection = property.Type.IsCollection(); IEdmTypeReference edmElementType = (isCollection ? property.Type.AsCollection().ElementType() : property.Type); Type clrElementType = EdmLibHelpers.GetClrType(edmElementType, _model); if (clrElementType == null) { throw new ODataException(Error.Format(SRResources.MappingDoesNotContainResourceType, edmElementType.FullName())); } Expression filterResult = nullablePropertyValue; ODataQuerySettings querySettings = new ODataQuerySettings() { HandleNullPropagation = HandleNullPropagationOption.True, }; if (isCollection) { Expression filterSource = typeof(IEnumerable).IsAssignableFrom(source.Type.GetProperty(propertyName).PropertyType) ? Expression.Call( ExpressionHelperMethods.QueryableAsQueryable.MakeGenericMethod(clrElementType), nullablePropertyValue) : nullablePropertyValue; // TODO: Implement proper support for $select/$expand after $apply Expression filterPredicate = FilterBinder.Bind(null, filterClause, clrElementType, _context, querySettings); filterResult = Expression.Call( ExpressionHelperMethods.QueryableWhereGeneric.MakeGenericMethod(clrElementType), filterSource, filterPredicate); nullablePropertyType = filterResult.Type; } else if (_settings.HandleReferenceNavigationPropertyExpandFilter) { LambdaExpression filterLambdaExpression = FilterBinder.Bind(null, filterClause, clrElementType, _context, querySettings) as LambdaExpression; if (filterLambdaExpression == null) { throw new ODataException(Error.Format(SRResources.ExpandFilterExpressionNotLambdaExpression, property.Name, "LambdaExpression")); } ParameterExpression filterParameter = filterLambdaExpression.Parameters.First(); Expression predicateExpression = new ReferenceNavigationPropertyExpandFilterVisitor(filterParameter, nullablePropertyValue).Visit(filterLambdaExpression.Body); // create expression similar to: 'predicateExpression == true ? nullablePropertyValue : null' filterResult = Expression.Condition( test: predicateExpression, ifTrue: nullablePropertyValue, ifFalse: Expression.Constant(value: null, type: nullablePropertyType)); } if (_settings.HandleNullPropagation == HandleNullPropagationOption.True) { // create expression similar to: 'nullablePropertyValue == null ? null : filterResult' nullablePropertyValue = Expression.Condition( test: Expression.Equal(nullablePropertyValue, Expression.Constant(value: null)), ifTrue: Expression.Constant(value: null, type: nullablePropertyType), ifFalse: filterResult); } else { nullablePropertyValue = filterResult; } } if (_settings.HandleNullPropagation == HandleNullPropagationOption.True) { // create expression similar to: 'source == null ? null : propertyValue' propertyValue = Expression.Condition( test: Expression.Equal(source, Expression.Constant(value: null)), ifTrue: Expression.Constant(value: null, type: nullablePropertyType), ifFalse: nullablePropertyValue); } else { // need to cast this to nullable as EF would fail while materializing if the property is not nullable and source is null. propertyValue = nullablePropertyValue; } return(propertyValue); }
/// <summary> /// Verifies that CreateEntryReader or CreateFeedReader or CreateDeltaReader can be called. /// </summary> /// <param name="navigationSource">The navigation source we are going to read entities for.</param> /// <param name="entityType">The expected entity type for the entry/entries to be read.</param> private void VerifyCanCreateODataReader(IEdmNavigationSource navigationSource, IEdmEntityType entityType) { Debug.Assert(navigationSource == null || entityType != null, "If an navigation source is specified, the entity type must be specified as well."); // We require metadata information for reading requests. if (!this.ReadingResponse) { this.VerifyUserModel(); if (navigationSource == null) { throw new ODataException(ODataErrorStrings.ODataJsonLightInputContext_NoEntitySetForRequest); } } // We only check that the base type of the entity set is assignable from the specified entity type. // If no entity set/entity type is specified in the API, we will read it from the context URI. IEdmEntityType entitySetElementType = this.EdmTypeResolver.GetElementType(navigationSource); if (navigationSource != null && entityType != null && !entityType.IsOrInheritsFrom(entitySetElementType)) { throw new ODataException(ODataErrorStrings.ODataJsonLightInputContext_EntityTypeMustBeCompatibleWithEntitySetBaseType(entityType.FullName(), entitySetElementType.FullName(), navigationSource.FullNavigationSourceName())); } }
/// <summary> /// Process the operation candidates using the information. /// </summary> /// <param name="context">The controller and action context.</param> /// <param name="entityType">The Edm entity type.</param> /// <param name="navigationSource">The Edm navigation source.</param> protected void ProcessOperations(ODataControllerActionContext context, IEdmEntityType entityType, IEdmNavigationSource navigationSource) { Contract.Assert(context != null); Contract.Assert(entityType != null); Contract.Assert(navigationSource != null); string actionName = context.Action.ActionName; bool hasKeyParameter = context.Action.HasODataKeyParameter(entityType, context.Options?.RouteOptions?.EnablePropertyNameCaseInsensitive ?? false); if (context.Singleton != null && hasKeyParameter) { // Singleton doesn't allow to call action with key. return; } bool isOnCollection = false; IEdmEntityType castTypeFromActionName = null; IEdmOperation[] candidates = FindCandidates(context, actionName); if (candidates.Length == 0) { // If we can't find any Edm operation using the action name directly, // Let's split the action name and use part of it to search again. candidates = FindCandidates(context, entityType, actionName, out castTypeFromActionName, out isOnCollection); } foreach (IEdmOperation edmOperation in candidates) { IEdmOperationParameter bindingParameter = edmOperation.Parameters.FirstOrDefault(); if (bindingParameter == null) { // bound operation at least has one parameter which type is the binding type. continue; } IEdmTypeReference bindingType = bindingParameter.Type; bool bindToCollection = bindingType.TypeKind() == EdmTypeKind.Collection; if (bindToCollection) { // if binding to collection the action has key parameter or a singleton, skip if (context.Singleton != null || hasKeyParameter) { continue; } } else { // if binding to non-collection and the action hasn't key parameter, skip if (isOnCollection || (context.EntitySet != null && !hasKeyParameter)) { continue; } } // We only allow the binding type is entity type or collection of entity type. if (!bindingType.Definition.IsEntityOrEntityCollectionType(out IEdmEntityType bindingEntityType)) { continue; } IEdmEntityType castType = null; if (castTypeFromActionName == null) { if (entityType.IsOrInheritsFrom(bindingEntityType)) { // True if and only if the thisType is equivalent to or inherits from otherType. castType = null; } else if (bindingEntityType.InheritsFrom(entityType)) { // True if and only if the type inherits from the potential base type. castType = bindingEntityType; } else { continue; } } else { if (isOnCollection && !bindToCollection) { continue; } if (bindingEntityType != castTypeFromActionName) { continue; } if (castTypeFromActionName != entityType) { castType = castTypeFromActionName; } } // TODO: need discussion about: // 1) Do we need to match the whole parameter count? // 2) Do we need to select the best match? So far, i don't think and let it go. if (!IsOperationParameterMatched(edmOperation, context.Action)) { continue; } AddSelector(context, edmOperation, hasKeyParameter, entityType, navigationSource, castType); } }
/// <summary> /// Parses the next OData path segment following an entity. /// </summary> /// <param name="model">The model to use for path parsing.</param> /// <param name="previous">The previous path segment.</param> /// <param name="previousEdmType">The EDM type of the OData path up to the previous segment.</param> /// <param name="segment">The value of the segment to parse.</param> /// <returns>A parsed representation of the segment.</returns> protected virtual ODataPathSegment ParseAtEntity(IEdmModel model, ODataPathSegment previous, IEdmType previousEdmType, string segment) { if (previous == null) { throw Error.ArgumentNull("previous"); } if (String.IsNullOrEmpty(segment)) { throw Error.Argument(SRResources.SegmentNullOrEmpty); } IEdmEntityType previousType = previousEdmType as IEdmEntityType; if (previousType == null) { throw Error.Argument(SRResources.PreviousSegmentMustBeEntityType, previousEdmType); } if (segment == ODataSegmentKinds.Links) { return(new LinksPathSegment()); } // first look for navigation properties IEdmNavigationProperty navigation = previousType.NavigationProperties().SingleOrDefault(np => np.Name == segment); if (navigation != null) { return(new NavigationPathSegment(navigation)); } // next look for properties IEdmProperty property = previousType.Properties().SingleOrDefault(p => p.Name == segment); if (property != null) { return(new PropertyAccessPathSegment(property)); } // next look for type casts IEdmEntityType castType = model.FindDeclaredType(segment) as IEdmEntityType; if (castType != null) { if (!castType.IsOrInheritsFrom(previousType)) { throw new ODataException(Error.Format(SRResources.InvalidCastInPath, castType, previousType)); } return(new CastPathSegment(castType)); } // finally look for bindable procedures IEdmEntityContainer container = ExtractEntityContainer(model); IEdmFunctionImport procedure = container.FunctionImports().FindBindableAction(previousType, segment); if (procedure != null) { return(new ActionPathSegment(procedure)); } // Treating as an open property return(new UnresolvedPathSegment(segment)); }
internal static bool TypeIndirectlyContainsTarget(IEdmEntityType source, IEdmEntityType target, HashSetInternal<IEdmEntityType> visited, IEdmModel context) { bool flag; if (visited.Add(source)) { if (!source.IsOrInheritsFrom(target)) { foreach (IEdmNavigationProperty edmNavigationProperty in source.NavigationProperties()) { if (!edmNavigationProperty.ContainsTarget || !ValidationHelper.TypeIndirectlyContainsTarget(edmNavigationProperty.ToEntityType(), target, visited, context)) { continue; } flag = true; return flag; } IEnumerator<IEdmStructuredType> enumerator = context.FindAllDerivedTypes(source).GetEnumerator(); using (enumerator) { while (enumerator.MoveNext()) { IEdmStructuredType current = enumerator.Current; IEdmEntityType edmEntityType = current as IEdmEntityType; if (edmEntityType == null || !ValidationHelper.TypeIndirectlyContainsTarget(edmEntityType, target, visited, context)) { continue; } flag = true; return flag; } return false; } return flag; } else { return true; } } return false; }
internal static bool TypeIndirectlyContainsTarget(IEdmEntityType source, IEdmEntityType target, HashSetInternal<IEdmEntityType> visited, IEdmModel context) { if (visited.Add(source)) { if (source.IsOrInheritsFrom(target)) { return true; } foreach (IEdmNavigationProperty navProp in source.NavigationProperties()) { if (navProp.ContainsTarget && TypeIndirectlyContainsTarget(navProp.ToEntityType(), target, visited, context)) { return true; } } foreach (IEdmStructuredType derived in context.FindAllDerivedTypes(source)) { IEdmEntityType derivedEntity = derived as IEdmEntityType; if (derivedEntity != null && TypeIndirectlyContainsTarget(derivedEntity, target, visited, context)) { return true; } } } return false; }