/// <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<JsonConverter>)"/> 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"); } }
/// <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)); }
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(); }
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}")); }
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<Foo>().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); }
/// <remarks> /// This build method caters to expressions like: <code>item => item.As<Foo>().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)); }
/// <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<JsonConverter>)"/> 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<JsonConverter>)"/> 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()); }
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<Foo>().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<Foo>().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); }