private void UpdateVisibleEntitiesGraphForEntity(QueryStructuralValue entityValue, string path, string[] spanPaths) { if (!this.VisibleEntitiesGraph.ContainsKey(entityValue)) { if (entityValue.IsNull) { this.VisibleEntitiesGraph[entityValue] = entityValue; return; } else { var visibleEntity = entityValue.Type.CreateNewInstance(); this.PopulateNonNavigationPropertiesOfVisibleEntity(entityValue, visibleEntity); this.VisibleEntitiesGraph[entityValue] = visibleEntity; } } if (!entityValue.IsNull) { foreach (QueryProperty property in entityValue.Type.Properties) { string newPath = this.CreateNewPath(path, property.Name); if (this.IsValidSpanPath(newPath, spanPaths)) { var originalValue = entityValue.GetValue(property.Name); this.UpdateVisibleEntitiesGraphForQueryValue(originalValue, newPath, spanPaths); } } } }
/// <summary> /// Visits a QueryStructuralValue and returns the clr value of the structural value /// </summary> /// <param name="value">The QueryStructuralValue which contains the clr value of the structural type : complex/entity type</param> /// <returns>The clr instance of the structural value</returns> public object Visit(QueryStructuralValue value) { object clrInstance = null; if (this.objectLookup.TryGetValue(value, out clrInstance)) { return(clrInstance); } ExceptionUtilities.CheckObjectNotNull(value.Type as IQueryClrType, "Structural type does not implement IQueryClrType"); IQueryClrType clrTypeQueryable = value.Type as IQueryClrType; Type clrType = clrTypeQueryable.ClrType; ExceptionUtilities.CheckObjectNotNull(clrType, "ClrType should not be null"); clrInstance = clrType.GetConstructor(Type.EmptyTypes).Invoke(null); this.objectLookup.Add(value, clrInstance); foreach (var member in value.MemberNames) { QueryValue queryValue = value.GetValue(member); var memberValue = queryValue.Accept(this); PropertyInfo memberProperty = clrType.GetProperty(member); memberProperty.SetValue(clrInstance, memberValue, null); } return(clrInstance); }
/// <summary> /// Visits the QueryStructuralValue /// </summary> /// <param name="value">Value to visit</param> /// <returns>a string that represents a query structural value</returns> public string Visit(QueryStructuralValue value) { var indent = this.GenerateIndent(); var queryEntityType = value.Type as QueryEntityType; if (queryEntityType != null) { var idValue = this.LinkGenerator.GenerateEntityId(value).Replace(this.AstoriaServiceDescriptor.ServiceUri.AbsoluteUri, string.Empty); this.builder.AppendLine(string.Format(CultureInfo.InvariantCulture, "{0}Id:{1}", indent, idValue)); // do not expand below configured level of expands likely writing out querydataset if (this.currentDepth > this.maxDepth) { return(null); } foreach (var member in queryEntityType.Properties.Where(p => p.IsNavigationProperty())) { var memberValue = value.GetValue(member.Name); this.currentDepth++; var childIndent = this.GenerateIndent(); this.builder.AppendLine(string.Format(CultureInfo.InvariantCulture, "{0}{1}", childIndent, member.Name)); this.currentDepth++; memberValue.Accept(this); this.currentDepth -= 2; } } return(null); }
/// <summary> /// Fixes up the properties of the structural value based on the given selected paths /// </summary> /// <param name="instance">The value to fix up</param> /// <param name="selectedPaths">The selected paths for the value's scope</param> /// <returns>The fixed-up value</returns> private QueryStructuralValue FixupPropertiesForSelect(QueryStructuralValue instance, IEnumerable <string> selectedPaths) { ExceptionUtilities.CheckArgumentNotNull(instance, "instance"); ExceptionUtilities.CheckCollectionNotEmpty(selectedPaths, "selectedPaths"); var entityType = instance.Type as QueryEntityType; ExceptionUtilities.CheckObjectNotNull(entityType, "Select is not supported on non-entity types"); var masked = MaskedQueryStructuralValue.Create(instance); bool wildCard = selectedPaths.Contains(Endpoints.SelectAll); var propertyNames = entityType.EntityType.AllProperties.Select(p => p.Name).Concat(entityType.Properties.Streams().Select(m => m.Name)); foreach (string propertyName in propertyNames) { if (!selectedPaths.Contains(Uri.EscapeDataString(propertyName)) && !wildCard) { // Primitive, bag and stream properties are either entirely present or entirely missing. // However, complex properties can be partially present if a mapped sub-property has a // null value. For this reason, we recursively hide the property and all sub-properties. HidePropertyRecursive(masked, propertyName); } } foreach (string propertyName in entityType.EntityType.AllNavigationProperties.Select(p => p.Name)) { string pathMarker = Uri.EscapeDataString(propertyName) + "/"; var subpaths = selectedPaths.Where(p => p.StartsWith(pathMarker, StringComparison.Ordinal)).Select(p => p.Substring(pathMarker.Length)); var value = instance.GetValue(propertyName); ExceptionUtilities.CheckObjectNotNull(value, "Value for property '{0}' was null", propertyName); if (selectedPaths.Contains(Uri.EscapeDataString(propertyName))) { continue; } else if (subpaths.Any()) { var maskedValue = this.VisitEntityValues(value, s => this.FixupPropertiesForSelect(s, subpaths)); masked.SetValue(propertyName, maskedValue); } else if (wildCard) { masked.SetValue(propertyName, value.Type.NullValue); } else { masked.HideMember(propertyName); } } return(masked); }
/// <summary> /// Fixes up the properties of the structural value based on the given expanded paths /// </summary> /// <param name="instance">The value to fix up</param> /// <param name="expandedPaths">The expanded paths for the value's scope</param> /// <returns>The fixed-up value</returns> private QueryStructuralValue FixupPropertiesForExpand(QueryStructuralValue instance, IEnumerable <string> expandedPaths) { ExceptionUtilities.CheckArgumentNotNull(instance, "instance"); ExceptionUtilities.CheckArgumentNotNull(expandedPaths, "expandedPaths"); var entityType = instance.Type as QueryEntityType; ExceptionUtilities.CheckObjectNotNull(entityType, "Expand is not supported on non-entity types"); var masked = MaskedQueryStructuralValue.Create(instance); foreach (var navigation in entityType.EntityType.AllNavigationProperties) { string expandedPath; string propertyName = navigation.Name; var value = instance.GetValue(propertyName); ExceptionUtilities.CheckObjectNotNull(value, "Value for property '{0}' was null", propertyName); bool match = TryGetExpandedPath(expandedPaths, propertyName, entityType.EntityType, out expandedPath); string pathMarker = expandedPath + "/"; var subpaths = expandedPaths.Where(p => p.StartsWith(pathMarker, StringComparison.Ordinal)).Select(p => p.Substring(pathMarker.Length)); // note that if there is match, we still need to fix up up any children, even if there are no subpaths if (subpaths.Any() || match) { // recurse var maskedValue = this.VisitEntityValues(value, s => this.FixupPropertiesForExpand(s, subpaths)); // handle page limit on expanded collections if (maskedValue.Type is QueryCollectionType) { var collection = maskedValue as QueryCollectionValue; ExceptionUtilities.CheckObjectNotNull(collection, "Value was a collection type, but not a collection value"); var relatedSet = entityType.EntitySet.GetRelatedEntitySet(navigation); var relatedSetPageSize = relatedSet.GetEffectivePageSize(); if (relatedSetPageSize.HasValue && this.applyPagingInExpands) { collection = this.AddOrderingForPagingToQueryCollectionValue(collection); maskedValue = collection.Take(relatedSetPageSize.Value); } } masked.SetValue(propertyName, maskedValue); } else if (!match) { // set any non-expanded navigation properties to null masked.SetValue(propertyName, value.Type.NullValue); } } return(masked); }
private ComparisonResult CompareGroupingItem <TKey, TElement>(QueryStructuralValue expected, object actual, string path, bool shouldThrow) { var groupingInterface = (IGrouping <TKey, TElement>)actual; QueryValue expectedKey = expected.GetValue("Key"); var keysMatch = this.Compare(expectedKey, groupingInterface.Key, path, false); if (keysMatch == ComparisonResult.Success) { var expectedElements = (QueryCollectionValue)expected.GetValue("Elements"); return(this.Compare(expectedElements, groupingInterface.Select(e => e), path, shouldThrow)); } else { this.ThrowOrLogError(shouldThrow, "Grouping keys don't match in '{0}'. Expected: {1}. Actual: {2}.", path, expectedKey, groupingInterface.Key); return(ComparisonResult.Failure); } }
private static void MarkPropertyAsDynamic(QueryStructuralValue value, string memberName) { var propertyValue = value.GetValue(memberName); if (propertyValue.Type is QueryComplexType) { MarkDynamicSubPropertyValues((QueryStructuralValue)propertyValue); } propertyValue.AsDynamicPropertyValue(); }
private void PopulateNonNavigationPropertiesOfVisibleEntity(QueryStructuralValue originalEntity, QueryStructuralValue visibleEntity) { foreach (var property in originalEntity.Type.Properties) { bool isCollectionOfNonEntities = property.PropertyType is QueryCollectionType && !(((QueryCollectionType)property.PropertyType).ElementType is QueryEntityType); if (property.PropertyType is QueryScalarType || property.PropertyType is QueryComplexType || isCollectionOfNonEntities) { visibleEntity.SetValue(property.Name, originalEntity.GetValue(property.Name)); } } }
/// <summary> /// Static method for creating masked structural values. Will not hide any properties initially. /// </summary> /// <param name="toMask">The structural value to mask</param> /// <returns>A masked structural value</returns> internal static MaskedQueryStructuralValue Create(QueryStructuralValue toMask) { ExceptionUtilities.CheckArgumentNotNull(toMask, "toMask"); var masked = new MaskedQueryStructuralValue(toMask.Type, toMask.IsNull, toMask.EvaluationError, toMask.Type.EvaluationStrategy); foreach (var memberName in toMask.MemberNames) { masked.SetValue(memberName, toMask.GetValue(memberName)); } masked.Annotations.AddRange(toMask.Annotations.Select(a => a.Clone())); return masked; }
/// <summary> /// Static method for creating masked structural values. Will not hide any properties initially. /// </summary> /// <param name="toMask">The structural value to mask</param> /// <returns>A masked structural value</returns> internal static MaskedQueryStructuralValue Create(QueryStructuralValue toMask) { ExceptionUtilities.CheckArgumentNotNull(toMask, "toMask"); var masked = new MaskedQueryStructuralValue(toMask.Type, toMask.IsNull, toMask.EvaluationError, toMask.Type.EvaluationStrategy); foreach (var memberName in toMask.MemberNames) { masked.SetValue(memberName, toMask.GetValue(memberName)); } masked.Annotations.AddRange(toMask.Annotations.Select(a => a.Clone())); return(masked); }
private void UpdateVisibleEntitiesGraphForAnonymousAndGrouping(QueryStructuralValue anonymousOrGroupingValue, string[] spanPaths) { // we never take Includes into account for anonymous and grouping properties, hence we can terminate/corrupt the current span path string newPath = "[anonymous or grouping]"; if (!anonymousOrGroupingValue.IsNull) { foreach (QueryProperty property in anonymousOrGroupingValue.Type.Properties) { var originalValue = anonymousOrGroupingValue.GetValue(property.Name); this.UpdateVisibleEntitiesGraphForQueryValue(originalValue, newPath, spanPaths); } } }
/// <summary> /// Creates a copy of the given value recursively /// </summary> /// <param name="value">The value to copy</param> /// <returns>The copied value</returns> public QueryValue Visit(QueryStructuralValue value) { return(this.HandleCommonCasesAndCache( value, () => value.Type.CreateNewInstance(), copy => { foreach (var memberName in value.MemberNames) { var memberValue = value.GetValue(memberName); var memberValueCopy = memberValue.Accept(this); ((QueryStructuralValue)copy).SetValue(memberName, memberValueCopy); } })); }
/// <summary> /// Compares properties of a structural object with expected value. Overridden here to handle named streams. /// </summary> /// <param name="structuralValue">The structural object containing the property to compare</param> /// <param name="expectedName">The name of the property to compare</param> /// <param name="expectedPropertyType">The expected type of the property</param> /// <param name="actualValue">The actual value of the property</param> /// <param name="path">The path to the compared object (for debugging purposes)</param> /// <param name="shouldThrow">Should exception be thrown if error is encountered</param> /// <returns>The comparison result</returns> protected override ComparisonResult CompareProperty(QueryStructuralValue structuralValue, string expectedName, QueryType expectedPropertyType, object actualValue, string path, bool shouldThrow) { if (this.CodeGenerator.GetType() == typeof(RemoteClientCodeLayerGenerator)) { if (structuralValue.GetValue(expectedName).IsNull&& expectedPropertyType is QueryComplexType) { return(ComparisonResult.Success); } } if (expectedPropertyType is AstoriaQueryStreamType) { #if WINDOWS_PHONE return(ComparisonResult.Success); #else DataServiceStreamLink actualStreamLink = (DataServiceStreamLink)actualValue; AstoriaQueryStreamValue expectedStreamValue = (AstoriaQueryStreamValue)structuralValue.GetStreamValue(expectedName); if (actualStreamLink == null) { if (!expectedStreamValue.IsNull) { this.ThrowOrLogError(shouldThrow, "Expected DataServiceStreamLink property to be null. Actual: {0}", actualStreamLink); return(ComparisonResult.Failure); } else { return(ComparisonResult.Success); } } try { this.VerifyStreamLink(expectedStreamValue, actualStreamLink); return(ComparisonResult.Success); } catch (TestFailedException e) { this.ThrowOrLogError(shouldThrow, e.ToString()); return(ComparisonResult.Failure); } #endif } else { return(base.CompareProperty(structuralValue, expectedName, expectedPropertyType, actualValue, path, shouldThrow)); } }
/// <summary> /// Gets the expected query value for the action request /// </summary> /// <param name="initialExpectedResults">Initial expected values for an action</param> /// <param name="parameterValues">Parameter values for the action</param> /// <returns>A query Value that is the expected value</returns> public QueryValue GetExpectedQueryValue(QueryValue initialExpectedResults, params QueryValue[] parameterValues) { ExceptionUtilities.CheckArgumentNotNull(initialExpectedResults, "initialExpectedResults"); if (initialExpectedResults.IsNull || this.ReturnProperty == null) { return(initialExpectedResults.Type.NullValue); } QueryStructuralValue initialStructuralValue = initialExpectedResults as QueryStructuralValue; QueryCollectionValue initialCollectionValue = initialExpectedResults as QueryCollectionValue; if (initialStructuralValue != null) { return(initialStructuralValue.GetValue(this.ReturnProperty)); } else { ExceptionUtilities.CheckArgumentNotNull(initialCollectionValue, "Unsupported initialExpectedResults type."); if (initialCollectionValue.Elements.Count > 0) { // Sort the results by the keys var queryEntityType = (QueryEntityType)initialCollectionValue.Type.ElementType; var sortedList = initialCollectionValue.Elements.Select(r => (QueryStructuralValue)r).ToList(); foreach (var key in queryEntityType.EntityType.AllKeyProperties) { sortedList = sortedList.OrderBy(r => r.GetScalarValue(key.Name).Value).ToList(); } var firstItemValue = (QueryStructuralValue)sortedList.First(); ExceptionUtilities.CheckArgumentNotNull(firstItemValue, "firstItemValue"); return(firstItemValue.GetValue(this.ReturnProperty)); } else { return(initialCollectionValue.Type.ElementType.NullValue); } } }
/// <summary> /// Visits the given QueryValue /// </summary> /// <param name="value">QueryValue being visited.</param> /// <returns>The result of visiting this QueryValue.</returns> public QueryValue Visit(QueryStructuralValue value) { if (value.Type is QueryComplexType) { return(value); } if (value.Type is QueryEntityType) { return(this.visibleEntitiesGraph[value]); } var result = value.Type.CreateNewInstance(); foreach (var property in value.Type.Properties) { var propertyValue = value.GetValue(property.Name); var resultPropertyValue = this.ProcessResult(propertyValue); result.SetValue(property.Name, resultPropertyValue); } return(result); }
/// <summary> /// Fixes up the properties of the structural value based on the given selected paths /// </summary> /// <param name="instance">The value to fix up</param> /// <param name="selectedPaths">The selected paths for the value's scope</param> /// <returns>The fixed-up value</returns> private QueryStructuralValue FixupPropertiesForSelect(QueryStructuralValue instance, IEnumerable<string> selectedPaths) { ExceptionUtilities.CheckArgumentNotNull(instance, "instance"); ExceptionUtilities.CheckCollectionNotEmpty(selectedPaths, "selectedPaths"); var entityType = instance.Type as QueryEntityType; ExceptionUtilities.CheckObjectNotNull(entityType, "Select is not supported on non-entity types"); var masked = MaskedQueryStructuralValue.Create(instance); bool wildCard = selectedPaths.Contains(Endpoints.SelectAll); var propertyNames = entityType.EntityType.AllProperties.Select(p => p.Name).Concat(entityType.Properties.Streams().Select(m => m.Name)); foreach (string propertyName in propertyNames) { if (!selectedPaths.Contains(Uri.EscapeDataString(propertyName)) && !wildCard) { // Primitive, bag and stream properties are either entirely present or entirely missing. // However, complex properties can be partially present if a mapped sub-property has a // null value. For this reason, we recursively hide the property and all sub-properties. HidePropertyRecursive(masked, propertyName); } } foreach (string propertyName in entityType.EntityType.AllNavigationProperties.Select(p => p.Name)) { string pathMarker = Uri.EscapeDataString(propertyName) + "/"; var subpaths = selectedPaths.Where(p => p.StartsWith(pathMarker, StringComparison.Ordinal)).Select(p => p.Substring(pathMarker.Length)); var value = instance.GetValue(propertyName); ExceptionUtilities.CheckObjectNotNull(value, "Value for property '{0}' was null", propertyName); if (selectedPaths.Contains(Uri.EscapeDataString(propertyName))) { continue; } else if (subpaths.Any()) { var maskedValue = this.VisitEntityValues(value, s => this.FixupPropertiesForSelect(s, subpaths)); masked.SetValue(propertyName, maskedValue); } else if (wildCard) { masked.SetValue(propertyName, value.Type.NullValue); } else { masked.HideMember(propertyName); } } return masked; }
private void CompareValues(QueryStructuralValue instance, IEnumerable<NamedValue> namedValues, string propertyPath) { foreach (QueryProperty property in instance.Type.Properties) { string childPropertyPath = property.Name; if (propertyPath != null) { childPropertyPath = propertyPath + "." + property.Name; } // Skip if its an EntityType, this only handles structural types var queryEntityType = property.PropertyType as QueryEntityType; if (queryEntityType != null) { continue; } var collectionType = property.PropertyType as QueryCollectionType; var scalarType = property.PropertyType as QueryScalarType; var complexDataType = property.PropertyType as QueryComplexType; QueryEntityType collectionQueryElementType = null; if (collectionType != null) { collectionQueryElementType = collectionType.ElementType as QueryEntityType; } // Skip if its a collection of QueryEntityType if (collectionQueryElementType != null) { continue; } if (scalarType != null) { // The following code block handles the case where the value is server generated. // For instance, server generated Keys would fail so if a property is marked as being server generated // we make sure to remove that property from the list of properties to verify MemberProperty memberProperty; var instanceEntityType = instance.Type as QueryEntityType; if (instanceEntityType != null) { memberProperty = instanceEntityType.EntityType.AllProperties.SingleOrDefault(p => p.Name == property.Name); } else { memberProperty = ((QueryComplexType)instance.Type).ComplexType.Properties.SingleOrDefault(p => p.Name == property.Name); } if (memberProperty != null && memberProperty.Annotations.OfType<StoreGeneratedPatternAnnotation>().Any()) { // TODO: be more fine-grained about whether this is an update or insert (ie, look at the annotation's property values) this.unusedNamedValuePaths.Remove(childPropertyPath); continue; } NamedValue primitivePropertyNamedValue = namedValues.SingleOrDefault(nv => nv.Name == childPropertyPath); if (primitivePropertyNamedValue != null) { var queryValue = instance.GetScalarValue(property.Name); this.WriteErrorIfNotEqual(childPropertyPath, primitivePropertyNamedValue.Value, queryValue); this.unusedNamedValuePaths.Remove(childPropertyPath); } } else if (collectionType != null) { List<NamedValue> bagNamedValues = namedValues.Where(nv => nv.Name.StartsWith(childPropertyPath + ".", StringComparison.Ordinal)).ToList(); if (bagNamedValues.Any()) { this.CompareBagProperty(instance, property, collectionType.ElementType, childPropertyPath, bagNamedValues); } else { this.CompareBagPropertyWithNullOrEmpty(instance, property, childPropertyPath, namedValues); } } else if (complexDataType != null) { // NOTE: we cannot assert that it is complex/primitive/bag, because there may be new query types added in other assemblies that we know nothing about here QueryStructuralValue complexTypeValue = instance.GetStructuralValue(property.Name); List<NamedValue> complexInstanceNamedValues = namedValues.Where(nv => nv.Name.StartsWith(childPropertyPath + ".", StringComparison.Ordinal)).ToList(); if (complexInstanceNamedValues.Any()) { if (!this.WriteErrorIfNull(childPropertyPath, complexTypeValue)) { this.CompareValues(complexTypeValue, complexInstanceNamedValues, childPropertyPath); } } else { // Check for null case List<NamedValue> exactMatches = namedValues.Where(nv => nv.Name == childPropertyPath).ToList(); ExceptionUtilities.Assert(exactMatches.Count < 2, "Should only find at most one property path {0} when looking for null value", childPropertyPath); if (exactMatches.Count == 1) { ExceptionUtilities.Assert( exactMatches[0].Value == null, "Named value at path '{0}' was unexpectedly non-null. Value was '{1}'", childPropertyPath, exactMatches[0].Value); QueryValue queryValue = instance.GetValue(property.Name); this.WriteErrorIfNotNull(childPropertyPath, queryValue); this.unusedNamedValuePaths.Remove(childPropertyPath); } } } } }
/// <summary> /// Returns the stream property values for the given entity instance /// </summary> /// <param name="instance">The instance to get stream property values from</param> /// <param name="namedStream">The named stream.</param> /// <returns>The stream values of the given instance</returns> public static AstoriaQueryStreamValue GetStreamValue(this QueryStructuralValue instance, string namedStream) { ExceptionUtilities.CheckArgumentNotNull(instance, "instance"); ExceptionUtilities.CheckArgumentNotNull(namedStream, "namedStream"); return((AstoriaQueryStreamValue)instance.GetValue(namedStream)); }
private void CompareValues(QueryStructuralValue instance, IEnumerable <NamedValue> namedValues, string propertyPath) { foreach (QueryProperty property in instance.Type.Properties) { string childPropertyPath = property.Name; if (propertyPath != null) { childPropertyPath = propertyPath + "." + property.Name; } // Skip if its an EntityType, this only handles structural types var queryEntityType = property.PropertyType as QueryEntityType; if (queryEntityType != null) { continue; } var collectionType = property.PropertyType as QueryCollectionType; var scalarType = property.PropertyType as QueryScalarType; var complexDataType = property.PropertyType as QueryComplexType; QueryEntityType collectionQueryElementType = null; if (collectionType != null) { collectionQueryElementType = collectionType.ElementType as QueryEntityType; } // Skip if its a collection of QueryEntityType if (collectionQueryElementType != null) { continue; } if (scalarType != null) { // The following code block handles the case where the value is server generated. // For instance, server generated Keys would fail so if a property is marked as being server generated // we make sure to remove that property from the list of properties to verify MemberProperty memberProperty; var instanceEntityType = instance.Type as QueryEntityType; if (instanceEntityType != null) { memberProperty = instanceEntityType.EntityType.AllProperties.SingleOrDefault(p => p.Name == property.Name); } else { memberProperty = ((QueryComplexType)instance.Type).ComplexType.Properties.SingleOrDefault(p => p.Name == property.Name); } if (memberProperty != null && memberProperty.Annotations.OfType <StoreGeneratedPatternAnnotation>().Any()) { // TODO: be more fine-grained about whether this is an update or insert (ie, look at the annotation's property values) this.unusedNamedValuePaths.Remove(childPropertyPath); continue; } NamedValue primitivePropertyNamedValue = namedValues.SingleOrDefault(nv => nv.Name == childPropertyPath); if (primitivePropertyNamedValue != null) { var queryValue = instance.GetScalarValue(property.Name); this.WriteErrorIfNotEqual(childPropertyPath, primitivePropertyNamedValue.Value, queryValue); this.unusedNamedValuePaths.Remove(childPropertyPath); } } else if (collectionType != null) { List <NamedValue> bagNamedValues = namedValues.Where(nv => nv.Name.StartsWith(childPropertyPath + ".", StringComparison.Ordinal)).ToList(); if (bagNamedValues.Any()) { this.CompareBagProperty(instance, property, collectionType.ElementType, childPropertyPath, bagNamedValues); } else { this.CompareBagPropertyWithNullOrEmpty(instance, property, childPropertyPath, namedValues); } } else if (complexDataType != null) { // NOTE: we cannot assert that it is complex/primitive/bag, because there may be new query types added in other assemblies that we know nothing about here QueryStructuralValue complexTypeValue = instance.GetStructuralValue(property.Name); List <NamedValue> complexInstanceNamedValues = namedValues.Where(nv => nv.Name.StartsWith(childPropertyPath + ".", StringComparison.Ordinal)).ToList(); if (complexInstanceNamedValues.Any()) { if (!this.WriteErrorIfNull(childPropertyPath, complexTypeValue)) { this.CompareValues(complexTypeValue, complexInstanceNamedValues, childPropertyPath); } } else { // Check for null case List <NamedValue> exactMatches = namedValues.Where(nv => nv.Name == childPropertyPath).ToList(); ExceptionUtilities.Assert(exactMatches.Count < 2, "Should only find at most one property path {0} when looking for null value", childPropertyPath); if (exactMatches.Count == 1) { ExceptionUtilities.Assert( exactMatches[0].Value == null, "Named value at path '{0}' was unexpectedly non-null. Value was '{1}'", childPropertyPath, exactMatches[0].Value); QueryValue queryValue = instance.GetValue(property.Name); this.WriteErrorIfNotNull(childPropertyPath, queryValue); this.unusedNamedValuePaths.Remove(childPropertyPath); } } } } }
private void VisitStructuralInstance(ComplexInstance payloadElement, QueryStructuralValue value) { ExceptionUtilities.CheckArgumentNotNull(payloadElement, "payloadElement"); ExceptionUtilities.CheckArgumentNotNull(value, "value"); var entityType = value.Type as QueryEntityType; var complexType = value.Type as QueryComplexType; ExceptionUtilities.Assert(entityType != null || complexType != null, "Value was neither an entity type nor a complex type"); bool isEntity = entityType != null; string errorType = isEntity ? "Entity" : "Complex"; if (value.IsNull) { this.parent.Assert.IsTrue(payloadElement.IsNull, errorType + " instance unexpectedly non-null"); return; } else { this.parent.Assert.IsFalse(payloadElement.IsNull, errorType + " instance unexpectedly null"); this.VerifyTypeName(value, payloadElement.FullTypeName, errorType + " instance type name did not match expectation."); } // get all the payload properties, and remove them as we go to detect any extras var payloadProperties = payloadElement.Properties.ToList(); // this is data-driven to deal with open types, but we need to skip over the 'default stream' property if it exists foreach (var propertyName in value.MemberNames.Where(m => m != AstoriaQueryStreamType.DefaultStreamPropertyName)) { var propertyInstance = payloadProperties.SingleOrDefault(p => p.Name == propertyName); this.parent.Assert.IsNotNull(propertyInstance, string.Format(CultureInfo.InvariantCulture, "Could not find property '{0}' in payload", propertyName)); payloadProperties.Remove(propertyInstance); var propertyValue = value.GetValue(propertyName); this.RecurseWithMessage(propertyInstance, propertyValue, "{0} instance did not match expectation", errorType); } string extraPropertyNames = string.Join(", ", payloadProperties.Select(p => '\'' + p.Name + '\'').ToArray()); this.parent.Assert.IsTrue(payloadProperties.Count == 0, string.Format(CultureInfo.InvariantCulture, "{0} instance contained unexpected properties: {1}", errorType, extraPropertyNames)); }
/// <summary> /// Compares a property of a structural object with expected value /// </summary> /// <param name="structuralValue">The structural object containing the property to compare</param> /// <param name="expectedName">The name of the property to compare</param> /// <param name="expectedPropertyType">The expected type of the property</param> /// <param name="actualValue">The actual value of the property</param> /// <param name="path">The path to the compared object (for debugging purposes)</param> /// <param name="shouldThrow">Should exception be thrown if error is encountered</param> /// <returns>Result of the comparison</returns> protected virtual ComparisonResult CompareProperty(QueryStructuralValue structuralValue, string expectedName, QueryType expectedPropertyType, object actualValue, string path, bool shouldThrow) { var expectedPropertyValue = structuralValue.GetValue(expectedName); return(this.Compare(expectedPropertyValue, actualValue, path, shouldThrow)); }
/// <summary> /// Fixes up the properties of the structural value based on the given expanded paths /// </summary> /// <param name="instance">The value to fix up</param> /// <param name="expandedPaths">The expanded paths for the value's scope</param> /// <returns>The fixed-up value</returns> private QueryStructuralValue FixupPropertiesForExpand(QueryStructuralValue instance, IEnumerable<string> expandedPaths) { ExceptionUtilities.CheckArgumentNotNull(instance, "instance"); ExceptionUtilities.CheckArgumentNotNull(expandedPaths, "expandedPaths"); var entityType = instance.Type as QueryEntityType; ExceptionUtilities.CheckObjectNotNull(entityType, "Expand is not supported on non-entity types"); var masked = MaskedQueryStructuralValue.Create(instance); foreach (var navigation in entityType.EntityType.AllNavigationProperties) { string expandedPath; string propertyName = navigation.Name; var value = instance.GetValue(propertyName); ExceptionUtilities.CheckObjectNotNull(value, "Value for property '{0}' was null", propertyName); bool match = TryGetExpandedPath(expandedPaths, propertyName, entityType.EntityType, out expandedPath); string pathMarker = expandedPath + "/"; var subpaths = expandedPaths.Where(p => p.StartsWith(pathMarker, StringComparison.Ordinal)).Select(p => p.Substring(pathMarker.Length)); // note that if there is match, we still need to fix up up any children, even if there are no subpaths if (subpaths.Any() || match) { // recurse var maskedValue = this.VisitEntityValues(value, s => this.FixupPropertiesForExpand(s, subpaths)); // handle page limit on expanded collections if (maskedValue.Type is QueryCollectionType) { var collection = maskedValue as QueryCollectionValue; ExceptionUtilities.CheckObjectNotNull(collection, "Value was a collection type, but not a collection value"); var relatedSet = entityType.EntitySet.GetRelatedEntitySet(navigation); var relatedSetPageSize = relatedSet.GetEffectivePageSize(); if (relatedSetPageSize.HasValue && this.applyPagingInExpands) { collection = this.AddOrderingForPagingToQueryCollectionValue(collection); maskedValue = collection.Take(relatedSetPageSize.Value); } } masked.SetValue(propertyName, maskedValue); } else if (!match) { // set any non-expanded navigation properties to null masked.SetValue(propertyName, value.Type.NullValue); } } return masked; }