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. |