PATCH: [NH1394][NH1393] Projections in Aggregates and Projections in Order Clause

"Tuna Toksöz" <[email protected]> Fri, 25 Jul 2008 00:07:09 +0300
Newsgroups gmane.comp.windows.dotnet.nhibernate.devel
Message-ID <[email protected]>
Hello everybody

I've prepared a cummulative patch with tests for the issues
http://jira.nhibernate.org/browse/NH-1393 and
http://jira.nhibernate.org/browse/NH-1394. I'll also add those to jira.
Please review and inform me if it is not good.



*I am finally posting this to right place!


PS: Those features are almost mandatory for linq to nhibernate.
-- 
Tuna Toksöz

Typos included to enhance the reader's attention ...

-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/

_______________________________________________
Nhibernate-development mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/nhibernate-development
cummulative_nh1393_nh1394.patch (application/octet-stream, 28 KB)
Index: nhibernate/src/NHibernate.Test/ExpressionTest/Projection/ProjectionFixture.cs
===================================================================
--- nhibernate/src/NHibernate.Test/ExpressionTest/Projection/ProjectionFixture.cs	(revision 3660)
+++ nhibernate/src/NHibernate.Test/ExpressionTest/Projection/ProjectionFixture.cs	(working copy)
@@ -1,6 +1,7 @@
 using NHibernate.DomainModel;
 using NHibernate.Criterion;
 using NHibernate.SqlCommand;
+using NHibernate.SqlTypes;
 using NHibernate.Type;
 using NUnit.Framework;
 
@@ -29,8 +30,11 @@
 			ISession session = factory.OpenSession();
 			IProjection expression = Projections.Avg("Pay");
 			CreateObjects(typeof(Simple), session);
+			IType nhType = NHibernateUtil.GuessType(typeof (double));
+			SqlType[] sqlTypes = nhType.SqlTypes(this.factoryImpl);
+			string sqlTypeString = factoryImpl.Dialect.GetCastTypeName(sqlTypes[0]);
 			SqlString sqlString = expression.ToSqlString(criteria, 0, criteriaQuery, new CollectionHelper.EmptyMapClass<string, IFilter>());
-			string expectedSql = "avg(sql_alias.Pay) as y0_";
+			string expectedSql = string.Format("avg(cast(sql_alias.Pay as {0})) as y0_",sqlTypeString);
 			CompareSqlStrings(sqlString, expectedSql, 0);
 			session.Close();
 		}
@@ -175,4 +179,4 @@
 			session.Close();
 		}
 	}
-}
\ No newline at end of file
+}
Index: nhibernate/src/NHibernate.Test/NHSpecificTest/NH1393/Fixture.cs
===================================================================
--- nhibernate/src/NHibernate.Test/NHSpecificTest/NH1393/Fixture.cs	(revision 0)
+++ nhibernate/src/NHibernate.Test/NHSpecificTest/NH1393/Fixture.cs	(revision 0)
@@ -0,0 +1,124 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+using NHibernate.Criterion;
+using NHibernate.Dialect.Function;
+using NHibernate.Transform;
+using NUnit.Framework;
+
+
+namespace NHibernate.Test.NHSpecificTest.NH1393
+{
+	[TestFixture]
+	public class Fixture : BugTestCase
+	{
+		public override string BugNumber
+		{
+			get { return "NH1393"; }
+		}
+
+		protected override void OnTearDown()
+		{
+			base.OnTearDown();
+			using (ISession session = OpenSession())
+			{
+				using (ITransaction tx = session.BeginTransaction())
+				{
+					session.Delete("from Person");
+					tx.Commit();
+				}
+			}
+		}
+
+		protected override void OnSetUp()
+		{
+			using (ISession s = OpenSession())
+			{
+				using (ITransaction tx = s.BeginTransaction())
+				{
+					Person e1 = new Person("Joe", 10, 9);
+					Person e2 = new Person("Sally", 100, 8);
+					Person e3 = new Person("Tim", 20, 7); //20
+					Person e4 = new Person("Fred", 40, 40);
+					Person e5 = new Person("Mike", 50, 50);
+					s.Save(e1);
+					s.Save(e2);
+					s.Save(e3);
+					s.Save(e4);
+					s.Save(e5);
+					tx.Commit();
+				}
+			}
+		}
+
+
+		[Test]
+		public void CanSumProjectionOnSqlFunction()
+		{
+			using (ISession s = OpenSession())
+			{
+				ISQLFunction arithmaticAddition = new VarArgsSQLFunction("(", "+", ")");
+				ICriteria c = s.CreateCriteria(typeof (Person))
+				    .SetProjection(
+				    Projections.Sum(Projections.SqlFunction(arithmaticAddition,
+				                                            NHibernateUtil.GuessType(typeof (double)),
+				                                            Projections.Property("IQ"),
+				                                            Projections.Property("ShoeSize"))));
+				IList list = c.List();
+				Assert.AreEqual(334,list[0]);
+			}
+		}
+
+		[Test]
+		public void CanAvgProjectionOnSqlFunction()
+		{
+			using (ISession s = OpenSession())
+			{
+				ISQLFunction arithmaticAddition = new VarArgsSQLFunction("(", "+", ")");
+				ICriteria c = s.CreateCriteria(typeof(Person))
+					.SetProjection(
+					Projections.Avg(Projections.SqlFunction(arithmaticAddition,
+															NHibernateUtil.GuessType(typeof(double)),
+															Projections.Property("IQ"),
+															Projections.Property("ShoeSize"))));
+				IList list = c.List();
+				Assert.AreEqual( ((double)334)/5,list[0]);
+			}
+		}
+
+		[Test]
+		public void CanMinProjectionOnIdentityProjection()
+		{
+			using (ISession s = OpenSession())
+			{
+				ISQLFunction arithmaticAddition = new VarArgsSQLFunction("(", "+", ")");
+				ICriteria c = s.CreateCriteria(typeof(Person))
+					.SetProjection(
+					Projections.Min(Projections.SqlFunction(arithmaticAddition,
+															NHibernateUtil.GuessType(typeof(double)),
+															Projections.Property("IQ"),
+															Projections.Property("ShoeSize"))));
+				IList list = c.List();
+				Assert.AreEqual(19, list[0]);
+			}
+		}
+
+		[Test]
+		public void CanMaxProjectionOnIdentityProjection()
+		{
+			using (ISession s = OpenSession())
+			{
+				ISQLFunction arithmaticAddition = new VarArgsSQLFunction("(", "+", ")");
+				ICriteria c = s.CreateCriteria(typeof(Person))
+					.SetProjection(
+					Projections.Max(Projections.SqlFunction(arithmaticAddition,
+															NHibernateUtil.GuessType(typeof(double)),
+															Projections.Property("IQ"),
+															Projections.Property("ShoeSize"))));
+				IList list = c.List();
+				Assert.AreEqual(108, list[0]);
+			}
+		}
+	}
+}
\ No newline at end of file
Index: nhibernate/src/NHibernate.Test/NHSpecificTest/NH1393/Mappings.hbm.xml
===================================================================
--- nhibernate/src/NHibernate.Test/NHSpecificTest/NH1393/Mappings.hbm.xml	(revision 0)
+++ nhibernate/src/NHibernate.Test/NHSpecificTest/NH1393/Mappings.hbm.xml	(revision 0)
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
+	assembly="NHibernate.Test"
+	namespace="NHibernate.Test.NHSpecificTest.NH1393">
+
+	<class name="Person" lazy="false">
+		<id name="Id">
+			<generator class="native" />
+		</id>
+		<bag name="Pets" inverse="true" lazy="false" cascade="all-delete-orphan">
+			<key column="PersonId" />
+			<one-to-many class="Pet"/>
+			<filter name="ExampleFilter" condition=":WeightVal &gt;= Weight" />
+		</bag>
+		<property name="Name"/>
+		<property name="IQ"/>
+		<property name="ShoeSize"/>
+	</class>
+
+	<class name="Pet" lazy="false">
+		<id name="Id">
+			<generator class="native" />
+		</id>
+		<many-to-one name="Owner" column="PersonId" not-null="true"/>
+		<property name="Name"/>
+		<property name="Species"/>
+		<property name="Weight"/>
+	</class>
+
+	<filter-def name="ExampleFilter">
+		<filter-param name="WeightVal" type="int"/>
+	</filter-def>
+
+</hibernate-mapping>
\ No newline at end of file
Index: nhibernate/src/NHibernate.Test/NHSpecificTest/NH1393/Person.cs
===================================================================
--- nhibernate/src/NHibernate.Test/NHSpecificTest/NH1393/Person.cs	(revision 0)
+++ nhibernate/src/NHibernate.Test/NHSpecificTest/NH1393/Person.cs	(revision 0)
@@ -0,0 +1,111 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+using ArrayList=System.Collections.ArrayList;
+
+namespace NHibernate.Test.NHSpecificTest.NH1393
+{
+	public class Person
+	{
+		private int id;
+		private int iq;
+		private string name;
+		private System.Collections.IList pets;
+		private int shoeSize;
+
+		public Person()
+		{
+			Pets = new ArrayList();
+		}
+
+		public Person(string name, int iq, int shoeSize)
+		{
+			this.name = name;
+			this.iq = iq;
+			this.shoeSize = shoeSize;
+			Pets = new ArrayList();
+		}
+
+		public virtual int Id
+		{
+			get { return id; }
+			set { id = value; }
+		}
+
+		public virtual string Name
+		{
+			get { return name; }
+			set { name = value; }
+		}
+
+		public virtual int IQ
+		{
+			get { return iq; }
+			set { iq = value; }
+		}
+
+		public virtual int ShoeSize
+		{
+			get { return shoeSize; }
+			set { shoeSize = value; }
+		}
+
+		public virtual IList Pets
+		{
+			get { return pets; }
+			protected set { pets = value; }
+		}
+	}
+
+	public class Pet
+	{
+		private int id;
+		private string name;
+		private Person owner;
+		private string species;
+		private double weight;
+
+		public Pet()
+		{
+		}
+
+		public Pet(string name, string species, int weight, Person owner)
+		{
+			this.name = name;
+			this.species = species;
+			this.weight = weight;
+			this.owner = owner;
+		}
+
+		public virtual int Id
+		{
+			get { return id; }
+			set { id = value; }
+		}
+
+		public virtual string Name
+		{
+			get { return name; }
+			set { name = value; }
+		}
+
+		public virtual string Species
+		{
+			get { return species; }
+			set { species = value; }
+		}
+
+		public virtual double Weight
+		{
+			get { return weight; }
+			set { weight = value; }
+		}
+
+		public virtual Person Owner
+		{
+			get { return owner; }
+			set { owner = value; }
+		}
+	}
+}
Index: nhibernate/src/NHibernate.Test/NHSpecificTest/NH1394/Fixture.cs
===================================================================
--- nhibernate/src/NHibernate.Test/NHSpecificTest/NH1394/Fixture.cs	(revision 0)
+++ nhibernate/src/NHibernate.Test/NHSpecificTest/NH1394/Fixture.cs	(revision 0)
@@ -0,0 +1,166 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+using NHibernate.Criterion;
+using NHibernate.Dialect.Function;
+using NHibernate.Transform;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.NH1394
+{
+	[TestFixture]
+	public class Fixture: BugTestCase
+	{
+		public override string BugNumber
+		{
+			get { return "NH1394"; }
+		}
+
+		protected override void OnTearDown()
+		{
+			base.OnTearDown();
+			using(ISession session = OpenSession())
+			{
+				using(ITransaction tx = session.BeginTransaction())
+				{
+					session.Delete("from Person");
+					tx.Commit();
+				}
+			}
+		}
+
+		protected override void OnSetUp()
+		{
+			using (ISession s = OpenSession())
+			{
+				using (ITransaction tx = s.BeginTransaction())
+				{
+					Person e1 = new Person("Joe", 10, 9);
+					Person e2 = new Person("Sally", 100, 8);
+					Person e3 = new Person("Tim", 20, 7); //20
+					Person e4 = new Person("Fred", 40, 40);
+					Person e5 = new Person("Mike", 50, 50);
+					s.Save(e1);
+					s.Save(e2);
+					s.Save(e3);
+					s.Save(e4);
+					s.Save(e5);
+					Pet p0 = new Pet("Fido", "Dog", 25, e1);
+					Pet p1 = new Pet("Biff", "Dog", 9, e1);
+					Pet p2 = new Pet("Pasha", "Dog", 25, e2);
+					Pet p3 = new Pet("Lord", "Dog", 10, e2);
+					Pet p4 = new Pet("Max", "Dog", 25, e3);
+					Pet p5 = new Pet("Min", "Dog", 8, e3);
+					s.Save(p0);
+					s.Save(p1);
+					s.Save(p2);
+					s.Save(p3);
+					s.Save(p4);
+					s.Save(p5);
+					tx.Commit();
+				}
+			}
+		}
+
+
+		[Test]
+		public void CanOrderByPropertyProjection()
+		{
+			using (ISession s = OpenSession())
+			{
+				ICriteria c = s.CreateCriteria(typeof (Person))
+					.AddOrder(Order.Desc(Projections.Property("IQ")));
+				IList<Person> list= c.List<Person>();
+
+				for (int i = 0; i < list.Count-1; i++)
+				{
+					Assert.IsTrue(list[i].IQ >= list[i + 1].IQ);
+				}
+			}
+		}
+
+		[Test]
+		public void CanOrderBySubqueryProjection()
+		{
+			using (ISession s = OpenSession())
+			{
+				DetachedCriteria dc = DetachedCriteria.For<Person>("sub");
+				dc.CreateCriteria("Pets", "pets")
+					.SetProjection(Projections.Min("pets.Weight"))
+					.Add(Restrictions.EqProperty("this.Id", "sub.Id"));
+					
+				ICriteria c = s.CreateCriteria(typeof (Person))
+					.AddOrder(Order.Asc(Projections.SubQuery(dc)));
+				IList<Person> list = c.List<Person>();
+
+				Assert.AreEqual(list[2].Name,"Tim");
+				Assert.AreEqual(list[3].Name, "Joe");
+				Assert.AreEqual(list[4].Name, "Sally");
+			}
+		}
+
+		[Test]
+		public void CanOrderBySubqueryProjectionDesc()
+		{
+			using (ISession s = OpenSession())
+			{
+				DetachedCriteria dc = DetachedCriteria.For<Person>("sub");
+				dc.CreateCriteria("Pets", "pets")
+					.SetProjection(Projections.Min("pets.Weight"))
+					.Add(Restrictions.EqProperty("this.Id", "sub.Id"));
+
+				ICriteria c = s.CreateCriteria(typeof(Person))
+					.AddOrder(Order.Desc(Projections.SubQuery(dc)));
+				IList<Person> list = c.List<Person>();
+
+				Assert.AreEqual(list[2].Name, "Tim");
+				Assert.AreEqual(list[1].Name, "Joe");
+				Assert.AreEqual(list[0].Name, "Sally");
+			}
+		}
+
+		[Test]
+		public void CanOrderBySqlProjectionAsc()
+		{
+			using (ISession s = OpenSession())
+			{
+				ISQLFunction arithmaticAddition = new VarArgsSQLFunction("(", "+", ")");
+				ICriteria c = s.CreateCriteria(typeof(Person))
+					.AddOrder(Order.Asc(
+					Projections.SqlFunction(arithmaticAddition,
+					NHibernateUtil.GuessType(typeof(double)),
+					Projections.Property("IQ"),
+					Projections.Property("ShoeSize"))));
+				IList<Person> list = c.List<Person>();
+
+				for (int i = 0; i < list.Count - 1; i++)
+				{
+					Assert.IsTrue(list[i].IQ + list[i].ShoeSize <= list[i+1].IQ + list[i+1].ShoeSize);
+				}
+			}
+		}
+
+
+		[Test]
+		public void CanOrderBySqlProjectionDesc()
+		{
+			using (ISession s = OpenSession())
+			{
+				ISQLFunction arithmaticAddition = new VarArgsSQLFunction("(", "+", ")");
+				ICriteria c = s.CreateCriteria(typeof(Person))
+					.AddOrder(Order.Desc(
+					Projections.SqlFunction(arithmaticAddition,
+					NHibernateUtil.GuessType(typeof(double)),
+					Projections.Property("IQ"),
+					Projections.Property("ShoeSize"))));
+				IList<Person> list = c.List<Person>();
+
+				for (int i = 0; i < list.Count - 1; i++)
+				{
+					Assert.IsTrue(list[i].IQ + list[i].ShoeSize >= list[i + 1].IQ + list[i + 1].ShoeSize);
+				}
+			}
+		}
+	}
+}
\ No newline at end of file
Index: nhibernate/src/NHibernate.Test/NHSpecificTest/NH1394/Mappings.hbm.xml
===================================================================
--- nhibernate/src/NHibernate.Test/NHSpecificTest/NH1394/Mappings.hbm.xml	(revision 0)
+++ nhibernate/src/NHibernate.Test/NHSpecificTest/NH1394/Mappings.hbm.xml	(revision 0)
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
+	assembly="NHibernate.Test"
+	namespace="NHibernate.Test.NHSpecificTest.NH1394">
+
+	<class name="Person" lazy="false">
+		<id name="Id">
+			<generator class="native" />
+		</id>
+		<bag name="Pets" inverse="true" lazy="false" cascade="all-delete-orphan">
+			<key column="PersonId" />
+			<one-to-many class="Pet"/>
+			<filter name="ExampleFilter" condition=":WeightVal &gt;= Weight" />
+		</bag>
+		<property name="Name"/>
+		<property name="IQ"/>
+		<property name="ShoeSize"/>
+	</class>
+
+	<class name="Pet" lazy="false">
+		<id name="Id">
+			<generator class="native" />
+		</id>
+		<many-to-one name="Owner" column="PersonId" not-null="true"/>
+		<property name="Name"/>
+		<property name="Species"/>
+		<property name="Weight"/>
+	</class>
+
+	<filter-def name="ExampleFilter">
+		<filter-param name="WeightVal" type="int"/>
+	</filter-def>
+
+</hibernate-mapping>
\ No newline at end of file
Index: nhibernate/src/NHibernate.Test/NHSpecificTest/NH1394/Person.cs
===================================================================
--- nhibernate/src/NHibernate.Test/NHSpecificTest/NH1394/Person.cs	(revision 0)
+++ nhibernate/src/NHibernate.Test/NHSpecificTest/NH1394/Person.cs	(revision 0)
@@ -0,0 +1,110 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+
+namespace NHibernate.Test.NHSpecificTest.NH1394
+{
+	public class Person
+	{
+		private int id;
+		private int iq;
+		private string name;
+		private IList pets;
+		private int shoeSize;
+
+		public Person()
+		{
+			Pets = new ArrayList();
+		}
+
+		public Person(string name, int iq, int shoeSize)
+		{
+			this.name = name;
+			this.iq = iq;
+			this.shoeSize = shoeSize;
+			Pets = new ArrayList();
+		}
+
+		public virtual int Id
+		{
+			get { return id; }
+			set { id = value; }
+		}
+
+		public virtual string Name
+		{
+			get { return name; }
+			set { name = value; }
+		}
+
+		public virtual int IQ
+		{
+			get { return iq; }
+			set { iq = value; }
+		}
+
+		public virtual int ShoeSize
+		{
+			get { return shoeSize; }
+			set { shoeSize = value; }
+		}
+
+		public virtual System.Collections.IList Pets
+		{
+			get { return pets; }
+			protected set { pets = value; }
+		}
+	}
+
+	public class Pet
+	{
+		private int id;
+		private string name;
+		private Person owner;
+		private string species;
+		private double weight;
+
+		public Pet()
+		{
+		}
+
+		public Pet(string name, string species, int weight, Person owner)
+		{
+			this.name = name;
+			this.species = species;
+			this.weight = weight;
+			this.owner = owner;
+		}
+
+		public virtual int Id
+		{
+			get { return id; }
+			set { id = value; }
+		}
+
+		public virtual string Name
+		{
+			get { return name; }
+			set { name = value; }
+		}
+
+		public virtual string Species
+		{
+			get { return species; }
+			set { species = value; }
+		}
+
+		public virtual double Weight
+		{
+			get { return weight; }
+			set { weight = value; }
+		}
+
+		public virtual Person Owner
+		{
+			get { return owner; }
+			set { owner = value; }
+		}
+	}
+}
Index: nhibernate/src/NHibernate/Criterion/AggregateProjection.cs
===================================================================
--- nhibernate/src/NHibernate/Criterion/AggregateProjection.cs	(revision 3660)
+++ nhibernate/src/NHibernate/Criterion/AggregateProjection.cs	(working copy)
@@ -1,6 +1,7 @@
 using System;
 using NHibernate.SqlCommand;
 using NHibernate.Type;
+using NHibernate.Util;
 
 namespace NHibernate.Criterion
 {
@@ -12,14 +13,19 @@
 	[Serializable]
 	public class AggregateProjection : SimpleProjection
 	{
+		protected readonly string aggregate;
+		protected readonly IProjection projection;
 		protected readonly string propertyName;
-		protected readonly string aggregate;
-
 		protected internal AggregateProjection(string aggregate, string propertyName)
 		{
+			this.propertyName = propertyName;
 			this.aggregate = aggregate;
-			this.propertyName = propertyName;
 		}
+		protected internal AggregateProjection(string aggregate, IProjection projection)
+		{
+			this.aggregate = aggregate;
+			this.projection = projection;
+		}
 
 		public override bool IsAggregate
 		{
@@ -28,25 +34,41 @@
 
 		public override string ToString()
 		{
-			return aggregate + "(" + propertyName + ')';
+			return aggregate + "(" + (projection!=null?projection.ToString():this.propertyName) + ')';
 		}
 
 		public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery)
 		{
-			return new IType[] {criteriaQuery.GetType(criteria, propertyName)};
+			if (projection != null)
+				return projection.GetTypes(criteria, criteriaQuery);
+			return new IType[] { criteriaQuery.GetType(criteria, propertyName) };
+
 		}
 
 		public override SqlString ToSqlString(ICriteria criteria, int loc, ICriteriaQuery criteriaQuery, IDictionary<string, IFilter> enabledFilters)
 		{
-			return new SqlString(new object[]
-			                     	{
-			                     		aggregate,
-			                     		"(",
-			                     		criteriaQuery.GetColumn(criteria, propertyName),
-			                     		") as y",
-			                     		loc.ToString(),
-			                     		"_"
-			                     	});
+			if (projection != null)
+				return new SqlString(new object[]
+				                     	{
+				                     		aggregate,
+				                     		"(",
+				                     		StringHelper.RemoveAsAliasesFromSql(
+				                     			projection.ToSqlString(criteria, loc, criteriaQuery, enabledFilters)).ToString(),
+				                     		") as y",
+				                     		loc.ToString(),
+				                     		"_"
+				                     	});
+			else
+				return new SqlString(new object[]
+				                     	{
+				                     		aggregate,
+				                     		"(",
+				                     		criteriaQuery.GetColumn(criteria, propertyName),
+				                     		") as y",
+				                     		loc.ToString(),
+				                     		"_"
+				                     	});
+
 		}
 
 		public override bool IsGrouped
Index: nhibernate/src/NHibernate/Criterion/AvgProjection.cs
===================================================================
--- nhibernate/src/NHibernate/Criterion/AvgProjection.cs	(revision 3660)
+++ nhibernate/src/NHibernate/Criterion/AvgProjection.cs	(working copy)
@@ -1,19 +1,46 @@
 using System;
+using System.Collections.Generic;
+using NHibernate.Engine;
+using NHibernate.SqlCommand;
+using NHibernate.SqlTypes;
 using NHibernate.Type;
+using NHibernate.Util;
 
 namespace NHibernate.Criterion
 {
 	[Serializable]
 	public class AvgProjection : AggregateProjection
 	{
+		public AvgProjection(IProjection projection)
+			: base("avg", projection)
+		{
+		}
 		public AvgProjection(String propertyName)
-			: base("avg", propertyName)
+			: base("avg",propertyName)
 		{
 		}
+		public override SqlString ToSqlString(ICriteria criteria, int loc, ICriteriaQuery criteriaQuery, IDictionary<string, IFilter> enabledFilters)
+		{
+			ISessionFactoryImplementor factory = criteriaQuery.Factory;
+			SqlType[] sqlTypeCodes = NHibernateUtil.Double.SqlTypes(factory);
+			string sqlType = factory.Dialect.GetCastTypeName(sqlTypeCodes[0]);
+			string parameter;
+			if(projection!=null)
+				parameter = StringHelper.RemoveAsAliasesFromSql(
+					projection.ToSqlString(criteria, loc, criteriaQuery, enabledFilters)).ToString();
+			else
+				parameter = criteriaQuery.GetColumn(criteria, propertyName);
+			string expression = string.Format("{0}(cast({1} as {2})) as {3}", 
+				aggregate, 
+				parameter, 
+				sqlType,
+				GetColumnAliases(loc)[0]);
+			return new SqlString(expression);
 
+		}
 		public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery)
 		{
 			return new IType[] {NHibernateUtil.Double};
 		}
 	}
-}
\ No newline at end of file
+}
Index: nhibernate/src/NHibernate/Criterion/CountProjection.cs
===================================================================
--- nhibernate/src/NHibernate/Criterion/CountProjection.cs	(revision 3660)
+++ nhibernate/src/NHibernate/Criterion/CountProjection.cs	(working copy)
@@ -1,6 +1,7 @@
 using System;
 using NHibernate.SqlCommand;
 using NHibernate.Type;
+using NHibernate.Util;
 
 namespace NHibernate.Criterion
 {
@@ -18,7 +19,12 @@
 			: base("count", prop)
 		{
 		}
+		protected internal CountProjection(IProjection projection)
+			: base("count", projection)
+		{
+		}
 
+
 		public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery)
 		{
 			return new IType[] {NHibernateUtil.Int32};
@@ -50,4 +56,4 @@
 			return this;
 		}
 	}
-}
\ No newline at end of file
+}
Index: nhibernate/src/NHibernate/Criterion/Order.cs
===================================================================
--- nhibernate/src/NHibernate/Criterion/Order.cs	(revision 3660)
+++ nhibernate/src/NHibernate/Criterion/Order.cs	(working copy)
@@ -1,6 +1,9 @@
 using System;
+using System.Collections.Generic;
 using System.Text;
 using NHibernate.Engine;
+using NHibernate.Criterion;
+using NHibernate.SqlCommand;
 
 namespace NHibernate.Criterion
 {
@@ -13,6 +16,17 @@
 	{
 		protected bool ascending;
 		protected string propertyName;
+		protected IProjection projection;
+		/// <summary>
+		/// Constructor for Order.
+		/// </summary>
+		/// <param name="projection"></param>
+		/// <param name="ascending"></param>
+		public Order(IProjection projection, bool ascending)
+		{
+			this.projection = projection;
+			this.ascending = ascending;
+		}
 
 		/// <summary>
 		/// Constructor for Order.
@@ -31,6 +45,16 @@
 		/// </summary>
 		public virtual string ToSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery)
 		{
+			if(projection!=null)
+			{
+				SqlString sb=new SqlString();
+				SqlString produced = this.projection.ToSqlString(criteria, 0, criteriaQuery, new Dictionary<string, IFilter>());
+				SqlString truncated = NHibernate.Util.StringHelper.RemoveAsAliasesFromSql(produced);
+				sb = sb.Append(truncated);
+				sb = sb.Append(ascending ? " asc" : " desc");
+				return sb.ToString();
+			}
+
 			string[] columns = criteriaQuery.GetColumnAliasesUsingProjection(criteria, propertyName);
 
 			StringBuilder fragment = new StringBuilder();
@@ -65,7 +89,7 @@
 
 		public override string ToString()
 		{
-			return propertyName + (ascending ? " asc" : " desc");
+			return (projection!=null?projection.ToString():propertyName) + (ascending ? " asc" : " desc");
 		}
 
 		/// <summary>
@@ -79,8 +103,28 @@
 		}
 
 		/// <summary>
+		/// Ascending order
+		/// </summary>
+		/// <param name="projection"></param>
+		/// <returns></returns>
+		public static Order Asc(IProjection projection)
+		{
+			return new Order(projection, true);
+		}
+
+		/// <summary>
 		/// Descending order
 		/// </summary>
+		/// <param name="projection"></param>
+		/// <returns></returns>
+		public static Order Desc(IProjection projection)
+		{
+			return new Order(projection, false);
+		}
+
+		/// <summary>
+		/// Descending order
+		/// </summary>
 		/// <param name="propertyName"></param>
 		/// <returns></returns>
 		public static Order Desc(string propertyName)
@@ -88,4 +132,4 @@
 			return new Order(propertyName, false);
 		}
 	}
-}
\ No newline at end of file
+}
Index: nhibernate/src/NHibernate/Criterion/Projections.cs
===================================================================
--- nhibernate/src/NHibernate/Criterion/Projections.cs	(revision 3660)
+++ nhibernate/src/NHibernate/Criterion/Projections.cs	(working copy)
@@ -56,10 +56,18 @@
 		{
 			return new RowCountInt64Projection();
 		}
-
 		/// <summary>
 		/// A property value count
 		/// </summary>
+		/// <param name="projection"></param>
+		/// <returns></returns>
+		public static CountProjection Count(IProjection projection)
+		{
+			return new CountProjection(projection);
+		}
+		/// <summary>
+		/// A property value count
+		/// </summary>
 		/// <param name="propertyName"></param>
 		/// <returns></returns>
 		public static CountProjection Count(string propertyName)
@@ -88,6 +96,17 @@
 		}
 
 		/// <summary>
+		/// A projection maximum value
+		/// </summary>
+		/// <param name="projection"></param>
+		/// <returns></returns>
+		public static AggregateProjection Max(IProjection projection)
+		{
+			return new AggregateProjection("max", projection);
+		}
+
+
+		/// <summary>
 		/// A property minimum value
 		/// </summary>
 		/// <param name="propertyName"></param>
@@ -98,6 +117,16 @@
 		}
 
 		/// <summary>
+		/// A projection minimum value
+		/// </summary>
+		/// <param name="projection"></param>
+		/// <returns></returns>
+		public static AggregateProjection Min(IProjection projection)
+		{
+			return new AggregateProjection("min", projection);
+		}
+
+		/// <summary>
 		/// A property average value
 		/// </summary>
 		/// <param name="propertyName"></param>
@@ -108,6 +137,16 @@
 		}
 
 		/// <summary>
+		/// A property average value
+		/// </summary>
+		/// <param name="projection"></param>
+		/// <returns></returns>
+		public static AggregateProjection Avg(IProjection projection)
+		{
+			return new AvgProjection(projection);
+		}
+
+		/// <summary>
 		/// A property value sum
 		/// </summary>
 		/// <param name="propertyName"></param>
@@ -118,6 +157,16 @@
 		}
 
 		/// <summary>
+		/// A property value sum
+		/// </summary>
+		/// <param name="projection"></param>
+		/// <returns></returns>
+		public static AggregateProjection Sum(IProjection projection)
+		{
+			return new AggregateProjection("sum", projection);
+		}
+
+		/// <summary>
 		/// A SQL projection, a typed select clause fragment
 		/// </summary>
 		/// <param name="sql"></param>