/// <remarks>
 /// This build method caters to expressions like: <code>item => item.Count()</code>
 /// </remarks>
 static string BuildText(
     MethodCallExpression expression,
     CypherCapabilities capabilities,
     IEnumerable <JsonConverter> jsonConvertersThatTheDeserializerWillUse)
 {
     return(BuildStatement(expression, false, capabilities, jsonConvertersThatTheDeserializerWillUse));
 }
 public CypherWhereExpressionVisitor(Func<object, string> createParameterCallback, CypherCapabilities capabilities, bool camelCaseProperties)
 {
     this.createParameterCallback = createParameterCallback;
     this.capabilities = capabilities;
     this.camelCaseProperties = camelCaseProperties;
     TextOutput = new StringBuilder();
 }
        static string BuildStatement(
            Expression sourceExpression,
            MemberInfo targetMember,
            CypherCapabilities capabilities,
            IEnumerable <JsonConverter> jsonConvertersThatTheDeserializerWillUse,
            bool camelCaseProperties)
        {
            var unwrappedExpression = UnwrapImplicitCasts(sourceExpression);

            var memberExpression = unwrappedExpression as MemberExpression;

            if (memberExpression != null)
            {
                return(BuildStatement(memberExpression, targetMember, capabilities, camelCaseProperties));
            }

            var methodCallExpression = unwrappedExpression as MethodCallExpression;

            if (methodCallExpression != null)
            {
                return(BuildStatement(methodCallExpression, targetMember, capabilities, jsonConvertersThatTheDeserializerWillUse));
            }

            var binaryExpression = unwrappedExpression as BinaryExpression;

            if (binaryExpression != null)
            {
                return(BuildStatement(binaryExpression, targetMember));
            }

            throw new NotSupportedException(string.Format(
                                                "Expression of type {0} is not supported.",
                                                unwrappedExpression.GetType().FullName));
        }
        static string BuildStatement(
            MethodCallExpression expression,
            bool isNullable,
            CypherCapabilities capabilities,
            IEnumerable <JsonConverter> jsonConvertersThatTheDeserializerWillUse)
        {
            string statement;

            if (expression.Method.DeclaringType == typeof(ICypherResultItem) || expression.Method.DeclaringType == typeof(IFluentCypherResultItem))
            {
                statement = BuildCypherResultItemStatement(expression, isNullable, capabilities, jsonConvertersThatTheDeserializerWillUse);
            }
            else if (expression.Method.DeclaringType == typeof(All))
            {
                statement = BuildCypherAllStatement(expression);
            }
            else if (expression.Method.DeclaringType == typeof(Return))
            {
                statement = BuildCypherReturnStatement(expression);
            }
            else
            {
                throw new ArgumentException(ReturnExpressionCannotBeSerializedToCypherExceptionMessage);
            }

            return(statement);
        }
        /// <remarks>
        /// This build method caters to object initializers, like:
        ///
        ///     new MyType { Foo = "Bar", Baz = "Qak" }
        ///
        /// It does not however cater to anonymous types, as they don't compile
        /// down to traditional object initializers.
        ///
        /// <see cref="BuildText(NewExpression, CypherCapabilities, IEnumerable&lt;JsonConverter&gt;)"/> caters to anonymous types.
        /// </remarks>
        static string BuildText(
            MemberInitExpression expression,
            CypherCapabilities capabilities,
            IEnumerable <JsonConverter> jsonConvertersThatTheDeserializerWillUse,
            bool camelCaseProperties)
        {
            if (expression.NewExpression.Constructor == null)
            {
                throw new ArgumentException(ReturnExpressionCannotBeStruct);
            }

            if (expression.NewExpression.Constructor.GetParameters().Any())
            {
                throw new ArgumentException(
                          "The result type must be constructed using a parameterless constructor. For example: n => new MyResultType { Foo = n.Bar }",
                          "expression");
            }

            var bindingTexts = expression.Bindings.Select(binding =>
            {
                if (binding.BindingType != MemberBindingType.Assignment)
                {
                    throw new ArgumentException("All bindings must be assignments. For example: n => new MyResultType { Foo = n.Bar }", "expression");
                }

                var memberAssignment = (MemberAssignment)binding;
                return(BuildStatement(memberAssignment.Expression, binding.Member, capabilities, jsonConvertersThatTheDeserializerWillUse, camelCaseProperties));
            });

            return(string.Join(", ", bindingTexts.ToArray()));
        }
 public CypherWhereExpressionVisitor(Func <object, string> createParameterCallback, CypherCapabilities capabilities, bool camelCaseProperties)
 {
     this.createParameterCallback = createParameterCallback;
     this.capabilities            = capabilities;
     this.camelCaseProperties     = camelCaseProperties;
     TextOutput = new StringBuilder();
 }
        // Terminology used in this file:
        //
        // - a "statement" is something like "x.Foo? AS Bar"
        // - "text" is a collection of statements, like "x.Foo? AS Bar, y.Baz as Qak"
        public static ReturnExpression BuildText(
            LambdaExpression expression,
            CypherCapabilities capabilities,
            IEnumerable<JsonConverter> jsonConvertersThatTheDeserializerWillUse,
            bool camelCaseProperties = false)
        {
            capabilities = capabilities ?? CypherCapabilities.Default;

            var body = expression.Body;

            if (body.NodeType == ExpressionType.Convert &&
                body is UnaryExpression)
            {
                body = ((UnaryExpression)expression.Body).Operand;
            }

            ExpressionBuild expressionBuild;
            switch (body.NodeType)
            {
                case ExpressionType.MemberInit:
                    var memberInitExpression = (MemberInitExpression) body;
                    expressionBuild = BuildText(memberInitExpression, capabilities, jsonConvertersThatTheDeserializerWillUse, camelCaseProperties);
                    return new ReturnExpression
                    {
                        Text = expressionBuild.ExpressionText,
                        ResultMode = CypherResultMode.Projection,
                        ResultFormat = expressionBuild.ResultFormat
                    };
                case ExpressionType.New:
                    var newExpression = (NewExpression) body;
                    expressionBuild = BuildText(newExpression, capabilities, jsonConvertersThatTheDeserializerWillUse, camelCaseProperties);
                    return new ReturnExpression
                    {
                        Text = expressionBuild.ExpressionText,
                        ResultMode = CypherResultMode.Projection,
                        ResultFormat = expressionBuild.ResultFormat
                    };
                case ExpressionType.Call:
                    var methodCallExpression = (MethodCallExpression) body;
                    expressionBuild = BuildText(methodCallExpression, capabilities, jsonConvertersThatTheDeserializerWillUse);
                    return new ReturnExpression
                    {
                        Text = expressionBuild.ExpressionText,
                        ResultMode = CypherResultMode.Set,
                        ResultFormat = expressionBuild.ResultFormat
                    };
                case ExpressionType.MemberAccess:
                    var memberExpression = (MemberExpression) body;
                    expressionBuild = BuildText(memberExpression, capabilities, jsonConvertersThatTheDeserializerWillUse, camelCaseProperties);
                    return new ReturnExpression
                    {
                        Text = expressionBuild.ExpressionText,
                        ResultMode = CypherResultMode.Set,
                        ResultFormat = expressionBuild.ResultFormat
                    };
                default:
                    throw new ArgumentException(ReturnExpressionShouldBeOneOfExceptionMessage, "expression");
            }
        }
Exemple #8
0
        /// <remarks>
        /// This C#:
        ///
        ///     new { Foo = "Bar", Baz = "Qak" }
        ///
        /// translates to:
        ///
        ///     new __SomeAnonymousType("Bar", "Qak")
        ///
        /// which is then a NewExpression rather than a MemberInitExpression.
        ///
        /// This is the scenario that this build method caters for.
        /// </remarks>
        static ExpressionBuild BuildText(
            NewExpression expression,
            CypherCapabilities capabilities,
            IEnumerable <JsonConverter> jsonConvertersThatTheDeserializerWillUse,
            bool camelCaseProperties)
        {
            var constructor = expression.Constructor;

            if (constructor == null)
            {
                throw new ArgumentException(ReturnExpressionCannotBeStruct);
            }

            var resultingType             = constructor.DeclaringType;
            var typeInfo                  = resultingType.GetTypeInfo();
            var quacksLikeAnAnonymousType =
                resultingType != null &&
                typeInfo.IsSpecialName &&
                typeInfo.IsValueType &&
                typeInfo.IsNestedPrivate &&
                !typeInfo.IsGenericType;
            var expressionMembers = expression.Members;

            if (expressionMembers == null && !quacksLikeAnAnonymousType)
            {
                // expression.Members is null for Tuples and Record types generated by F#
                // ref: https://fsharppowerpack.codeplex.com/workitem/4572
                var reflectedMembers = resultingType.GetProperties(); //resultingType.GetMembers() gets all members, we only want properties
                if (reflectedMembers == null || !resultingType.FullName.StartsWith("System.Tuple`"))
                {
                    throw new ArgumentException(ReturnExpressionShouldBeOneOfExceptionMessage, "expression");
                }
                expressionMembers = new System.Collections.ObjectModel.ReadOnlyCollection <MemberInfo>(reflectedMembers);
            }

            if (expressionMembers == null)
            {
                throw new ArgumentException(ReturnExpressionShouldBeOneOfExceptionMessage, "expression");
            }

            if (expression.Arguments.Count != expressionMembers.Count)
            {
                throw new InvalidOperationException("Somehow we had a different number of members than arguments. We weren't expecting this to happen. Please raise an issue at http://hg.readify.net/neo4jclient including your query code.");
            }

            var bindingTexts = expressionMembers.Select((member, index) =>
            {
                var argument = expression.Arguments[index];
                return(BuildStatement(argument, member, capabilities, jsonConvertersThatTheDeserializerWillUse, camelCaseProperties));
            }).ToArray();

            var resultFormat = bindingTexts.Any(expressionBuild => expressionBuild.ResultFormat == CypherResultFormat.Rest)
                ? CypherResultFormat.Rest
                : CypherResultFormat.DependsOnEnvironment;

            return(new ExpressionBuild(
                       string.Join(", ", bindingTexts.Select(expressionBuild => expressionBuild.ExpressionText)),
                       resultFormat));
        }
        static string BuildStatement(
            MemberExpression memberExpression,
            MemberInfo targetMember,
            CypherCapabilities capabilities,
            bool camelCaseProperties)
        {
            MethodCallExpression methodCallExpression;
            MemberInfo           memberInfo;

            if (memberExpression.NodeType == ExpressionType.MemberAccess && memberExpression.Expression.NodeType == ExpressionType.Call)
            {
                methodCallExpression = (MethodCallExpression)memberExpression.Expression;
                memberInfo           = memberExpression.Member;
            }
            else if (memberExpression.NodeType == ExpressionType.MemberAccess && memberExpression.Expression.NodeType == ExpressionType.MemberAccess)
            {
                var nextedExpression = ((MemberExpression)memberExpression.Expression);
                methodCallExpression = (MethodCallExpression)nextedExpression.Expression;
                memberInfo           = nextedExpression.Member;
            }
            else
            {
                throw new NotSupportedException(string.Format("The expression {0} is not supported", memberExpression));
            }
            var targetObject = (ParameterExpression)methodCallExpression.Object;

            if (targetObject == null)
            {
                throw new InvalidOperationException(
                          "Somehow targetObject ended up as null. We weren't expecting this to happen. Please raise an issue at https://github.com/Readify/Neo4jClient including your query code.");
            }

            var optionalIndicator = "";

            if (capabilities.SupportsPropertySuffixesForControllingNullComparisons)
            {
                var isTargetMemberNullable = IsMemberNullable(targetMember);
                var isNullable             = isTargetMemberNullable || IsMemberNullable(memberInfo);
                if (isNullable)
                {
                    optionalIndicator = "?";
                }
            }

            JsonPropertyAttribute[] jsonProperties = (JsonPropertyAttribute[])memberInfo.GetCustomAttributes(typeof(JsonPropertyAttribute), true);
            JsonPropertyAttribute   jsonProperty   = jsonProperties.SingleOrDefault();
            string memberName = null;

            if (jsonProperty != null)
            {
                memberName = jsonProperty.PropertyName;
            }
            if (string.IsNullOrWhiteSpace(memberName))
            {
                memberName = CypherFluentQuery.ApplyCamelCase(camelCaseProperties, memberInfo.Name);
            }

            return(string.Format("{0}.{1}{2} AS {3}", targetObject.Name, memberName, optionalIndicator, targetMember.Name));
        }
Exemple #10
0
        static ExpressionBuild BuildStatement(
            MethodCallExpression expression,
            MemberInfo targetMember,
            CypherCapabilities capabilities,
            IEnumerable <JsonConverter> jsonConvertersThatTheDeserializerWillUse)
        {
            var isNullable = IsMemberNullable(targetMember);
            var statement  = BuildStatement(expression, isNullable, capabilities, jsonConvertersThatTheDeserializerWillUse);

            return(new ExpressionBuild(statement.ExpressionText + " AS " + targetMember.Name, statement.ResultFormat));
        }
        static string BuildStatement(
            MethodCallExpression expression,
            MemberInfo targetMember,
            CypherCapabilities capabilities,
            IEnumerable <JsonConverter> jsonConvertersThatTheDeserializerWillUse)
        {
            var isNullable = IsMemberNullable(targetMember);
            var statement  = BuildStatement(expression, isNullable, capabilities, jsonConvertersThatTheDeserializerWillUse);

            statement = statement + " AS " + targetMember.Name;
            return(statement);
        }
        // Terminology used in this file:
        //
        // - a "statement" is something like "x.Foo? AS Bar"
        // - "text" is a collection of statements, like "x.Foo? AS Bar, y.Baz as Qak"

        public static ReturnExpression BuildText(
            LambdaExpression expression,
            CypherCapabilities capabilities,
            IEnumerable <JsonConverter> jsonConvertersThatTheDeserializerWillUse,
            bool camelCaseProperties = false)
        {
            capabilities = capabilities ?? CypherCapabilities.Default;

            var body = expression.Body;

            if (body.NodeType == ExpressionType.Convert &&
                body is UnaryExpression)
            {
                body = ((UnaryExpression)expression.Body).Operand;
            }

            string text;

            switch (body.NodeType)
            {
            case ExpressionType.MemberInit:
                var memberInitExpression = (MemberInitExpression)body;
                text = BuildText(memberInitExpression, capabilities, jsonConvertersThatTheDeserializerWillUse, camelCaseProperties);
                return(new ReturnExpression {
                    Text = text, ResultMode = CypherResultMode.Projection
                });

            case ExpressionType.New:
                var newExpression = (NewExpression)body;
                text = BuildText(newExpression, capabilities, jsonConvertersThatTheDeserializerWillUse, camelCaseProperties);
                return(new ReturnExpression {
                    Text = text, ResultMode = CypherResultMode.Projection
                });

            case ExpressionType.Call:
                var methodCallExpression = (MethodCallExpression)body;
                text = BuildText(methodCallExpression, capabilities, jsonConvertersThatTheDeserializerWillUse);
                return(new ReturnExpression {
                    Text = text, ResultMode = CypherResultMode.Set
                });

            case ExpressionType.MemberAccess:
                var memberExpression = (MemberExpression)body;
                text = BuildText(memberExpression, capabilities, jsonConvertersThatTheDeserializerWillUse, camelCaseProperties);
                return(new ReturnExpression {
                    Text = text, ResultMode = CypherResultMode.Set
                });

            default:
                throw new ArgumentException(ReturnExpressionShouldBeOneOfExceptionMessage, "expression");
            }
        }
        public CypherWhereExpressionVisitor(Func <object, string> createParameterCallback, CypherCapabilities capabilities, bool camelCaseProperties)
        {
            this.createParameterCallback = createParameterCallback;
            this.capabilities            = capabilities;
            this.camelCaseProperties     = camelCaseProperties;
            TextOutput = new StringBuilder();

            supportedMethodCalls = new Dictionary <string, Func <MethodCallExpression, Expression> >()
            {
                { "StartsWith", VisitStartsWithMethod },
                { "Contains", VisitContainsMethod },
                { "EndsWith", VisitEndsWithMethod },
            };
        }
        public static string BuildText(
            LambdaExpression expression,
            Func<object, string> createParameterCallback,
            CypherCapabilities capabilities = null)
        {
            capabilities = capabilities ?? CypherCapabilities.Default;

            if (expression.NodeType == ExpressionType.Lambda &&
                expression.Body.NodeType == ExpressionType.MemberAccess)
                throw new NotSupportedException("Member access expressions, like Where(f => f.Foo), are not supported because these become ambiguous between C# and Cypher based on how Neo4j handles null values. Use a comparison instead, like Where(f => f.Foo == true).");

            var myVisitor = new CypherWhereExpressionVisitor(createParameterCallback, capabilities);
            myVisitor.Visit(expression);
            return myVisitor.TextOutput.ToString();
        }
Exemple #15
0
        static ExpressionBuild BuildStatement(
            MemberExpression memberExpression,
            MemberInfo targetMember,
            CypherCapabilities capabilities,
            bool camelCaseProperties)
        {
            MethodCallExpression methodCallExpression;
            MemberInfo           memberInfo;

            if (memberExpression.NodeType == ExpressionType.MemberAccess && memberExpression.Expression.NodeType == ExpressionType.Call)
            {
                methodCallExpression = (MethodCallExpression)memberExpression.Expression;
                memberInfo           = memberExpression.Member;
            }
            else if (memberExpression.NodeType == ExpressionType.MemberAccess && memberExpression.Expression.NodeType == ExpressionType.MemberAccess)
            {
                var nextedExpression = ((MemberExpression)memberExpression.Expression);
                methodCallExpression = (MethodCallExpression)nextedExpression.Expression;
                memberInfo           = nextedExpression.Member;
            }
            else
            {
                throw new NotSupportedException(string.Format("The expression {0} is not supported", memberExpression));
            }
            var targetObject = (ParameterExpression)methodCallExpression.Object;

            if (targetObject == null)
            {
                throw new InvalidOperationException(
                          "Somehow targetObject ended up as null. We weren't expecting this to happen. Please raise an issue at https://github.com/Readify/Neo4jClient including your query code.");
            }

            var optionalIndicator = "";

            if (capabilities.SupportsPropertySuffixesForControllingNullComparisons)
            {
                var isTargetMemberNullable = IsMemberNullable(targetMember);
                var isNullable             = isTargetMemberNullable || IsMemberNullable(memberInfo);
                if (isNullable)
                {
                    optionalIndicator = "?";
                }
            }

            return(new ExpressionBuild($"{targetObject.Name}.{ParseMemberName(memberInfo, camelCaseProperties)}{optionalIndicator} AS {targetMember.Name}"));
        }
Exemple #16
0
        public static string BuildText(
            LambdaExpression expression,
            Func <object, string> createParameterCallback,
            CypherCapabilities capabilities = null)
        {
            capabilities = capabilities ?? CypherCapabilities.Default;

            if (expression.NodeType == ExpressionType.Lambda &&
                expression.Body.NodeType == ExpressionType.MemberAccess)
            {
                throw new NotSupportedException("Member access expressions, like Where(f => f.Foo), are not supported because these become ambiguous between C# and Cypher based on how Neo4j handles null values. Use a comparison instead, like Where(f => f.Foo == true).");
            }

            var myVisitor = new CypherWhereExpressionVisitor(createParameterCallback, capabilities);

            myVisitor.Visit(expression);
            return(myVisitor.TextOutput.ToString());
        }
        /// <remarks>
        /// This build method caters to expressions like: <code>item => item.As&lt;Foo&gt;().Bar</code>
        /// </remarks>
        static string BuildText(
            MemberExpression expression,
            CypherCapabilities capabilities,
            IEnumerable <JsonConverter> jsonConvertersThatTheDeserializerWillUse)
        {
            var innerExpression = expression.Expression as MethodCallExpression;

            if (innerExpression == null ||
                innerExpression.Method.DeclaringType != typeof(ICypherResultItem) ||
                innerExpression.Method.Name != "As")
            {
                throw new ArgumentException("Member expressions are only supported off ICypherResultItem.As<TData>(). For example: Return(foo => foo.As<Bar>().Baz).", "expression");
            }

            var baseStatement = BuildStatement(innerExpression, false, capabilities, jsonConvertersThatTheDeserializerWillUse);
            var statement     = string.Format("{0}.{1}", baseStatement, expression.Member.Name);

            return(statement);
        }
Exemple #18
0
        /// <remarks>
        /// This build method caters to expressions like: <code>item => item.As&lt;Foo&gt;().Bar</code>
        /// </remarks>
        static ExpressionBuild BuildText(
            MemberExpression expression,
            CypherCapabilities capabilities,
            IEnumerable <JsonConverter> jsonConvertersThatTheDeserializerWillUse,
            bool camelCaseProperties)
        {
            var innerExpression = expression.Expression as MethodCallExpression;

            if (innerExpression == null ||
                innerExpression.Method.DeclaringType != typeof(ICypherResultItem) ||
                innerExpression.Method.Name != "As")
            {
                throw new ArgumentException("Member expressions are only supported off ICypherResultItem.As<TData>(). For example: Return(foo => foo.As<Bar>().Baz).", "expression");
            }

            var baseStatement = BuildStatement(innerExpression, false, capabilities, jsonConvertersThatTheDeserializerWillUse);
            var statement     = string.Format("{0}.{1}", baseStatement.ExpressionText, CypherFluentQuery.ApplyCamelCase(camelCaseProperties, expression.Member.GetNameUsingJsonProperty()));

            return(new ExpressionBuild(statement, baseStatement.ResultFormat));
        }
Exemple #19
0
        /// <remarks>
        /// This C#:
        ///
        ///     new { Foo = "Bar", Baz = "Qak" }
        ///
        /// translates to:
        ///
        ///     new __SomeAnonymousType("Bar", "Qak")
        ///
        /// which is then a NewExpression rather than a MemberInitExpression.
        ///
        /// This is the scenario that this build method caters for.
        /// </remarks>
        static string BuildText(
            NewExpression expression,
            CypherCapabilities capabilities,
            IEnumerable <JsonConverter> jsonConvertersThatTheDeserializerWillUse,
            bool camelCaseProperties)
        {
            var constructor = expression.Constructor;

            if (constructor == null)
            {
                throw new ArgumentException(ReturnExpressionCannotBeStruct);
            }

            var resultingType             = constructor.DeclaringType;
            var quacksLikeAnAnonymousType =
                resultingType != null &&
                resultingType.IsSpecialName &&
                resultingType.IsValueType &&
                resultingType.IsNestedPrivate &&
                !resultingType.IsGenericType;

            if (expression.Members == null && !quacksLikeAnAnonymousType)
            {
                throw new ArgumentException(ReturnExpressionShouldBeOneOfExceptionMessage, "expression");
            }

            if (expression.Arguments.Count != expression.Members.Count)
            {
                throw new InvalidOperationException("Somehow we had a different number of members than arguments. We weren't expecting this to happen. Please raise an issue at http://hg.readify.net/neo4jclient including your query code.");
            }

            var bindingTexts = expression.Members.Select((member, index) =>
            {
                var argument = expression.Arguments[index];
                return(BuildStatement(argument, member, capabilities, jsonConvertersThatTheDeserializerWillUse, camelCaseProperties));
            });

            return(string.Join(", ", bindingTexts.ToArray()));
        }
        /// <remarks>
        /// This C#:
        /// 
        ///     new { Foo = "Bar", Baz = "Qak" }
        /// 
        /// translates to:
        /// 
        ///     new __SomeAnonymousType("Bar", "Qak")
        /// 
        /// which is then a NewExpression rather than a MemberInitExpression.
        /// 
        /// This is the scenario that this build method caters for.
        /// </remarks>
        static string BuildText(
            NewExpression expression,
            CypherCapabilities capabilities,
            IEnumerable<JsonConverter> jsonConvertersThatTheDeserializerWillUse)
        {
            var resultingType = expression.Constructor.DeclaringType;
            var quacksLikeAnAnonymousType =
                resultingType != null &&
                resultingType.IsSpecialName &&
                resultingType.IsValueType &&
                resultingType.IsNestedPrivate &&
                !resultingType.IsGenericType;
            if (expression.Members == null && !quacksLikeAnAnonymousType)
                throw new ArgumentException(ReturnExpressionShouldBeOneOfExceptionMessage, "expression");

            if (expression.Arguments.Count != expression.Members.Count)
                throw new InvalidOperationException("Somehow we had a different number of members than arguments. We weren't expecting this to happen. Please raise an issue at http://hg.readify.net/neo4jclient including your query code.");

            var bindingTexts = expression.Members.Select((member, index) =>
            {
                var argument = expression.Arguments[index];
                return BuildStatement(argument, member, capabilities, jsonConvertersThatTheDeserializerWillUse);
            });

            return string.Join(", ", bindingTexts.ToArray());
        }
        static string BuildStatement(
            MethodCallExpression expression,
            bool isNullable,
            CypherCapabilities capabilities,
            IEnumerable<JsonConverter> jsonConvertersThatTheDeserializerWillUse)
        {
            string statement;
            if (expression.Method.DeclaringType == typeof(ICypherResultItem) || expression.Method.DeclaringType == typeof(IFluentCypherResultItem))
                statement = BuildCypherResultItemStatement(expression, isNullable, capabilities, jsonConvertersThatTheDeserializerWillUse);
            else if (expression.Method.DeclaringType == typeof(All))
                statement = BuildCypherAllStatement(expression);
            else if (expression.Method.DeclaringType == typeof(Return))
                statement = BuildCypherReturnStatement(expression);
            else
                throw new ArgumentException(ReturnExpressionCannotBeSerializedToCypherExceptionMessage);

            return statement;
        }
        /// <remarks>
        /// This build method caters to object initializers, like:
        /// 
        ///     new MyType { Foo = "Bar", Baz = "Qak" }
        /// 
        /// It does not however cater to anonymous types, as they don't compile
        /// down to traditional object initializers.
        /// 
        /// <see cref="BuildText(NewExpression, CypherCapabilities, IEnumerable&lt;JsonConverter&gt;)"/> caters to anonymous types.
        /// </remarks>
        static string BuildText(
            MemberInitExpression expression,
            CypherCapabilities capabilities,
            IEnumerable<JsonConverter> jsonConvertersThatTheDeserializerWillUse)
        {
            if (expression.NewExpression.Constructor.GetParameters().Any())
                throw new ArgumentException(
                    "The result type must be constructed using a parameterless constructor. For example: n => new MyResultType { Foo = n.Bar }",
                    "expression");

            var bindingTexts = expression.Bindings.Select(binding =>
            {
                if (binding.BindingType != MemberBindingType.Assignment)
                    throw new ArgumentException("All bindings must be assignments. For example: n => new MyResultType { Foo = n.Bar }", "expression");

                var memberAssignment = (MemberAssignment)binding;
                return BuildStatement(memberAssignment.Expression, binding.Member, capabilities, jsonConvertersThatTheDeserializerWillUse);
            });

            return string.Join(", ", bindingTexts.ToArray());
        }
        static string BuildStatement(
            MemberExpression memberExpression,
            MemberInfo targetMember,
            CypherCapabilities capabilities)
        {
            MethodCallExpression methodCallExpression;
            MemberInfo memberInfo;
            if (memberExpression.NodeType == ExpressionType.MemberAccess && memberExpression.Expression.NodeType == ExpressionType.Call)
            {
                methodCallExpression = (MethodCallExpression) memberExpression.Expression;
                memberInfo = memberExpression.Member;
            }
            else if (memberExpression.NodeType == ExpressionType.MemberAccess && memberExpression.Expression.NodeType == ExpressionType.MemberAccess)
            {
                var nextedExpression = ((MemberExpression) memberExpression.Expression);
                methodCallExpression = (MethodCallExpression) nextedExpression.Expression;
                memberInfo = nextedExpression.Member;
            }
            else
            {
                throw new NotSupportedException(string.Format("The expression {0} is not supported", memberExpression));
            }
            var targetObject = (ParameterExpression) methodCallExpression.Object;

            if (targetObject == null)
                throw new InvalidOperationException(
                    "Somehow targetObject ended up as null. We weren't expecting this to happen. Please raise an issue at https://github.com/Readify/Neo4jClient including your query code.");

            var optionalIndicator = "";
            if (capabilities.SupportsPropertySuffixesForControllingNullComparisons)
            {
                var isTargetMemberNullable = IsMemberNullable(targetMember);
                var isNullable = isTargetMemberNullable || IsMemberNullable(memberInfo);
                if (isNullable) optionalIndicator = "?";
            }

            return string.Format("{0}.{1}{2} AS {3}", targetObject.Name, memberInfo.Name, optionalIndicator, targetMember.Name);
        }
 static string BuildStatement(
     MethodCallExpression expression,
     MemberInfo targetMember,
     CypherCapabilities capabilities,
     IEnumerable<JsonConverter> jsonConvertersThatTheDeserializerWillUse)
 {
     var isNullable = IsMemberNullable(targetMember);
     var statement = BuildStatement(expression, isNullable, capabilities, jsonConvertersThatTheDeserializerWillUse);
     statement = statement + " AS " + targetMember.Name;
     return statement;
 }
        /// <remarks>
        /// This build method caters to object initializers, like:
        /// 
        ///     new MyType { Foo = "Bar", Baz = "Qak" }
        /// 
        /// It does not however cater to anonymous types, as they don't compile
        /// down to traditional object initializers.
        /// 
        /// <see cref="BuildText(NewExpression, CypherCapabilities, IEnumerable&lt;JsonConverter&gt;)"/> caters to anonymous types.
        /// </remarks>
        static ExpressionBuild BuildText(
            MemberInitExpression expression,
            CypherCapabilities capabilities,
            IEnumerable<JsonConverter> jsonConvertersThatTheDeserializerWillUse,
            bool camelCaseProperties)
        {
            if (expression.NewExpression.Constructor == null)
            {
                throw new ArgumentException(ReturnExpressionCannotBeStruct);
            }

            if (expression.NewExpression.Constructor.GetParameters().Any())
                throw new ArgumentException(
                    "The result type must be constructed using a parameterless constructor. For example: n => new MyResultType { Foo = n.Bar }",
                    "expression");

            var bindingTexts = expression.Bindings.Select(binding =>
            {
                if (binding.BindingType != MemberBindingType.Assignment)
                    throw new ArgumentException(
                        "All bindings must be assignments. For example: n => new MyResultType { Foo = n.Bar }",
                        "expression");

                var memberAssignment = (MemberAssignment) binding;
                return BuildStatement(memberAssignment.Expression, binding.Member, capabilities, jsonConvertersThatTheDeserializerWillUse, camelCaseProperties);
            }).ToArray();

            var resultFormat = bindingTexts.Any(expressionBuild => expressionBuild.ResultFormat == CypherResultFormat.Rest)
                ? CypherResultFormat.Rest
                : CypherResultFormat.DependsOnEnvironment;

            return new ExpressionBuild(
                string.Join(", ", bindingTexts.Select(expressionBuild => expressionBuild.ExpressionText)),
                resultFormat);
        }
        static string BuildStatement(
            Expression sourceExpression,
            MemberInfo targetMember,
            CypherCapabilities capabilities,
            IEnumerable<JsonConverter> jsonConvertersThatTheDeserializerWillUse)
        {
            var unwrappedExpression = UnwrapImplicitCasts(sourceExpression);

            var memberExpression = unwrappedExpression as MemberExpression;
            if (memberExpression != null)
                return BuildStatement(memberExpression, targetMember, capabilities);

            var methodCallExpression = unwrappedExpression as MethodCallExpression;
            if (methodCallExpression != null)
                return BuildStatement(methodCallExpression, targetMember, capabilities, jsonConvertersThatTheDeserializerWillUse);

            var binaryExpression = unwrappedExpression as BinaryExpression;
            if(binaryExpression != null)
                return BuildStatement(binaryExpression, targetMember);

            throw new NotSupportedException(string.Format(
                "Expression of type {0} is not supported.",
                unwrappedExpression.GetType().FullName));
        }
        static string BuildCypherResultItemStatement(
            MethodCallExpression expression,
            bool isNullable,
            CypherCapabilities capabilities,
            IEnumerable<JsonConverter> jsonConvertersThatTheDeserializerWillUse)
        {
            Debug.Assert(expression.Object != null, "expression.Object != null");

            string statement = null;
            var targetObject = expression.Object as ParameterExpression;

            if (expression.Object.Type == typeof (IFluentCypherResultItem))
            {
                var wrappedFunctionCall = BuildWrappedFunction(expression);
                statement = wrappedFunctionCall.StatementFormat;
                targetObject = (ParameterExpression)wrappedFunctionCall.InnerExpression;
            }

            if (targetObject == null)
                throw new InvalidOperationException(
                    "Somehow targetObject ended up as null. We weren't expecting this to happen. Please raise an issue at http://hg.readify.net/neo4jclient including your query code.");

            var optionalIndicator = capabilities.SupportsPropertySuffixesForControllingNullComparisons && isNullable ? "?" : "";
            string finalStatement;
            var methodName = expression.Method.Name;
            var singleGenericArgument = expression.Method.IsGenericMethod
                ? expression.Method.GetGenericArguments().Single()
                : null;

            switch (methodName)
            {
                case "As":
                case "Node":
                    Debug.Assert(singleGenericArgument != null);
                    if (!IsSupportedForAs(singleGenericArgument, jsonConvertersThatTheDeserializerWillUse))
                        throw new ArgumentException(string.Format(ReturnAsTypeShouldBeOneOfExceptionMessage, singleGenericArgument.Name), "expression");
                    finalStatement = string.Format("{0}{1}", targetObject.Name, optionalIndicator);
                    break;
                case "CollectAs":
                    if (IsNodeOfT(singleGenericArgument))
                        throw new ArgumentException(CollectAsShouldNotBeNodeTExceptionMessage, "expression");
                    finalStatement = string.Format("collect({0})", targetObject.Name);
                    break;
                case "CollectAsDistinct":
                    if (IsNodeOfT(singleGenericArgument))
                        throw new ArgumentException(CollectAsDistinctShouldNotBeNodeTExceptionMessage, "expression");
                    finalStatement = string.Format("collect(distinct {0})", targetObject.Name);
                    break;
                case "Count":
                    finalStatement = string.Format("count({0})", targetObject.Name);
                    break;
                case "CountDistinct":
                    finalStatement = string.Format("count(distinct {0})", targetObject.Name);
                    break;
                case "Id":
                    finalStatement = string.Format("id({0})", targetObject.Name);
                    break;
                case "Length":
                    finalStatement = string.Format("length({0})", targetObject.Name);
                    break;
                case "Type":
                    finalStatement = string.Format("type({0})", targetObject.Name);
                    break;
                case "Labels":
                    finalStatement = string.Format("labels({0})", targetObject.Name);
                    break;
                default:
                    throw new InvalidOperationException("Unexpected ICypherResultItem method definition, ICypherResultItem." + methodName);
            }

            statement = statement != null
                ? string.Format(statement, finalStatement)
                : finalStatement;

            return statement;
        }
 /// <remarks>
 /// This build method caters to expressions like: <code>item => item.Count()</code>
 /// </remarks>
 static string BuildText(
     MethodCallExpression expression,
     CypherCapabilities capabilities,
     IEnumerable<JsonConverter> jsonConvertersThatTheDeserializerWillUse)
 {
     return BuildStatement(expression, false, capabilities, jsonConvertersThatTheDeserializerWillUse);
 }
        /// <remarks>
        /// This C#:
        /// 
        ///     new { Foo = "Bar", Baz = "Qak" }
        /// 
        /// translates to:
        /// 
        ///     new __SomeAnonymousType("Bar", "Qak")
        /// 
        /// which is then a NewExpression rather than a MemberInitExpression.
        /// 
        /// This is the scenario that this build method caters for.
        /// </remarks>
        static string BuildText(
            NewExpression expression,
            CypherCapabilities capabilities,
            IEnumerable<JsonConverter> jsonConvertersThatTheDeserializerWillUse,
            bool camelCaseProperties)
        {
            var constructor = expression.Constructor;
            if (constructor == null)
            {
                throw new ArgumentException(ReturnExpressionCannotBeStruct);
            }

            var resultingType = constructor.DeclaringType;
            var quacksLikeAnAnonymousType =
                resultingType != null &&
                resultingType.IsSpecialName &&
                resultingType.IsValueType &&
                resultingType.IsNestedPrivate &&
                !resultingType.IsGenericType;
            var expressionMembers = expression.Members;
            if (expressionMembers == null && !quacksLikeAnAnonymousType)
            {
                // expression.Members is null for Tuples and Record types generated by F#
                // ref: https://fsharppowerpack.codeplex.com/workitem/4572
                var reflectedMembers = resultingType.GetProperties(); //resultingType.GetMembers() gets all members, we only want properties
                if (reflectedMembers == null || !resultingType.FullName.StartsWith("System.Tuple`"))
                {
                    throw new ArgumentException(ReturnExpressionShouldBeOneOfExceptionMessage, "expression");
                }
                expressionMembers = new System.Collections.ObjectModel.ReadOnlyCollection<MemberInfo>(reflectedMembers);
            }

            if (expressionMembers == null)
            {
                throw new ArgumentException(ReturnExpressionShouldBeOneOfExceptionMessage, "expression");
            }

            if (expression.Arguments.Count != expressionMembers.Count)
                throw new InvalidOperationException("Somehow we had a different number of members than arguments. We weren't expecting this to happen. Please raise an issue at http://hg.readify.net/neo4jclient including your query code.");

            var bindingTexts = expressionMembers.Select((member, index) =>
            {
                var argument = expression.Arguments[index];
                return BuildStatement(argument, member, capabilities, jsonConvertersThatTheDeserializerWillUse,camelCaseProperties);
            });

            return string.Join(", ", bindingTexts.ToArray());
        }
Exemple #30
0
 public CypherWithExpressionBuilder(CypherCapabilities capabilities, bool camelCaseProperties)
 {
     this.capabilities        = capabilities ?? CypherCapabilities.Default;
     this.camelCaseProperties = camelCaseProperties;
 }
 static ExpressionBuild BuildStatement(
     MethodCallExpression expression,
     MemberInfo targetMember,
     CypherCapabilities capabilities,
     IEnumerable<JsonConverter> jsonConvertersThatTheDeserializerWillUse)
 {
     var isNullable = IsMemberNullable(targetMember);
     var statement = BuildStatement(expression, isNullable, capabilities, jsonConvertersThatTheDeserializerWillUse);
     return new ExpressionBuild(statement.ExpressionText + " AS " + targetMember.Name, statement.ResultFormat);
 }
        /// <remarks>
        /// This build method caters to expressions like: <code>item => item.As&lt;Foo&gt;().Bar</code>
        /// </remarks>
        static string BuildText(
            MemberExpression expression,
            CypherCapabilities capabilities,
            IEnumerable<JsonConverter> jsonConvertersThatTheDeserializerWillUse)
        {
            var innerExpression = expression.Expression as MethodCallExpression;
            if (innerExpression == null ||
                innerExpression.Method.DeclaringType != typeof(ICypherResultItem) ||
                innerExpression.Method.Name != "As")
                throw new ArgumentException("Member expressions are only supported off ICypherResultItem.As<TData>(). For example: Return(foo => foo.As<Bar>().Baz).", "expression");

            var baseStatement = BuildStatement(innerExpression, false, capabilities, jsonConvertersThatTheDeserializerWillUse);
            var statement = string.Format("{0}.{1}", baseStatement, expression.Member.Name);

            return statement;
        }
        static string BuildCypherResultItemStatement(
            MethodCallExpression expression,
            bool isNullable,
            CypherCapabilities capabilities,
            IEnumerable <JsonConverter> jsonConvertersThatTheDeserializerWillUse)
        {
            Debug.Assert(expression.Object != null, "expression.Object != null");

            string statement    = null;
            var    targetObject = expression.Object as ParameterExpression;

            if (expression.Object.Type == typeof(IFluentCypherResultItem))
            {
                var wrappedFunctionCall = BuildWrappedFunction(expression);
                statement    = wrappedFunctionCall.StatementFormat;
                targetObject = (ParameterExpression)wrappedFunctionCall.InnerExpression;
            }

            if (targetObject == null)
            {
                throw new InvalidOperationException(
                          "Somehow targetObject ended up as null. We weren't expecting this to happen. Please raise an issue at http://hg.readify.net/neo4jclient including your query code.");
            }

            var    optionalIndicator = capabilities.SupportsPropertySuffixesForControllingNullComparisons && isNullable ? "?" : "";
            string finalStatement;
            var    methodName            = expression.Method.Name;
            var    singleGenericArgument = expression.Method.IsGenericMethod
                ? expression.Method.GetGenericArguments().Single()
                : null;

            switch (methodName)
            {
            case "As":
            case "Node":
                Debug.Assert(singleGenericArgument != null);
                if (!IsSupportedForAs(singleGenericArgument, jsonConvertersThatTheDeserializerWillUse))
                {
                    throw new ArgumentException(string.Format(ReturnAsTypeShouldBeOneOfExceptionMessage, singleGenericArgument.Name), "expression");
                }
                finalStatement = string.Format("{0}{1}", targetObject.Name, optionalIndicator);
                break;

            case "CollectAs":
                if (IsNodeOfT(singleGenericArgument))
                {
                    throw new ArgumentException(CollectAsShouldNotBeNodeTExceptionMessage, "expression");
                }
                finalStatement = string.Format("collect({0})", targetObject.Name);
                break;

            case "CollectAsDistinct":
                if (IsNodeOfT(singleGenericArgument))
                {
                    throw new ArgumentException(CollectAsDistinctShouldNotBeNodeTExceptionMessage, "expression");
                }
                finalStatement = string.Format("collect(distinct {0})", targetObject.Name);
                break;

            case "Count":
                finalStatement = string.Format("count({0})", targetObject.Name);
                break;

            case "CountDistinct":
                finalStatement = string.Format("count(distinct {0})", targetObject.Name);
                break;

            case "Id":
                finalStatement = string.Format("id({0})", targetObject.Name);
                break;

            case "Length":
                finalStatement = string.Format("length({0})", targetObject.Name);
                break;

            case "Type":
                finalStatement = string.Format("type({0})", targetObject.Name);
                break;

            case "Labels":
                finalStatement = string.Format("labels({0})", targetObject.Name);
                break;

            default:
                throw new InvalidOperationException("Unexpected ICypherResultItem method definition, ICypherResultItem." + methodName);
            }

            statement = statement != null
                ? string.Format(statement, finalStatement)
                : finalStatement;

            return(statement);
        }
        /// <remarks>
        /// This build method caters to expressions like: <code>item => item.As&lt;Foo&gt;().Bar</code>
        /// </remarks>
        static ExpressionBuild BuildText(
            MemberExpression expression,
            CypherCapabilities capabilities,
            IEnumerable<JsonConverter> jsonConvertersThatTheDeserializerWillUse, 
            bool camelCaseProperties)
        {
            var innerExpression = expression.Expression as MethodCallExpression;
            if (innerExpression == null ||
                innerExpression.Method.DeclaringType != typeof(ICypherResultItem) ||
                innerExpression.Method.Name != "As")
                throw new ArgumentException("Member expressions are only supported off ICypherResultItem.As<TData>(). For example: Return(foo => foo.As<Bar>().Baz).", "expression");

            var baseStatement = BuildStatement(innerExpression, false, capabilities, jsonConvertersThatTheDeserializerWillUse);
            var statement = string.Format("{0}.{1}", baseStatement.ExpressionText, CypherFluentQuery.ApplyCamelCase(camelCaseProperties, expression.Member.GetNameUsingJsonProperty()));

            return new ExpressionBuild(statement, baseStatement.ResultFormat);
        }
 public CypherWithExpressionBuilder(CypherCapabilities capabilities, bool camelCaseProperties)
 {
     this.capabilities = capabilities ?? CypherCapabilities.Default;
     this.camelCaseProperties = camelCaseProperties;
 }