diff --git a/Settings.StyleCop b/Settings.StyleCop
index 2f20031..5af3478 100644
--- a/Settings.StyleCop
+++ b/Settings.StyleCop
@@ -247,6 +247,11 @@
False
+
+
+ False
+
+
@@ -371,6 +376,16 @@
False
+
+
+ False
+
+
+
+
+ False
+
+
diff --git a/WebAPI.NHibernate-OData/Internal/FixStringMethodsVisitor.cs b/WebAPI.NHibernate-OData/Internal/FixStringMethodsVisitor.cs
index cf5c425..06590fc 100644
--- a/WebAPI.NHibernate-OData/Internal/FixStringMethodsVisitor.cs
+++ b/WebAPI.NHibernate-OData/Internal/FixStringMethodsVisitor.cs
@@ -5,97 +5,138 @@
namespace Pathoschild.WebApi.NhibernateOdata.Internal
{
- /// Intercepts queries before they're parsed by NHibernate to rewrite unsupported lambdas for , and .
- ///
- /// The expression tree generated by the ODataQueryOptions.ApplyTo method looks like the following sample.
- ///
- /// .Lambda #Lambda1<System.Func`2[Pathoschild.WebApi.NhibernateOdata.Tests.Models.Parent,System.Boolean]>(Pathoschild.WebApi.NhibernateOdata.Tests.Models.Parent $$it)
- /// {
- /// (.If (
- /// $$it.Name == null | .Constant<System.Web.Http.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.String]>(System.Web.Http.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.String]).TypedProperty ==
- /// null
- /// ) {
- /// null
- /// } .Else {
- /// (System.Nullable`1[System.Boolean]).Call ($$it.Name).Contains(.Constant<System.Web.Http.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.String]>(System.Web.Http.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.String]).TypedProperty)
- /// } == (System.Nullable`1[System.Boolean]).Constant<System.Web.Http.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.Boolean]>(System.Web.Http.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.Boolean]).TypedProperty)
- /// == .Constant<System.Nullable`1[System.Boolean]>(True)
- /// }
- ///
- ///
- public class FixStringMethodsVisitor : ExpressionVisitor
- {
- /*********
- ** Properties
- *********/
- /// Whether the visitor is visiting a nested node.
- /// This is used to recognize the top-level node for logging.
- private bool IsRecursing;
-
- /// A list of methods supported by this visitor.
- private readonly List StringMethods = new List();
-
-
- /*********
- ** Public methods
- *********/
- /// Constructs an instance.
- public FixStringMethodsVisitor()
- {
- this.StringMethods.AddRange(typeof(string).GetMethods(BindingFlags.Public | BindingFlags.Instance).Where(x => x.Name == "Contains" || x.Name == "StartsWith" || x.Name == "EndsWith"));
- }
-
- /// Dispatches the expression to one of the more specialized visit methods in this class.
- /// The expression to visit.
- /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
- public override Expression Visit(Expression node)
- {
- // top node
- if (!this.IsRecursing)
- {
- this.IsRecursing = true;
- return base.Visit(node);
- }
-
- var conditionalExpression = node as ConditionalExpression;
- if (conditionalExpression != null)
- return this.HandleConditionalExpression(node, conditionalExpression);
-
- return base.Visit(node);
- }
-
-
- /*********
- ** Protected methods
- *********/
- /// Handles the conditional expression (equivalent to .If {} .Else {} in the sample expression tree in the remarks).
- /// The original expression.
- /// The conditional expression.
- /// A reduced if/else statement if it contains any of the matched methods. Otherwise, the original expression.
- private Expression HandleConditionalExpression(Expression original, ConditionalExpression ifElse)
- {
- var elseExpression = ifElse.IfFalse as UnaryExpression;
- if (elseExpression != null)
- {
- var methodCallExpression = elseExpression.Operand as MethodCallExpression;
- if (methodCallExpression != null)
- {
- if (this.StringMethods.Contains(methodCallExpression.Method))
- {
- var methodCallReplacement = Expression.Call(
- methodCallExpression.Object,
- methodCallExpression.Method,
- methodCallExpression.Arguments);
-
- // Convert the result to a nullable boolean so the Expression.Equal works.
- var result = Expression.Convert(methodCallReplacement, typeof(bool?));
-
- return result;
- }
- }
- }
-
- return original;
- }
- }
+ /// Intercepts queries before they're parsed by NHibernate to rewrite unsupported lambdas for , and .
+ ///
+ /// The expression tree generated by the ODataQueryOptions.ApplyTo method looks like the following sample.
+ ///
+ /// .Lambda #Lambda1<System.Func`2[Pathoschild.WebApi.NhibernateOdata.Tests.Models.Parent,System.Boolean]>(Pathoschild.WebApi.NhibernateOdata.Tests.Models.Parent $$it)
+ /// {
+ /// (.If (
+ /// $$it.Name == null | .Constant<System.Web.Http.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.String]>(System.Web.Http.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.String]).TypedProperty ==
+ /// null
+ /// ) {
+ /// null
+ /// } .Else {
+ /// (System.Nullable`1[System.Boolean]).Call ($$it.Name).Contains(.Constant<System.Web.Http.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.String]>(System.Web.Http.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.String]).TypedProperty)
+ /// } == (System.Nullable`1[System.Boolean]).Constant<System.Web.Http.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.Boolean]>(System.Web.Http.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.Boolean]).TypedProperty)
+ /// == .Constant<System.Nullable`1[System.Boolean]>(True)
+ /// }
+ ///
+ ///
+ /// The actual System.Web.Http.OData parser DOES NOT support the "replace" string method, so we can't make it go through NHibernate.
+ ///
+ public class FixStringMethodsVisitor : ExpressionVisitor
+ {
+ /*********
+ ** Properties
+ *********/
+ /// Whether the visitor is visiting a nested node.
+ /// This is used to recognize the top-level node for logging.
+ private bool IsRecursing;
+
+ /// A list of boolean return methods supported by this visitor.
+ private readonly List BooleanReturnStringMethods = new List();
+
+ /// A list of integer return methods supported by this visitor.
+ private readonly List IntegerStringMethods = new List();
+
+ /// A list of concatenation methods supported by this visitor.
+ private readonly List ConcatStringMethods = new List();
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// Constructs an instance.
+ public FixStringMethodsVisitor()
+ {
+ this.BooleanReturnStringMethods.AddRange(typeof(string).GetMethods(BindingFlags.Public | BindingFlags.Instance).Where(x => x.Name == "Contains" || x.Name == "StartsWith" || x.Name == "EndsWith"));
+ this.IntegerStringMethods.AddRange(typeof(string).GetMethods(BindingFlags.Public | BindingFlags.Instance).Where(x => x.Name == "IndexOf").ToList());
+ this.ConcatStringMethods.AddRange(typeof(string).GetMethods(BindingFlags.Public | BindingFlags.Static).Where(x => x.Name == "Concat").ToList());
+ }
+
+ /// Dispatches the expression to one of the more specialized visit methods in this class.
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
+ public override Expression Visit(Expression node)
+ {
+ // top node
+ if (!this.IsRecursing)
+ {
+ this.IsRecursing = true;
+ return base.Visit(node);
+ }
+
+ var conditionalExpression = node as ConditionalExpression;
+ if (conditionalExpression != null)
+ return this.HandleConditionalExpression(node, conditionalExpression);
+
+ return base.Visit(node);
+ }
+
+
+ /*********
+ ** Protected methods
+ *********/
+ /// Handles the conditional expression (equivalent to .If {} .Else {} in the sample expression tree in the remarks).
+ /// The original expression.
+ /// The conditional expression.
+ /// A reduced if/else statement if it contains any of the matched methods. Otherwise, the original expression.
+ private Expression HandleConditionalExpression(Expression original, ConditionalExpression ifElse)
+ {
+ var elseExpression = ifElse.IfFalse as UnaryExpression;
+ if (elseExpression != null)
+ {
+ var methodCallExpression = elseExpression.Operand as MethodCallExpression;
+ if (methodCallExpression != null)
+ {
+ if (this.BooleanReturnStringMethods.Contains(methodCallExpression.Method))
+ {
+ var methodCallReplacement = Expression.Call(
+ methodCallExpression.Object,
+ methodCallExpression.Method,
+ methodCallExpression.Arguments);
+
+ // Convert the result to a nullable boolean so the Expression.Equal works.
+ var result = Expression.Convert(methodCallReplacement, typeof(bool?));
+ return result;
+ }
+
+ if (this.IntegerStringMethods.Contains(methodCallExpression.Method))
+ {
+ var methodCallReplacement = Expression.Call(
+ methodCallExpression.Object,
+ methodCallExpression.Method,
+ methodCallExpression.Arguments);
+
+ var result = Expression.Convert(methodCallReplacement, typeof(int?));
+ return result;
+ }
+ }
+ }
+
+ var firstLevelMethodCallExpression = ifElse.IfFalse as MethodCallExpression;
+ if (firstLevelMethodCallExpression != null)
+ {
+ // Using the method name and declaring type as strings because I don't want to add a dependency to the project for a simple check like that.
+ if (firstLevelMethodCallExpression.Method.DeclaringType != null &&
+ firstLevelMethodCallExpression.Method.DeclaringType.FullName == "System.Web.Http.OData.Query.Expressions.ClrSafeFunctions" &&
+ (firstLevelMethodCallExpression.Method.Name == "SubstringStartAndLength" || firstLevelMethodCallExpression.Method.Name == "SubstringStart"))
+ {
+ var arguments = firstLevelMethodCallExpression.Arguments.Skip(1).ToArray();
+ return Expression.Call(
+ firstLevelMethodCallExpression.Arguments[0],
+ typeof(string).GetMethod("Substring", arguments.Select(x => typeof(int)).ToArray()),
+ arguments);
+ }
+
+ if (this.ConcatStringMethods.Contains(firstLevelMethodCallExpression.Method))
+ {
+ return Expression.Add(firstLevelMethodCallExpression.Arguments.First(), firstLevelMethodCallExpression.Arguments.Last(), firstLevelMethodCallExpression.Method);
+ }
+ }
+
+ return original;
+ }
+ }
}
\ No newline at end of file
diff --git a/WebApi.NHibernate-OData.Tests/Internal/GivenNHibernateQuery.cs b/WebApi.NHibernate-OData.Tests/Internal/GivenNHibernateQuery.cs
index 9b009c4..50e174d 100644
--- a/WebApi.NHibernate-OData.Tests/Internal/GivenNHibernateQuery.cs
+++ b/WebApi.NHibernate-OData.Tests/Internal/GivenNHibernateQuery.cs
@@ -11,121 +11,115 @@
namespace Pathoschild.WebApi.NhibernateOdata.Tests.Internal
{
- // ReSharper disable InconsistentNaming
- [TestFixture]
- [Category("Integration")]
- public class GivenNHibernateQuery
- {
- private static readonly ISessionFactory SessionFactory = NHibernateHelper.SessionFactory;
- private ISession _session;
-
- [SetUp]
- public void SetUp()
- {
- this._session = SessionFactory.OpenSession();
- }
-
- [TearDown]
- public void TearDown()
- {
- this._session.Dispose();
- }
-
- [TestCase(true)]
- [TestCase(false)]
- public void When_querying_nullable_Then_queries_database(bool withVisitor)
- {
- var visitor = new FixNullableBooleanVisitor();
- var odataQuery = Helpers.Build("$filter=Parent/Id eq 61 and Id eq 11");
- var children = this._session.Query();
-
- var results = odataQuery.ApplyTo(children).Cast