This morning I saw a question ( Querying my model for a range of values ), which I seemed to answer ( https://stackoverflow.com/a/3/4/4/ ), but the whole situation there looked into my interest in a more generalized solution.
I was hoping to use Jon Skeet to implement Between , which will work with string keys in a non-SQL environment, but it seems that the fact that the string does not implement GreaterThan , LessThan , GreaterThanOrEqual , LessThanOrEqual operators prevent Linq from being able to create Expression trees needed for this.
I understand that to accomplish this task, you can simply execute a query using CompareTo methods, but I really like the elegance of the query.Between(v=>v.StringKey, "abc", "hjk") expression query.Between(v=>v.StringKey, "abc", "hjk") .
I looked at the System.Linq.Expression assembly and saw that it was looking for a method called op_GreaterThan, for example, for the GreaterThan operation, but I donβt know
- I can implement this for a string (knowing that I cannot extend the actual '>' operator for strings)
- How to get the correct method signature.
I created the following example and tests that show that the Inter Between method does not work with a string key.
It would be pretty elegant if it were implemented for string keys. Anyone have any suggestions or ideas on how to do this?
Between operator from Jon Skeet, with flag added
public static class BetweenExtension { public static IQueryable<TSource> Between<TSource, TKey>( this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector, TKey low, TKey high, bool inclusive = true) where TKey : IComparable<TKey> { 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<Func<TSource, bool>>( and, keySelector.Parameters); return source.Where(lambda); } }
Work test above using the "int" key
[TestFixture] public class BetweenIntTests { public class SampleEntityInt { public int SampleSearchKey { get; set; } } private IQueryable<SampleEntityInt> BuildSampleEntityInt(params int[] values) { return values.Select( value => new SampleEntityInt() { SampleSearchKey = value }).AsQueryable(); } [Test] public void BetweenIntInclusive() { var sampleData = BuildSampleEntityInt(1, 3, 10, 11, 12, 15); var query = sampleData.Between(s => 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 => s.SampleSearchKey, 2, 11, false); Assert.AreEqual(2, query.Count()); } }
Idle test above using the row key
[TestFixture] public class BetweenStringsTests { public class SampleEntityString { public string SampleSearchKey { get; set; } } private IQueryable<SampleEntityString> BuildSampleEntityString(params int[] values) { return values.Select( value => new SampleEntityString() {SampleSearchKey = value.ToString() }).AsQueryable(); } [Test] public void BetweenStringInclusive() { var sampleData = BuildSampleEntityString(1, 3, 10, 11, 12, 15); var query = sampleData.Between(s => 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 => s.SampleSearchKey, "2", "11", false); Assert.AreEqual(2, query.Count()); } }