Skip to content

Instantly share code, notes, and snippets.

@robertdean
Forked from mattwarren/gist:2575701
Created January 25, 2013 19:08
Show Gist options
  • Save robertdean/4636955 to your computer and use it in GitHub Desktop.
Save robertdean/4636955 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
namespace BetterFacetedSearchAPI
{
class Program
{
static void Main(string[] args)
{
var facetList = new List<Facet>
{
new FacetRange<TestClass, DateTime>
{
Field = x => x.Date,
Ranges =
{
Range.CreateLessThan(new DateTime(2008, 1, 1)), // NULL TO 1/1/2008 (less than)
Range.CreateGreaterThan(new DateTime(2008, 1, 22)), // 1/1/2008 TO NULL (greater than)
Range.Create(minValue: new DateTime(2008, 1, 1), maxValue: new DateTime(2008, 1, 2))
}
},
//Would like to be able to write something like this, but not sure if it's possible
//FacetRange.Create(x => x.Date, ranges:
// {
// Range.CreateLessThan(new DateTime(2008, 1, 1)), // NULL TO 1/1/2008 (less than)
// Range.CreateGreaterThan(new DateTime(2008, 1, 22)), // 1/1/2008 TO NULL (greater than)
// Range.Create(minValue: new DateTime(2008, 1, 1), maxValue: new DateTime(2008, 1, 2))
// }),
new FacetRange<TestClass, int>
{
Field = x => x.Number,
Ranges =
{
Range.CreateLessThan(1001), // [NULL TO 1001] (less than)
Range.CreateGreaterThan(500), // [500 TO NULL] (greater than)
Range.Create(minValue: 10, maxValue: 100)
}
},
new FacetRange<TestClass, string>
{
Field = x => x.Id,
Ranges =
{
Range.CreateLessThan("ddddd"), // [NULL TO ddddd] (less than)
Range.CreateGreaterThan("eeeee"), // [eeeee TO NULL] (greater than)
Range.Create(minValue: "aa", maxValue: "bb")
}
},
new FacetRange<TestClass, string> { Field = x => x.Id }
};
Facet firstItem = facetList.First();
//Should print out "Date_Range"
Console.WriteLine(firstItem.Name);
//The expected ranges are
// [NULL TO 20080101000000]
// [20080122000000 TO NULL]
// [20080101000000 TO 20080102000000]
Console.WriteLine(String.Join("\n", firstItem.Ranges));
}
}
class TestClass
{
public string Id { get; set; }
public DateTime Date { get; set; }
public int Number { get; set; }
}
//Having this helper class doesn't seem to help with the type inference
public static class FacetRange
{
public static FacetRange<T, U> Create<T, U>(Expression<Func<T, U>> field, List<Range<U>> ranges)
{
return new FacetRange<T, U>
{
Field = field,
Ranges = ranges
};
}
}
public class FacetRange<T, U>
{
public Expression<Func<T, U>> Field { get; set; }
public List<Range<U>> Ranges { get; set; }
public FacetRange()
{
Ranges = new List<Range<U>>();
}
private string GetFieldName()
{
if (Field.Body.NodeType == ExpressionType.MemberAccess)
{
//http://stackoverflow.com/questions/3049825/given-a-member-access-lambda-expression-convert-it-to-a-specific-string-represe
//This only works for 1 level, i.e. x => x.Date, NOT for x => x.Date.Now
MemberExpression body = (Field.Body.NodeType == ExpressionType.Convert)
? (MemberExpression)((UnaryExpression)Field.Body).Operand
: (MemberExpression)Field.Body;
return body.Member.Name;
}
throw new InvalidOperationException(String.Format("Unable to parse Expression: {0} - {1}", Field, Field.Body));
}
public static implicit operator Facet(FacetRange<T, U> facetRange)
{
return facetRange.ToSimpleFacet();
}
private Facet ToSimpleFacet()
{
var mode = (Ranges.Count == 0) ? FacetMode.Default : FacetMode.Ranges;
var nameSuffix = (mode == FacetMode.Ranges && typeof(U).FullName != "System.String")
? "_Range"
: String.Empty;
return new Facet
{
Name = GetFieldName() + nameSuffix,
Mode = mode,
Ranges = (Ranges == null)
? new List<string>()
: Ranges.Select(x => x.ToString()).ToList()
};
}
}
//This is just here to help with Genetic type inference (makes code nicer to write)
public static class Range
{
public static Range<T> Create<T>(T minValue, T maxValue)
{
return new Range<T>(minValue, maxValue);
}
public static Range<T> CreateLessThan<T>(T value)
{
return new Range<T>(value, isLessThanRange: true);
}
public static Range<T> CreateGreaterThan<T>(T value)
{
return new Range<T>(value, isLessThanRange: false);
}
}
public class Range<T>
{
private readonly T _minValue;
private readonly bool _hasMinValue;
private readonly T _maxValue;
private readonly bool _hasMaxValue;
public Range(T minValue, T maxValue)
{
//do some null checks???
_minValue = minValue;
_hasMinValue = true;
_maxValue = maxValue;
_hasMaxValue = true;
}
public Range(T singleValue, bool isLessThanRange)
{
if (isLessThanRange)
{
//[NULL to value], i.e. results < value
_hasMinValue = false;
_hasMaxValue = true;
_maxValue = singleValue;
}
else
{
//[value to NULL]. i.e. results > value
_hasMinValue = true;
_hasMaxValue = false;
_minValue = singleValue;
}
}
public override string ToString()
{
//Once this code is in RavenDB, the calls to GetValue(..) below will be replaced with calls to NumberUtil.NumberToString(..)
//see https://github.com/ayende/ravendb/blob/master/Raven.Abstractions/Indexing/NumberUtil.cs#L14
switch (typeof(T).FullName)
{
//The nullable stuff here it a bit wierd, but it helps with trying to cast Value types
case "System.DateTime":
return ToStringHelper(GetValue(_minValue as DateTime?), GetValue(_maxValue as DateTime?));
case "System.Int32":
return ToStringHelper(GetValue(_minValue as Int32?), GetValue(_maxValue as Int32?));
case "System.Int64":
return ToStringHelper(GetValue(_minValue as Int64?), GetValue(_maxValue as Int64?));
case "System.Single":
return ToStringHelper(GetValue(_minValue as Single?), GetValue(_maxValue as Single?));
case "System.Double":
return ToStringHelper(GetValue(_minValue as Double?), GetValue(_maxValue as Double?));
case "System.String":
return ToStringHelper(_minValue == null ? String.Empty : _minValue.ToString(),
_maxValue == null ? String.Empty : _maxValue.ToString());
}
throw new InvalidOperationException("Unable to parse the given type " + typeof (T).Name + ", into a facet range!!! ");
}
private string ToStringHelper(string minValueAsText, string maxValueAsText)
{
if (_hasMinValue && _hasMaxValue)
return String.Format("[{0} TO {1}]", minValueAsText, maxValueAsText);
else if (_hasMinValue)
return String.Format("[{0} TO NULL]", minValueAsText);
else if (_hasMaxValue)
return String.Format("[NULL TO {0}]", maxValueAsText);
throw new InvalidOperationException("Invalid combination of min/max values");
}
private string GetValue(DateTime? value)
{
return value.Value.ToString("yyyyMMddHHmmss");
}
private string GetValue(Int32? value) //int
{
return string.Format("0x{0:X8}", value.Value);
}
private string GetValue(Int64? value) //long
{
return string.Format("0x{0:X16}", value.Value);
}
private string GetValue(Single? value) //float
{
return "Fx" + value.Value.ToString("G", CultureInfo.InvariantCulture);
}
private string GetValue(Double? value) //double
{
return "Dx" + value.Value.ToString("G", CultureInfo.InvariantCulture);
}
}
public class Facet
{
public string Name { get; set; }
public FacetMode Mode { get; set; }
public List<string> Ranges { get; set; }
}
public enum FacetMode
{
Default,
Ranges
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment