|
From: <ric...@us...> - 2010-07-12 21:48:52
|
Revision: 5004
http://nhibernate.svn.sourceforge.net/nhibernate/?rev=5004&view=rev
Author: ricbrown
Date: 2010-07-12 21:48:46 +0000 (Mon, 12 Jul 2010)
Log Message:
-----------
Added custom extension methods usable inside QueryOver expressions.
Modified Paths:
--------------
trunk/nhibernate/src/NHibernate/Impl/ExpressionProcessor.cs
trunk/nhibernate/src/NHibernate/NHibernate.csproj
trunk/nhibernate/src/NHibernate.Test/Criteria/Lambda/ExpressionProcessorFixture.cs
trunk/nhibernate/src/NHibernate.Test/Criteria/Lambda/QueryOverFixture.cs
trunk/nhibernate/src/NHibernate.Test/Criteria/Lambda/RestrictionsFixture.cs
Added Paths:
-----------
trunk/nhibernate/src/NHibernate/Criterion/RestrictionsExtensions.cs
Added: trunk/nhibernate/src/NHibernate/Criterion/RestrictionsExtensions.cs
===================================================================
--- trunk/nhibernate/src/NHibernate/Criterion/RestrictionsExtensions.cs (rev 0)
+++ trunk/nhibernate/src/NHibernate/Criterion/RestrictionsExtensions.cs 2010-07-12 21:48:46 UTC (rev 5004)
@@ -0,0 +1,152 @@
+using System;
+using System.Collections;
+using System.Linq.Expressions;
+using NHibernate.Impl;
+
+namespace NHibernate.Criterion
+{
+ public static class RestrictionExtensions
+ {
+ /// <summary>
+ /// Apply a "like" restriction in a QueryOver expression
+ /// Note: throws an exception outside of a QueryOver expression
+ /// </summary>
+ public static bool IsLike(this string projection, string comparison)
+ {
+ throw new Exception("Not to be used directly - use inside QueryOver expression");
+ }
+
+ /// <summary>
+ /// Apply a "like" restriction in a QueryOver expression
+ /// Note: throws an exception outside of a QueryOver expression
+ /// </summary>
+ public static bool IsLike(this string projection, string comparison, MatchMode matchMode)
+ {
+ throw new Exception("Not to be used directly - use inside QueryOver expression");
+ }
+
+ /// <summary>
+ /// Apply a "like" restriction in a QueryOver expression
+ /// Note: throws an exception outside of a QueryOver expression
+ /// </summary>
+ public static bool IsLike(this string projection, string comparison, MatchMode matchMode, char? escapeChar)
+ {
+ throw new Exception("Not to be used directly - use inside QueryOver expression");
+ }
+
+ /// <summary>
+ /// Apply a "like" restriction in a QueryOver expression
+ /// Note: throws an exception outside of a QueryOver expression
+ /// </summary>
+ public static bool IsInsensitiveLike(this string projection, string comparison)
+ {
+ throw new Exception("Not to be used directly - use inside QueryOver expression");
+ }
+
+ /// <summary>
+ /// Apply a "like" restriction in a QueryOver expression
+ /// Note: throws an exception outside of a QueryOver expression
+ /// </summary>
+ public static bool IsInsensitiveLike(this string projection, string comparison, MatchMode matchMode)
+ {
+ throw new Exception("Not to be used directly - use inside QueryOver expression");
+ }
+
+ /// <summary>
+ /// Apply an "in" constraint to the named property
+ /// Note: throws an exception outside of a QueryOver expression
+ /// </summary>
+ public static bool IsIn(this object projection, object[] values)
+ {
+ throw new Exception("Not to be used directly - use inside QueryOver expression");
+ }
+
+ /// <summary>
+ /// Apply an "in" constraint to the named property
+ /// Note: throws an exception outside of a QueryOver expression
+ /// </summary>
+ public static bool IsIn(this object projection, ICollection values)
+ {
+ throw new Exception("Not to be used directly - use inside QueryOver expression");
+ }
+
+ /// <summary>
+ /// Apply a "between" constraint to the named property
+ /// Note: throws an exception outside of a QueryOver expression
+ /// </summary>
+ public static RestrictionBetweenBuilder IsBetween(this object projection, object lo)
+ {
+ throw new Exception("Not to be used directly - use inside QueryOver expression");
+ }
+
+ public class RestrictionBetweenBuilder
+ {
+ public bool And(object hi)
+ {
+ throw new Exception("Not to be used directly - use inside QueryOver expression");
+ }
+ }
+
+ public static ICriterion ProcessIsLike(MethodCallExpression methodCallExpression)
+ {
+ string property = ExpressionProcessor.FindMemberExpression(methodCallExpression.Arguments[0]);
+ object value = ExpressionProcessor.FindValue(methodCallExpression.Arguments[1]);
+ return Restrictions.Like(property, value);
+ }
+
+ public static ICriterion ProcessIsLikeMatchMode(MethodCallExpression methodCallExpression)
+ {
+ string property = ExpressionProcessor.FindMemberExpression(methodCallExpression.Arguments[0]);
+ string value = (string)ExpressionProcessor.FindValue(methodCallExpression.Arguments[1]);
+ MatchMode matchMode = (MatchMode)ExpressionProcessor.FindValue(methodCallExpression.Arguments[2]);
+ return Restrictions.Like(property, value, matchMode);
+ }
+
+ public static ICriterion ProcessIsLikeMatchModeEscapeChar(MethodCallExpression methodCallExpression)
+ {
+ string property = ExpressionProcessor.FindMemberExpression(methodCallExpression.Arguments[0]);
+ string value = (string)ExpressionProcessor.FindValue(methodCallExpression.Arguments[1]);
+ MatchMode matchMode = (MatchMode)ExpressionProcessor.FindValue(methodCallExpression.Arguments[2]);
+ char? escapeChar = (char?)ExpressionProcessor.FindValue(methodCallExpression.Arguments[3]);
+ return Restrictions.Like(property, value, matchMode, escapeChar);
+ }
+
+ public static ICriterion ProcessIsInsensitiveLike(MethodCallExpression methodCallExpression)
+ {
+ string property = ExpressionProcessor.FindMemberExpression(methodCallExpression.Arguments[0]);
+ object value = ExpressionProcessor.FindValue(methodCallExpression.Arguments[1]);
+ return Restrictions.InsensitiveLike(property, value);
+ }
+
+ public static ICriterion ProcessIsInsensitiveLikeMatchMode(MethodCallExpression methodCallExpression)
+ {
+ string property = ExpressionProcessor.FindMemberExpression(methodCallExpression.Arguments[0]);
+ string value = (string)ExpressionProcessor.FindValue(methodCallExpression.Arguments[1]);
+ MatchMode matchMode = (MatchMode)ExpressionProcessor.FindValue(methodCallExpression.Arguments[2]);
+ return Restrictions.InsensitiveLike(property, value, matchMode);
+ }
+
+ public static ICriterion ProcessIsInArray(MethodCallExpression methodCallExpression)
+ {
+ string property = ExpressionProcessor.FindMemberExpression(methodCallExpression.Arguments[0]);
+ object[] values = (object[])ExpressionProcessor.FindValue(methodCallExpression.Arguments[1]);
+ return Restrictions.In(property, values);
+ }
+
+ public static ICriterion ProcessIsInCollection(MethodCallExpression methodCallExpression)
+ {
+ string property = ExpressionProcessor.FindMemberExpression(methodCallExpression.Arguments[0]);
+ ICollection values = (ICollection)ExpressionProcessor.FindValue(methodCallExpression.Arguments[1]);
+ return Restrictions.In(property, values);
+ }
+
+ public static ICriterion ProcessIsBetween(MethodCallExpression methodCallExpression)
+ {
+ MethodCallExpression betweenFunction = (MethodCallExpression)methodCallExpression.Object;
+ string property = ExpressionProcessor.FindMemberExpression(betweenFunction.Arguments[0]);
+ object lo = ExpressionProcessor.FindValue(betweenFunction.Arguments[1]);
+ object hi = ExpressionProcessor.FindValue(methodCallExpression.Arguments[0]);
+ return Restrictions.Between(property, lo, hi);
+ }
+ }
+}
Modified: trunk/nhibernate/src/NHibernate/Impl/ExpressionProcessor.cs
===================================================================
--- trunk/nhibernate/src/NHibernate/Impl/ExpressionProcessor.cs 2010-07-08 22:13:54 UTC (rev 5003)
+++ trunk/nhibernate/src/NHibernate/Impl/ExpressionProcessor.cs 2010-07-12 21:48:46 UTC (rev 5004)
@@ -34,6 +34,7 @@
private readonly static IDictionary<ExpressionType, Func<string, object, ICriterion>> _simpleExpressionCreators = null;
private readonly static IDictionary<ExpressionType, Func<string, string, ICriterion>> _propertyExpressionCreators = null;
private readonly static IDictionary<LambdaSubqueryType, IDictionary<ExpressionType, Func<string, DetachedCriteria, AbstractCriterion>>> _subqueryExpressionCreatorTypes = null;
+ private readonly static IDictionary<string, Func<MethodCallExpression, ICriterion>> _customMethodCallProcessors = null;
static ExpressionProcessor()
{
@@ -75,6 +76,16 @@
_subqueryExpressionCreatorTypes[LambdaSubqueryType.Some][ExpressionType.GreaterThanOrEqual] = Subqueries.PropertyGeSome;
_subqueryExpressionCreatorTypes[LambdaSubqueryType.Some][ExpressionType.LessThan] = Subqueries.PropertyLtSome;
_subqueryExpressionCreatorTypes[LambdaSubqueryType.Some][ExpressionType.LessThanOrEqual] = Subqueries.PropertyLeSome;
+
+ _customMethodCallProcessors = new Dictionary<string, Func<MethodCallExpression, ICriterion>>();
+ RegisterCustomMethodCall(() => RestrictionExtensions.IsLike("", ""), RestrictionExtensions.ProcessIsLike);
+ RegisterCustomMethodCall(() => RestrictionExtensions.IsLike("", "", null), RestrictionExtensions.ProcessIsLikeMatchMode);
+ RegisterCustomMethodCall(() => RestrictionExtensions.IsLike("", "", null, null), RestrictionExtensions.ProcessIsLikeMatchModeEscapeChar);
+ RegisterCustomMethodCall(() => RestrictionExtensions.IsInsensitiveLike("", ""), RestrictionExtensions.ProcessIsInsensitiveLike);
+ RegisterCustomMethodCall(() => RestrictionExtensions.IsInsensitiveLike("", "", null), RestrictionExtensions.ProcessIsInsensitiveLikeMatchMode);
+ RegisterCustomMethodCall(() => RestrictionExtensions.IsIn(null, new object[0]), RestrictionExtensions.ProcessIsInArray);
+ RegisterCustomMethodCall(() => RestrictionExtensions.IsIn(null, new List<object>()), RestrictionExtensions.ProcessIsInCollection);
+ RegisterCustomMethodCall(() => RestrictionExtensions.IsBetween(null, null).And(null), RestrictionExtensions.ProcessIsBetween);
}
private static ICriterion Eq(string propertyName, object value)
@@ -110,6 +121,16 @@
}
/// <summary>
+ /// Invoke the expression to extract its runtime value
+ /// </summary>
+ public static object FindValue(Expression expression)
+ {
+ var valueExpression = Expression.Lambda(expression).Compile();
+ object value = valueExpression.DynamicInvoke();
+ return value;
+ }
+
+ /// <summary>
/// Retrieves the name of the property from a member expression
/// </summary>
/// <param name="expression">An expression tree that can contain either a member, or a conversion from a member.
@@ -299,8 +320,7 @@
string property = FindMemberExpression(be.Left);
System.Type propertyType = FindMemberType(be.Left);
- var valueExpression = Expression.Lambda(be.Right).Compile();
- object value = valueExpression.DynamicInvoke();
+ object value = FindValue(be.Right);
value = ConvertType(value, propertyType);
if (value == null)
@@ -395,12 +415,39 @@
if (unaryExpression.NodeType != ExpressionType.Not)
throw new Exception("Cannot interpret member from " + expression.ToString());
- return Restrictions.Eq(FindMemberExpression(unaryExpression.Operand), false);
+ if (IsMemberExpression(unaryExpression.Operand))
+ return Restrictions.Eq(FindMemberExpression(unaryExpression.Operand), false);
+ else
+ return Restrictions.Not(ProcessExpression(unaryExpression.Operand));
}
+ if (expression is MethodCallExpression)
+ {
+ MethodCallExpression methodCallExpression = (MethodCallExpression)expression;
+ return ProcessCustomMethodCall(methodCallExpression);
+ }
+
throw new Exception("Could not determine member type from " + expression.ToString());
}
+ private static string Signature(MethodInfo methodInfo)
+ {
+ return methodInfo.DeclaringType.FullName
+ + ":" + methodInfo.ToString();
+ }
+
+ private static ICriterion ProcessCustomMethodCall(MethodCallExpression methodCallExpression)
+ {
+ string signature = Signature(methodCallExpression.Method);
+
+ if (!_customMethodCallProcessors.ContainsKey(signature))
+ throw new Exception("Unrecognised method call: " + signature);
+
+ Func<MethodCallExpression, ICriterion> customMethodCallProcessor = _customMethodCallProcessors[signature];
+ ICriterion criterion = customMethodCallProcessor(methodCallExpression);
+ return criterion;
+ }
+
private static ICriterion ProcessExpression(Expression expression)
{
if (expression is BinaryExpression)
@@ -524,6 +571,18 @@
return criterion;
}
+ /// <summary>
+ /// Register a custom method for use in a QueryOver expression
+ /// </summary>
+ /// <param name="function">Lambda expression demonstrating call of custom method</param>
+ /// <param name="functionProcessor">function to convert MethodCallExpression to ICriterion</param>
+ public static void RegisterCustomMethodCall(Expression<Func<bool>> function, Func<MethodCallExpression, ICriterion> functionProcessor)
+ {
+ MethodCallExpression functionExpression = (MethodCallExpression)function.Body;
+ string signature = Signature(functionExpression.Method);
+ _customMethodCallProcessors.Add(signature, functionProcessor);
+ }
+
}
}
Modified: trunk/nhibernate/src/NHibernate/NHibernate.csproj
===================================================================
--- trunk/nhibernate/src/NHibernate/NHibernate.csproj 2010-07-08 22:13:54 UTC (rev 5003)
+++ trunk/nhibernate/src/NHibernate/NHibernate.csproj 2010-07-12 21:48:46 UTC (rev 5004)
@@ -580,6 +580,7 @@
<Compile Include="Criterion\NullSubqueryExpression.cs" />
<Compile Include="Criterion\QueryOverBuilderExtensions.cs" />
<Compile Include="Criterion\ProjectionsExtensions.cs" />
+ <Compile Include="Criterion\RestrictionsExtensions.cs" />
<Compile Include="Dialect\MsSql2008Dialect.cs" />
<Compile Include="Dialect\InformixDialect0940.cs" />
<Compile Include="Dialect\InformixDialect1000.cs" />
Modified: trunk/nhibernate/src/NHibernate.Test/Criteria/Lambda/ExpressionProcessorFixture.cs
===================================================================
--- trunk/nhibernate/src/NHibernate.Test/Criteria/Lambda/ExpressionProcessorFixture.cs 2010-07-08 22:13:54 UTC (rev 5003)
+++ trunk/nhibernate/src/NHibernate.Test/Criteria/Lambda/ExpressionProcessorFixture.cs 2010-07-12 21:48:46 UTC (rev 5004)
@@ -147,6 +147,14 @@
Assert.AreEqual(before.ToString(), after.ToString());
}
+ [Test]
+ public void TestEvaluateRestrictionExtension()
+ {
+ ICriterion before = Restrictions.Like("Name", "%test%");
+ ICriterion after = ExpressionProcessor.ProcessExpression<Person>(p => p.Name.IsLike("%test%"));
+ Assert.AreEqual(before.ToString(), after.ToString());
+ }
+
}
}
Modified: trunk/nhibernate/src/NHibernate.Test/Criteria/Lambda/QueryOverFixture.cs
===================================================================
--- trunk/nhibernate/src/NHibernate.Test/Criteria/Lambda/QueryOverFixture.cs 2010-07-08 22:13:54 UTC (rev 5003)
+++ trunk/nhibernate/src/NHibernate.Test/Criteria/Lambda/QueryOverFixture.cs 2010-07-12 21:48:46 UTC (rev 5004)
@@ -101,6 +101,23 @@
}
[Test]
+ public void CustomMethodExpression()
+ {
+ ICriteria expected =
+ CreateTestCriteria(typeof(Person), "personAlias")
+ .Add(Restrictions.Or(
+ Restrictions.Not(Restrictions.Eq("Name", "test name")),
+ Restrictions.Not(Restrictions.Like("personAlias.Name", "%test%"))));
+
+ Person personAlias = null;
+ IQueryOver<Person> actual =
+ CreateTestQueryOver<Person>(() => personAlias)
+ .Where(p => !(p.Name == "test name") || !personAlias.Name.IsLike("%test%"));
+
+ AssertCriteriaAreEqual(expected, actual);
+ }
+
+ [Test]
public void Negation()
{
ICriteria expected =
Modified: trunk/nhibernate/src/NHibernate.Test/Criteria/Lambda/RestrictionsFixture.cs
===================================================================
--- trunk/nhibernate/src/NHibernate.Test/Criteria/Lambda/RestrictionsFixture.cs 2010-07-08 22:13:54 UTC (rev 5003)
+++ trunk/nhibernate/src/NHibernate.Test/Criteria/Lambda/RestrictionsFixture.cs 2010-07-12 21:48:46 UTC (rev 5004)
@@ -221,6 +221,34 @@
AssertCriteriaAreEqual(expected, actual);
}
+ [Test]
+ public void RestrictionsExtensions()
+ {
+ ICriteria expected =
+ CreateTestCriteria(typeof(Person))
+ .Add(Restrictions.Like("Name", "%test%"))
+ .Add(Restrictions.Like("Name", "test", MatchMode.End))
+ .Add(Restrictions.Like("Name", "test", MatchMode.Start, '?'))
+ .Add(Restrictions.InsensitiveLike("Name", "%test%"))
+ .Add(Restrictions.InsensitiveLike("Name", "test", MatchMode.Anywhere))
+ .Add(Restrictions.In("Name", new string[] { "name1", "name2" }))
+ .Add(Restrictions.In("Name", new ArrayList() { "name3", "name4" }))
+ .Add(Restrictions.Between("Age", 10, 20));
+
+ IQueryOver<Person> actual =
+ CreateTestQueryOver<Person>()
+ .Where(p => p.Name.IsLike("%test%"))
+ .And(p => p.Name.IsLike("test", MatchMode.End))
+ .And(p => p.Name.IsLike("test", MatchMode.Start, '?'))
+ .And(p => p.Name.IsInsensitiveLike("%test%"))
+ .And(p => p.Name.IsInsensitiveLike("test", MatchMode.Anywhere))
+ .And(p => p.Name.IsIn(new string[] { "name1", "name2" }))
+ .And(p => p.Name.IsIn(new ArrayList() { "name3", "name4" }))
+ .And(p => p.Age.IsBetween(10).And(20));
+
+ AssertCriteriaAreEqual(expected, actual);
+ }
+
}
}
\ No newline at end of file
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|