/// <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;
        }
Example #7
0
        /// <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))));
            }
        }