/// <summary> /// Add a projection to the query. /// </summary> /// <param name="expression">Select method call expression.</param> private void AddProjection(MethodCallExpression expression) { // We only support Select(x => ...) projections. Anything else // will throw a NotSupportException. if (expression != null && expression.Arguments.Count == 2) { LambdaExpression projection = StripQuote(expression.Arguments[1]) as LambdaExpression; if (projection != null && projection.Parameters.Count == 1) { // Store the type of the input to the projection as we'll // need that for deserialization of values (since the // projection will change the expected type of the data // source) this.query.ProjectionArgumentType = projection.Parameters[0].Type; // Compile the projection into a function that we can apply // to the deserialized value to transform it accordingly. this.query.Projection = projection.Compile(); // Filter the selection down to just the values used by // the projection VisitorHelper.VisitMembers( projection.Body, (expr, recur) => { // Ensure we only process members of the parameter if (expr != null && expr.Expression.NodeType == ExpressionType.Parameter) { SerializableMember member = SerializableType.GetMember(expr.Member); if (member != null) { query.Selection.Add(member.Name); } } return(recur(expr)); }); // Make sure we also include all the members that would be // required for deserialization foreach (SerializableMember member in SerializableType.Get(this.query.ProjectionArgumentType) .Members .Select(p => p.Value) .Where(m => m.IsRequired)) { if (!this.query.Selection.Contains(member.Name)) { this.query.Selection.Add(member.Name); } } return; } } ThrowForUnsupportedException(expression); }
/// <summary> /// Get the member ordering for a given member name. This is used to /// deserialize members in the desired order. /// </summary> /// <param name="memberName">Name of the member.</param> /// <returns>The ordering for the member.</returns> public int GetMemberOrder(string memberName) { Debug.Assert(!string.IsNullOrEmpty(memberName), "memberName cannot be null or empty!"); // Get the order from a member if we can find one, otherwise we'll // default to MaxValue so that any "extra" properties are // serialized after the known properties. SerializableMember member = null; return(this.Members.TryGetValue(memberName, out member) && member.Order >= 0 ? member.Order : int.MaxValue); }
/// <summary> /// Adds the SerializableMember to the Members dictionary or throws /// if there is already a member with the same name. /// </summary> /// <param name="member">The member to add to the Members dictionary.</param> private void AddMemberWithDuplicateCheck(SerializableMember member) { if (this.Members.ContainsKey(member.Name)) { throw new InvalidOperationException( string.Format( CultureInfo.InvariantCulture, Resources.SerializableType_DuplicateKey, this.Type.Name, member.Name)); } this.Members.Add(member.Name, member); }
/// <summary> /// Add an ordering constraint for an OrderBy/ThenBy call. /// </summary> /// <param name="expression">The ordering method call.</param> /// <param name="ascending"> /// Whether the order is ascending or descending. /// </param> private void AddOrdering(MethodCallExpression expression, bool ascending) { // Keep updating with the deepest nested expression structure we // can get to so that we can provide a more detailed error message Expression deepest = expression; // We only allow OrderBy(x => x.Member) expressions. Anything else // will result in a NotSupportedException. if (expression != null && expression.Arguments.Count >= 2) { LambdaExpression lambda = StripQuote(expression.Arguments[1]) as LambdaExpression; if (lambda != null) { deepest = lambda.Body ?? lambda; // Find the name of the member being ordered MemberExpression memberAccess = lambda.Body as MemberExpression; if (memberAccess != null) { if (memberAccess.Expression.NodeType == ExpressionType.Parameter) { SerializableMember member = SerializableType.GetMember(memberAccess.Member); if (member != null && member.Name != null) { // Add the ordering this.query.Ordering.Add(new KeyValuePair <string, bool>(member.Name, ascending)); return; } } } } } throw new NotSupportedException( string.Format( CultureInfo.InvariantCulture, Resources.MobileServiceTableQueryTranslator_GetOrdering_Unsupported, expression != null && expression.Method != null ? expression.Method.Name : null, deepest != null ? deepest.ToString() : null)); }
/// <summary> /// Process member references. /// </summary> /// <param name="expression">The expression to visit.</param> /// <returns>The visited expression.</returns> protected override Expression VisitMember(MemberExpression expression) { // Lookup the Mobile Services name of the member and use that SerializableMember member = GetTableMember(expression); if (member != null) { this.filter.Append(member.Name); this.MarkVisited(); return(expression); } // Check if this member is actually a function that looks like a // property (like string.Length, etc.) string methodName = null; if (instanceProperties.TryGetValue(expression.Member, out methodName)) { this.filter.Append(methodName); this.filter.Append("("); this.Visit(expression.Expression); this.filter.Append(")"); this.MarkVisited(); return(expression); } // Otherwise we can't process the member. throw new NotSupportedException( string.Format( CultureInfo.InvariantCulture, Resources.FilterBuildingExpressionVisitor_VisitMember_Unsupported, expression != null && expression.Member != null ? expression.Member.Name : null, expression != null ? expression.ToString() : null)); }
private SerializableType(Type type) { this.Type = type; this.Members = new Dictionary <string, SerializableMember>(); // The table name mapped by this type is the same as the type // by default unless a DataTable attribute is supplied or a DataContract // is being used in which the name is being set. DataTableAttribute dataTableAttribute = type.GetCustomAttribute <DataTableAttribute>(true); DataContractAttribute dataContractAttribute = type.GetCustomAttribute <DataContractAttribute>(true); if (dataTableAttribute != null && !string.IsNullOrWhiteSpace(dataTableAttribute.Name)) { this.TableName = dataTableAttribute.Name; } else if (dataContractAttribute != null && !string.IsNullOrWhiteSpace(dataContractAttribute.Name)) { this.TableName = dataContractAttribute.Name; } else { this.TableName = type.Name; } // Add all of the Mobile Services properties and fields to Members // (TODO: We're using Members.Add(n, m) instead of Members[n] = m // so that any duplicates will throw an exception. We can consider // whether we should throw a friendlier exception at some point in // the future, but this will prevent duplicate names) bool hasContract = dataContractAttribute != null; foreach (PropertyInfo property in GetSerializableMembers(hasContract, type.GetProperties)) { SerializableMember member = new SerializableMember(property); this.AddMemberWithDuplicateCheck(member); } foreach (FieldInfo field in GetSerializableMembers(hasContract, type.GetFields)) { SerializableMember member = new SerializableMember(field); this.AddMemberWithDuplicateCheck(member); } // Ensure we have a valid ID field (and check a couple of variants // to enable POCOs). SerializableMember id = null; if (!this.Members.TryGetValue(MobileServiceTableUrlBuilder.IdPropertyName, out id) && !this.Members.TryGetValue(IdPropertyName, out id) && !this.Members.TryGetValue(IdPropertyName.ToUpperInvariant(), out id) && !this.Members.TryGetValue(IdPropertyName.ToLowerInvariant(), out id)) { throw new InvalidOperationException( string.Format( CultureInfo.InvariantCulture, Resources.SerializableType_Ctor_MemberNotFound, MobileServiceTableUrlBuilder.IdPropertyName, type.FullName)); } // Coerce the name of the ID property to the required format if // we've got a POCO with a slightly different name. if (id.Name != MobileServiceTableUrlBuilder.IdPropertyName) { this.Members.Remove(id.Name); id.Name = MobileServiceTableUrlBuilder.IdPropertyName; this.AddMemberWithDuplicateCheck(id); } this.IdMember = id; }
/// <summary> /// Deserialize a JSON value into an instance. /// </summary> /// <param name="value">The JSON value.</param> /// <param name="instance">The instance to deserialize into.</param> /// <param name="ignoreCustomSerialization"> /// A value to indicate whether or not custom serialization should be /// ignored if the instance implements /// ICustomMobileServiceTableSerialization. This flag is used by /// implementations of ICustomMobileServiceTableSerialization that want /// to invoke the default serialization behavior. /// </param> public static void Deserialize(JToken value, object instance, bool ignoreCustomSerialization) { if (value == null) { throw new ArgumentNullException("value"); } else if (instance == null) { throw new ArgumentNullException("instance"); } // If the instance implements // ICustomMobileServiceTableSerialization, allow it to handle its // own deserialization. if (!ignoreCustomSerialization) { ICustomMobileServiceTableSerialization custom = instance as ICustomMobileServiceTableSerialization; if (custom != null) { custom.Deserialize(value); return; } } // Get the Mobile Services specific type info SerializableType type = SerializableType.Get(instance.GetType()); // Get the object to deserialize JObject obj = value.AsObject(); if (obj == null) { throw new ArgumentException( string.Format( CultureInfo.InvariantCulture, Resources.MobileServiceTableSerializer_Deserialize_NeedObject, value.Type), "value"); } // Create a set of required members that we can remove from as we // process their values from the object. If there's anything left // in the set after processing all of the values, then we weren't // given all of our required properties. HashSet <SerializableMember> requiredMembers = new HashSet <SerializableMember>(type.Members.Values.Where(m => m.IsRequired)); // Walk through all of the members defined in the object and // deserialize them into the instance one at a time foreach (KeyValuePair <string, JToken> assignment in obj.GetPropertyValues().OrderBy(a => type.GetMemberOrder(a.Key))) { // Look up the instance member corresponding to the JSON member SerializableMember member = null; if (type.Members.TryGetValue(assignment.Key, out member)) { // Remove the member from the required list (does nothing // if it wasn't present) requiredMembers.Remove(member); // Convert the property value into a CLR value using either // the converter or a standard simple JSON mapping. This // will throw for JSON arrays or objects. Also note that // we'll still run the value returned from the converter // through the ChangeType call below to make writing // converters a little easier (but it should be a no-op // for most folks anyway). object propertyValue = null; if (member.Converter != null) { propertyValue = member.Converter.ConvertFromJson(assignment.Value); } else if (!assignment.Value.TryConvert(out propertyValue)) { throw new ArgumentException( string.Format( CultureInfo.InvariantCulture, Resources.MobileServiceTableSerializer_Deserialize_CannotDeserializeValue, assignment.Value.ToString(), type.Type.FullName, member.MemberName), "value"); } // Change the type of the value to the desired property // type (mostly to handle things like casting issues) and // set the value on the instance. object convertedValue = TypeExtensions.ChangeType(propertyValue, member.Type); member.SetValue(instance, convertedValue); } } // Ensure we were provided all of the required properties. if (requiredMembers.Count > 0) { throw new SerializationException( string.Format( CultureInfo.InvariantCulture, Resources.MobileServiceTableSerializer_Deserialize_MissingRequired, type.Type.FullName, string.Join(", ", requiredMembers.Select(m => m.Name)))); } }