title: Is it possible to implement ExpressionTree.GreaterThan, etc for string so that LINQ can use it
body: <p>I saw a question this morning (<a href="https://stackoverflow.com/questions/14896585/query-my-model-on-a-range-of-values">Query my model on a range of values</a>) that seems to be answered by (<a href="https://stackoverflow.com/a/1447926/195550">https://stackoverflow.com/a/1447926/195550</a>), but the whole situation there peeked my interest in a more generalized solution.</p>
  
  <p>I was hoping to be able to use Jon Skeet's answer to implement a <code>Between</code>that would work with string keys in a non-SQL generated environment, but it appears that the fact that string does not implement the <strong>GreaterThan</strong>, <strong>LessThan</strong>, <strong>GreaterThanOrEqual</strong>, <strong>LessThanOrEqual</strong> operators gets in the way of Linq being able to build the Expression trees necessary to do this.</p>
  
  <ul>
  <li><p>I realize that it is possible to just do query using the the <code>CompareTo</code> methods to accomplish this task, but I really like the elegance of the <code>query.Between(v=&gt;v.StringKey, "abc", "hjk")</code> expression.</p></li>
  <li><p>I've looked through the System.Linq.Expression assembly, and saw that it is looking for a method named 'op_GreaterThan', for example for the GreaterThan operation, but <em>I don't know</em></p>
  
  <ol>
  <li>Whether I can implement this for a string (knowing that I can't extend the actual '>' operator for strings)</li>
  <li>How to get the correct method signature built.</li>
  </ol></li>
  <li><p>I created the following example and tests that shows where the Between extension method doesn't work on a string key.</p></li>
  </ul>
  
  <p>It would be quite elegant if that could be implemented for string keys.  Anybody have any suggestions, or insights on how to accomplish this?</p>
  
  <p><strong>Between operator from Jon Skeet, with inclusive flag added</strong></p>
  
  <hr>
  
  <pre><code>public static class BetweenExtension
  {
      public static IQueryable&lt;TSource&gt; Between&lt;TSource, TKey&gt;(
          this IQueryable&lt;TSource&gt; source,
          Expression&lt;Func&lt;TSource, TKey&gt;&gt; keySelector,
          TKey low,
          TKey high,
          bool inclusive = true) where TKey : IComparable&lt;TKey&gt;
      {
          var key = Expression.Invoke(keySelector, keySelector.Parameters.ToArray());
  
          var lowerBound = (inclusive)
                    ? Expression.GreaterThanOrEqual(key, Expression.Constant(low))
                    : Expression.GreaterThan(key, Expression.Constant(low));
  
          var upperBound = (inclusive)
                    ? Expression.LessThanOrEqual(key, Expression.Constant(high))
                    : Expression.LessThan(key, Expression.Constant(high));
  
          var and = Expression.AndAlso(lowerBound, upperBound);
          var lambda = Expression.Lambda&lt;Func&lt;TSource, bool&gt;&gt;(
                          and, keySelector.Parameters);
  
          return source.Where(lambda);
      }
  }
  </code></pre>
  
  <p><strong>Working Test of the above using an "int" key</strong></p>
  
  <hr>
  
  <pre><code>[TestFixture]
  public class BetweenIntTests
  {
      public class SampleEntityInt
      {
          public int SampleSearchKey { get; set; }
      }
  
      private IQueryable&lt;SampleEntityInt&gt; BuildSampleEntityInt(params int[] values)
      {
          return values.Select(
                 value =&gt; 
                 new SampleEntityInt() { SampleSearchKey = value }).AsQueryable();
      }
  
      [Test]
      public void BetweenIntInclusive()
      {
          var sampleData = BuildSampleEntityInt(1, 3, 10, 11, 12, 15);
          var query = sampleData.Between(s =&gt; s.SampleSearchKey, 3, 10);
          Assert.AreEqual(2, query.Count());
      }
  
      [Test]
      public void BetweenIntNotInclusive()
      {
          var sampleData = BuildSampleEntityInt(1, 3, 10, 11, 12, 15);
          var query = sampleData.Between(s =&gt; s.SampleSearchKey, 2, 11, false);
          Assert.AreEqual(2, query.Count());
      }
  }
  </code></pre>
  
  <p><strong>Non Working Test of the above using a "string" key</strong></p>
  
  <hr>
  
  <pre><code>[TestFixture]
  public class BetweenStringsTests
  {
  
      public class SampleEntityString
      {
          public string SampleSearchKey { get; set; }
      }
  
      private IQueryable&lt;SampleEntityString&gt; BuildSampleEntityString(params int[] values)
      {
          return values.Select(
                 value =&gt;
                 new SampleEntityString() {SampleSearchKey = value.ToString() }).AsQueryable();
      }
  
      [Test]
      public void BetweenStringInclusive()
      {
          var sampleData = BuildSampleEntityString(1, 3, 10, 11, 12, 15);
          var query = sampleData.Between(s =&gt; s.SampleSearchKey, "3", "10");
          Assert.AreEqual(2, query.Count());
      }
  
      [Test]
      public void BetweenStringNotInclusive()
      {
          var sampleData = BuildSampleEntityString(1, 3, 10, 11, 12, 15);
          var query = sampleData.Between(s =&gt; s.SampleSearchKey, "2", "11", false);
          Assert.AreEqual(2, query.Count());
      }
  }
  </code></pre>