diff --git a/Neo4jClient.Tests/Cypher/CypherFluentQueryWithTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryWithTests.cs index 0bd9d1692..07de75119 100644 --- a/Neo4jClient.Tests/Cypher/CypherFluentQueryWithTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryWithTests.cs @@ -78,6 +78,36 @@ public void ShouldReturnSpecificPropertyOnItsOwn() Assert.AreEqual("WITH a.Name", query.QueryText); } + [Test] + public void ShouldReturnSpecificPropertyWithAliasWithNullableSuffixInCypher19() + { + var client = Substitute.For(); + client.CypherCapabilities.Returns(CypherCapabilities.Cypher19); + var query = new CypherFluentQuery(client) + .With(a => new + { + SomeAlias = a.As().Name + }) + .Query; + + Assert.AreEqual("WITH a.Name? AS SomeAlias", query.QueryText); + } + + [Test] + public void ShouldReturnSpecificPropertyWithAliasWithoutNullableSuffixInCypher20() + { + var client = Substitute.For(); + client.CypherCapabilities.Returns(CypherCapabilities.Cypher20); + var query = new CypherFluentQuery(client) + .With(a => new + { + SomeAlias = a.As().Name + }) + .Query; + + Assert.AreEqual("WITH a.Name AS SomeAlias", query.QueryText); + } + [Test] public void ShouldReturnSpecificPropertyOnItsOwnCamelAs() { @@ -87,7 +117,7 @@ public void ShouldReturnSpecificPropertyOnItsOwnCamelAs() .With(a => new Commodity(){ Name = a.As().Name}) .Query; - Assert.AreEqual("WITH a.name? AS Name", query.QueryText); + Assert.AreEqual("WITH a.name AS Name", query.QueryText); } [Test] diff --git a/Neo4jClient/Cypher/CypherFluentQuery`With.cs b/Neo4jClient/Cypher/CypherFluentQuery`With.cs index dfeb894ba..6bf965d6b 100644 --- a/Neo4jClient/Cypher/CypherFluentQuery`With.cs +++ b/Neo4jClient/Cypher/CypherFluentQuery`With.cs @@ -12,7 +12,8 @@ public ICypherFluentQuery With(string withText) ICypherFluentQuery With(LambdaExpression expression) { - var withExpression = CypherWithExpressionBuilder.BuildText(expression, CamelCaseProperties); + var expressionBuilder = new CypherWithExpressionBuilder(Client.CypherCapabilities, CamelCaseProperties); + var withExpression = expressionBuilder.BuildText(expression); return Mutate(w => { diff --git a/Neo4jClient/Cypher/CypherWithExpressionBuilder.cs b/Neo4jClient/Cypher/CypherWithExpressionBuilder.cs index d4c8a2091..e894793bc 100644 --- a/Neo4jClient/Cypher/CypherWithExpressionBuilder.cs +++ b/Neo4jClient/Cypher/CypherWithExpressionBuilder.cs @@ -22,7 +22,16 @@ public class CypherWithExpressionBuilder // - 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, bool camelCaseProperties) + private readonly CypherCapabilities capabilities; + private readonly bool camelCaseProperties; + + public CypherWithExpressionBuilder(CypherCapabilities capabilities, bool camelCaseProperties) + { + this.capabilities = capabilities ?? CypherCapabilities.Default; + this.camelCaseProperties = camelCaseProperties; + } + + public ReturnExpression BuildText(LambdaExpression expression) { var body = expression.Body; @@ -37,11 +46,11 @@ public static ReturnExpression BuildText(LambdaExpression expression, bool camel { case ExpressionType.MemberInit: var memberInitExpression = (MemberInitExpression) body; - text = BuildText(memberInitExpression, camelCaseProperties); + text = BuildText(memberInitExpression); return new ReturnExpression {Text = text, ResultMode = CypherResultMode.Projection}; case ExpressionType.New: var newExpression = (NewExpression) body; - text = BuildText(newExpression,camelCaseProperties); + text = BuildText(newExpression); return new ReturnExpression {Text = text, ResultMode = CypherResultMode.Projection}; case ExpressionType.Call: var methodCallExpression = (MethodCallExpression) body; @@ -49,7 +58,7 @@ public static ReturnExpression BuildText(LambdaExpression expression, bool camel return new ReturnExpression {Text = text, ResultMode = CypherResultMode.Set}; case ExpressionType.MemberAccess: var memberExpression = (MemberExpression) body; - text = BuildText(memberExpression, camelCaseProperties); + text = BuildText(memberExpression); return new ReturnExpression { Text = text, ResultMode = CypherResultMode.Set }; default: throw new ArgumentException(WithExpressionShouldBeOneOfExceptionMessage, "expression"); @@ -66,7 +75,7 @@ public static ReturnExpression BuildText(LambdaExpression expression, bool camel /// /// caters to anonymous types. /// - static string BuildText(MemberInitExpression expression,bool camelCaseProperties) + string BuildText(MemberInitExpression expression) { if (expression.NewExpression.Constructor.GetParameters().Any()) throw new ArgumentException( @@ -79,7 +88,7 @@ static string BuildText(MemberInitExpression expression,bool camelCaseProperties 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, camelCaseProperties); + return BuildStatement(memberAssignment.Expression, binding.Member); }); return string.Join(", ", bindingTexts.ToArray()); @@ -98,7 +107,7 @@ static string BuildText(MemberInitExpression expression,bool camelCaseProperties /// /// This is the scenario that this build method caters for. /// - static string BuildText(NewExpression expression, bool camelCaseProperties) + string BuildText(NewExpression expression) { var resultingType = expression.Constructor.DeclaringType; Debug.Assert(resultingType != null, "resultingType != null"); @@ -117,7 +126,7 @@ static string BuildText(NewExpression expression, bool camelCaseProperties) var bindingTexts = expression.Members.Select((member, index) => { var argument = expression.Arguments[index]; - return BuildStatement(argument, member,camelCaseProperties); + return BuildStatement(argument, member); }); return string.Join(", ", bindingTexts.ToArray()); @@ -126,7 +135,7 @@ static string BuildText(NewExpression expression, bool camelCaseProperties) /// /// This build method caters to expressions like: item => item.Count() /// - static string BuildText(MethodCallExpression expression) + string BuildText(MethodCallExpression expression) { return BuildStatement(expression, false); } @@ -134,7 +143,7 @@ static string BuildText(MethodCallExpression expression) /// /// This build method caters to expressions like: item => item.As<Foo>().Bar /// - static string BuildText(MemberExpression expression, bool camelCaseProperties) + string BuildText(MemberExpression expression) { var innerExpression = expression.Expression as MethodCallExpression; if (innerExpression == null || @@ -148,13 +157,13 @@ static string BuildText(MemberExpression expression, bool camelCaseProperties) return statement; } - static string BuildStatement(Expression sourceExpression, MemberInfo targetMember, bool camelCaseProperties) + string BuildStatement(Expression sourceExpression, MemberInfo targetMember) { var unwrappedExpression = UnwrapImplicitCasts(sourceExpression); var memberExpression = unwrappedExpression as MemberExpression; if (memberExpression != null) - return BuildStatement(memberExpression, targetMember,camelCaseProperties); + return BuildStatement(memberExpression, targetMember); var methodCallExpression = unwrappedExpression as MethodCallExpression; if (methodCallExpression != null) @@ -173,7 +182,7 @@ static string BuildStatement(Expression sourceExpression, MemberInfo targetMembe unwrappedExpression.GetType().FullName)); } - static string BuildStatement(MemberExpression memberExpression, MemberInfo targetMember, bool camelCaseProperties) + string BuildStatement(MemberExpression memberExpression, MemberInfo targetMember) { MethodCallExpression methodCallExpression; MemberInfo memberInfo; @@ -198,10 +207,13 @@ static string BuildStatement(MemberExpression memberExpression, MemberInfo targe 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 isTargetMemberNullable = IsMemberNullable(targetMember); - var isNullable = isTargetMemberNullable || IsMemberNullable(memberInfo); - - var optionalIndicator = isNullable ? "?" : ""; + var optionalIndicator = string.Empty; + 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, CypherFluentQuery.ApplyCamelCase(camelCaseProperties, memberInfo.Name), optionalIndicator, targetMember.Name); }