From: <ste...@us...> - 2010-06-21 14:57:39
|
Revision: 4993 http://nhibernate.svn.sourceforge.net/nhibernate/?rev=4993&view=rev Author: steverstrong Date: 2010-06-21 14:57:32 +0000 (Mon, 21 Jun 2010) Log Message: ----------- Improved support for projections, can now do things like from u in users select SomeCSharpFunction(u.username) Modified Paths: -------------- trunk/nhibernate/src/NHibernate/Linq/Functions/FunctionRegistry.cs trunk/nhibernate/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs trunk/nhibernate/src/NHibernate/Linq/Visitors/SelectClauseVisitor.cs trunk/nhibernate/src/NHibernate.Test/Linq/ProjectionsTests.cs Modified: trunk/nhibernate/src/NHibernate/Linq/Functions/FunctionRegistry.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Linq/Functions/FunctionRegistry.cs 2010-06-19 15:00:19 UTC (rev 4992) +++ trunk/nhibernate/src/NHibernate/Linq/Functions/FunctionRegistry.cs 2010-06-21 14:57:32 UTC (rev 4993) @@ -28,56 +28,85 @@ { public class FunctionRegistry { - public static FunctionRegistry Initialise() - { - var registry = new FunctionRegistry(); + public static readonly FunctionRegistry Instance = new FunctionRegistry(); - // TODO - could use reflection here - registry.Register(new QueryableGenerator()); - registry.Register(new StringGenerator()); - registry.Register(new DateTimeGenerator()); - registry.Register(new ICollectionGenerator()); - - return registry; - } - private readonly Dictionary<MethodInfo, IHqlGeneratorForMethod> _registeredMethods = new Dictionary<MethodInfo, IHqlGeneratorForMethod>(); private readonly Dictionary<MemberInfo, IHqlGeneratorForProperty> _registeredProperties = new Dictionary<MemberInfo, IHqlGeneratorForProperty>(); private readonly List<IHqlGeneratorForType> _typeGenerators = new List<IHqlGeneratorForType>(); + private FunctionRegistry() + { + // TODO - could use reflection here + Register(new QueryableGenerator()); + Register(new StringGenerator()); + Register(new DateTimeGenerator()); + Register(new ICollectionGenerator()); + } + public IHqlGeneratorForMethod GetMethodGenerator(MethodInfo method) { IHqlGeneratorForMethod methodGenerator; + if (!TryGetMethodGenerator(method, out methodGenerator)) + { + throw new NotSupportedException(method.ToString()); + } + + return methodGenerator; + } + + public bool TryGetMethodGenerator(MethodInfo method, out IHqlGeneratorForMethod methodGenerator) + { if (method.IsGenericMethod) { method = method.GetGenericMethodDefinition(); } - if (_registeredMethods.TryGetValue(method, out methodGenerator)) + if (GetRegisteredMethodGenerator(method, out methodGenerator)) return true; + + // No method generator registered. Look to see if it's a standard LinqExtensionMethod + if (GetStandardLinqExtensionMethodGenerator(method, out methodGenerator)) return true; + + // Not that either. Let's query each type generator to see if it can handle it + if (GetMethodGeneratorForType(method, out methodGenerator)) return true; + + return false; + } + + private bool GetMethodGeneratorForType(MethodInfo method, out IHqlGeneratorForMethod methodGenerator) + { + methodGenerator = null; + + foreach (var typeGenerator in _typeGenerators.Where(typeGenerator => typeGenerator.SupportsMethod(method))) { - return methodGenerator; + methodGenerator = typeGenerator.GetMethodGenerator(method); + return true; } + return false; + } - // No method generator registered. Look to see if it's a standard LinqExtensionMethod - var attr = method.GetCustomAttributes(typeof (LinqExtensionMethodAttribute), false); + private bool GetStandardLinqExtensionMethodGenerator(MethodInfo method, out IHqlGeneratorForMethod methodGenerator) + { + methodGenerator = null; + + var attr = method.GetCustomAttributes(typeof(LinqExtensionMethodAttribute), false); + if (attr.Length == 1) { // It is - // TODO - cache this? Is it worth it? - return new HqlGeneratorForExtensionMethod((LinqExtensionMethodAttribute) attr[0], method); + methodGenerator = new HqlGeneratorForExtensionMethod((LinqExtensionMethodAttribute)attr[0], method); + return true; } + return false; + } - // Not that either. Let's query each type generator to see if it can handle it - foreach (var typeGenerator in _typeGenerators) + private bool GetRegisteredMethodGenerator(MethodInfo method, out IHqlGeneratorForMethod methodGenerator) + { + if (_registeredMethods.TryGetValue(method, out methodGenerator)) { - if (typeGenerator.SupportsMethod(method)) - { - return typeGenerator.GetMethodGenerator(method); - } + return true; } - - throw new NotSupportedException(method.ToString()); + return false; } public IHqlGeneratorForProperty GetPropertyGenerator(MemberInfo member) Modified: trunk/nhibernate/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs 2010-06-19 15:00:19 UTC (rev 4992) +++ trunk/nhibernate/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs 2010-06-21 14:57:32 UTC (rev 4993) @@ -12,7 +12,7 @@ { private readonly HqlTreeBuilder _hqlTreeBuilder; private readonly VisitorParameters _parameters; - static private readonly FunctionRegistry FunctionRegistry = FunctionRegistry.Initialise(); + static private readonly FunctionRegistry FunctionRegistry = FunctionRegistry.Instance; public static HqlTreeNode Visit(Expression expression, VisitorParameters parameters) { Modified: trunk/nhibernate/src/NHibernate/Linq/Visitors/SelectClauseVisitor.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Linq/Visitors/SelectClauseVisitor.cs 2010-06-19 15:00:19 UTC (rev 4992) +++ trunk/nhibernate/src/NHibernate/Linq/Visitors/SelectClauseVisitor.cs 2010-06-21 14:57:32 UTC (rev 4993) @@ -3,12 +3,15 @@ using System.Linq.Expressions; using NHibernate.Hql.Ast; using NHibernate.Linq.Expressions; +using NHibernate.Linq.Functions; using Remotion.Data.Linq.Parsing; namespace NHibernate.Linq.Visitors { public class SelectClauseVisitor : ExpressionTreeVisitor { + static private readonly FunctionRegistry FunctionRegistry = FunctionRegistry.Instance; + private HashSet<Expression> _hqlNodes; private readonly ParameterExpression _inputParameter; private readonly VisitorParameters _parameters; @@ -69,7 +72,24 @@ private static bool CanBeEvaluatedInHqlSelectStatement(Expression expression) { - return (expression.NodeType != ExpressionType.MemberInit) && (expression.NodeType != ExpressionType.New); + if ((expression.NodeType == ExpressionType.MemberInit) || (expression.NodeType == ExpressionType.New) || (expression.NodeType == ExpressionType.Constant)) + { + // Hql can't do New or Member Init + return false; + } + + if (expression.NodeType == ExpressionType.Call) + { + // Depends if it's in the function registry + IHqlGeneratorForMethod methodGenerator; + if (!FunctionRegistry.TryGetMethodGenerator(((MethodCallExpression) expression).Method, out methodGenerator)) + { + return false; + } + } + + // Assume all is good + return true; } private static bool CanBeEvaluatedInHqlStatementShortcut(Expression expression) Modified: trunk/nhibernate/src/NHibernate.Test/Linq/ProjectionsTests.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/Linq/ProjectionsTests.cs 2010-06-19 15:00:19 UTC (rev 4992) +++ trunk/nhibernate/src/NHibernate.Test/Linq/ProjectionsTests.cs 2010-06-21 14:57:32 UTC (rev 4993) @@ -147,5 +147,55 @@ Assert.AreEqual(3, query.Intersect(new[] { "ayende", "rahien", "nhibernate" }) .ToList().Count); } + + [Test] + public void CanCallLocalMethodsInSelect() + { + var query = ( + from user in db.Users + orderby user.Id + select FormatName(user.Name, user.LastLoginDate) + ).ToList(); + + Assert.AreEqual(3, query.Count); + Assert.IsTrue(query[0].StartsWith("User ayende logged in at")); + Assert.IsTrue(query[1].StartsWith("User rahien logged in at")); + Assert.IsTrue(query[2].StartsWith("User nhibernate logged in at")); + } + + [Test] + public void CanCallLocalMethodsInAnonymousTypeInSelect() + { + var query = ( + from user in db.Users + orderby user.Id + select new {Title = FormatName(user.Name, user.LastLoginDate)} + ).ToList(); + + Assert.AreEqual(3, query.Count); + Assert.IsTrue(query[0].Title.StartsWith("User ayende logged in at")); + Assert.IsTrue(query[1].Title.StartsWith("User rahien logged in at")); + Assert.IsTrue(query[2].Title.StartsWith("User nhibernate logged in at")); + } + + [Test] + public void CanPerformStringOperationsInSelect() + { + var query = ( + from user in db.Users + orderby user.Id + select new { Title = "User " + user.Name + " logged in at " + user.LastLoginDate } + ).ToList(); + + Assert.AreEqual(3, query.Count); + Assert.IsTrue(query[0].Title.StartsWith("User ayende logged in at")); + Assert.IsTrue(query[1].Title.StartsWith("User rahien logged in at")); + Assert.IsTrue(query[2].Title.StartsWith("User nhibernate logged in at")); + } + + private string FormatName(string name, DateTime? lastLoginDate) + { + return string.Format("User {0} logged in at {1}", name, lastLoginDate); + } } } \ No newline at end of file This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |