diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2eeefdc --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Standard VS.NET and ReSharper Foo +source/*/obj +source/*/bin +*.csproj.user +*ReSharper.user +_ReSharper* +*resharper* +*.suo +*.cache +* Thumbs.db \ No newline at end of file diff --git a/source/XeroApi.Tests/ApiQueryTests.cs b/source/XeroApi.Tests/ApiQueryTests.cs index 68a7faa..716dea8 100644 --- a/source/XeroApi.Tests/ApiQueryTests.cs +++ b/source/XeroApi.Tests/ApiQueryTests.cs @@ -425,17 +425,45 @@ public void TestApiQueryCanCallEmployeeEndpointWithSingleOperator() } [Test] - public void TestApiQueryCanCallEmployeeEndpointWithSingleOrDefaultOperator() + public void TestApiQueryCanCallInvoicesEndpointWithUpdatedDateAndUrlFilterCombinedWithAndOperator() { StubIntegrationProxy integrationProxy = new StubIntegrationProxy(); Repository repository = new Repository(integrationProxy); - repository.Users.SingleOrDefault(u => u.FullName == "Joe Bloggs"); + repository.Invoices.SingleOrDefault(i => i.UpdatedDateUTC > new DateTime(2010, 1, 1) && i.Url != null); var queryDesctipion = integrationProxy.LastQueryDescription; - Assert.AreEqual("User", queryDesctipion.ElementType.Name); + Assert.AreEqual("Invoice", queryDesctipion.ElementType.Name); Assert.AreEqual("SingleOrDefault", queryDesctipion.ClientSideExpression); - Assert.AreEqual("(FullName == \"Joe Bloggs\")", queryDesctipion.Where); + Assert.AreEqual("(Url <> NULL)", queryDesctipion.Where); + } + + [Test] + public void TestApiQueryCanCallContactsEndpointWithContactNumberIsNull() + { + StubIntegrationProxy integrationProxy = new StubIntegrationProxy(); + Repository repository = new Repository(integrationProxy); + + repository.Contacts.SingleOrDefault(i => i.ContactNumber == null); + + var queryDesctipion = integrationProxy.LastQueryDescription; + Assert.AreEqual("Contact", queryDesctipion.ElementType.Name); + Assert.AreEqual("SingleOrDefault", queryDesctipion.ClientSideExpression); + Assert.AreEqual("(ContactNumber == NULL)", queryDesctipion.Where); + } + + [Test] + public void TestApiQueryCanCallContactsEndpointWithContactNumberIsNotNull() + { + StubIntegrationProxy integrationProxy = new StubIntegrationProxy(); + Repository repository = new Repository(integrationProxy); + + repository.Contacts.SingleOrDefault(i => i.ContactNumber != null); + + var queryDesctipion = integrationProxy.LastQueryDescription; + Assert.AreEqual("Contact", queryDesctipion.ElementType.Name); + Assert.AreEqual("SingleOrDefault", queryDesctipion.ClientSideExpression); + Assert.AreEqual("(ContactNumber <> NULL)", queryDesctipion.Where); } } } diff --git a/source/XeroApi/Linq/ApiQueryTranslator.cs b/source/XeroApi/Linq/ApiQueryTranslator.cs index 5a1913d..4dcd311 100644 --- a/source/XeroApi/Linq/ApiQueryTranslator.cs +++ b/source/XeroApi/Linq/ApiQueryTranslator.cs @@ -147,6 +147,27 @@ protected override Expression VisitUnary(UnaryExpression u) return u; } + bool IsNotRenderedIntoExpression(Expression e) + { + BinaryExpression b = e as BinaryExpression; + if (b == null) return false; + + MemberExpression mExp = b.Left as MemberExpression; + + // Check if the LHS is an ItemId, ItemNumber or UpdatedDate. If so, record away from the main where clause. + if (mExp != null && (mExp.Member.DeclaringType.Name == _query.ElementName)) + { + if (mExp.Member.Name == _query.ElementIdProperty.SafeName() + || mExp.Member.Name == _query.ElementNumberProperty.SafeName() + || mExp.Member.Name == _query.ElementUpdatedDateProperty.SafeName()) + { + return true; + } + } + + return false; + } + protected override Expression VisitBinary(BinaryExpression b) { MemberExpression mExp = b.Left as MemberExpression; @@ -164,8 +185,13 @@ protected override Expression VisitBinary(BinaryExpression b) } if (mExp.Member.Name == _query.ElementNumberProperty.SafeName()) { - _query.ElementId = EvaluateExpression(b.Right).ToString(); - return b; + var rightValue = EvaluateExpression(b.Right); + + if (rightValue != null) + { + _query.ElementId = rightValue.ToString(); + return b; + } } if (mExp.Member.Name == _query.ElementUpdatedDateProperty.SafeName()) { @@ -193,48 +219,55 @@ protected override Expression VisitBinary(BinaryExpression b) return Visit(Expression.Equal(memberExpression, valueExpression)); } - - // Otherswise, parse as a normal (operand1 operator operand2) - Append("("); - Visit(b.Left); - switch (b.NodeType) - { - case ExpressionType.And: - case ExpressionType.AndAlso: - Append(" AND "); - break; - case ExpressionType.Or: - case ExpressionType.OrElse: - Append(" OR "); - break; - case ExpressionType.Equal: - Append(" == "); - break; - case ExpressionType.NotEqual: - Append(" <> "); - break; - case ExpressionType.LessThan: - Append(" < "); - break; - case ExpressionType.LessThanOrEqual: - Append(" <= "); - break; - case ExpressionType.GreaterThan: - Append(" > "); - break; - case ExpressionType.GreaterThanOrEqual: - Append(" >= "); - break; - default: - throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", b.NodeType)); + if (IsNotRenderedIntoExpression(b.Left)) { + VisitBinary((BinaryExpression)b.Left); + Visit(b.Right); + return b.Right; + } else if (IsNotRenderedIntoExpression(b.Right)) { + Visit(b.Left); + VisitBinary((BinaryExpression)b.Right); + return b.Left; + } else { + // Otherswise, parse as a normal (operand1 operator operand2) + Append("("); + Visit(b.Left); + switch (b.NodeType) + { + case ExpressionType.And: + case ExpressionType.AndAlso: + Append(" AND "); + break; + case ExpressionType.Or: + case ExpressionType.OrElse: + Append(" OR "); + break; + case ExpressionType.Equal: + Append(" == "); + break; + case ExpressionType.NotEqual: + Append(" <> "); + break; + case ExpressionType.LessThan: + Append(" < "); + break; + case ExpressionType.LessThanOrEqual: + Append(" <= "); + break; + case ExpressionType.GreaterThan: + Append(" > "); + break; + case ExpressionType.GreaterThanOrEqual: + Append(" >= "); + break; + default: + throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", b.NodeType)); + } + Visit(b.Right); + Append(")"); + return b; } - Visit(b.Right); - Append(")"); - return b; } - - protected override Expression VisitConstant(ConstantExpression c) { IQueryable q = c.Value as IQueryable;