Frage

Not sure this is even possible but I'll explain anyway.

I have a custom projection for doing simple arithmetic in a query which works perfectly in a single projection.

namespace Custom.Projections
{
    using System.Collections.Generic;
    using System.Text;
    using global::NHibernate;
    using global::NHibernate.Criterion;
    using global::NHibernate.SqlCommand;
    using global::NHibernate.Type;

    /// <summary>
    /// Represents a CalculatedProjection
    /// </summary>
    public class CalculatedProjection : SimpleProjection
    {
        private readonly CalculationType calculationType;
        private readonly IProjection firstProjection;
        private readonly IProjection secondProjection;
        private readonly string firstPropertyName;
        private readonly string secondPropertyName;

        /// <summary>
        /// Initializes a new instance of the CalculatedProjection class
        /// </summary>
        /// <param name="type">The type of calculation</param>
        /// <param name="firstPropertyName">The name of the first property</param>
        /// <param name="secondPropertyName">The name of the second property</param>
        protected internal CalculatedProjection(CalculationType type, string firstPropertyName, string secondPropertyName)
        {
            this.calculationType = type;
            this.firstPropertyName = firstPropertyName;
            this.secondPropertyName = secondPropertyName;
            System.Diagnostics.Debugger.Launch();
        }

        /// <summary>
        /// Initializes a new instance of the CalculatedProjection class
        /// </summary>
        /// <param name="type">The type of calculation</param>
        /// <param name="firstProjection">The first projection</param>
        /// <param name="secondProjection">The second projection</param>
        protected internal CalculatedProjection(CalculationType type, IProjection firstProjection, IProjection secondProjection)
        {
            this.calculationType = type;
            this.firstProjection = firstProjection;
            this.secondProjection = secondProjection;
        }

        /// <summary>
        /// The type of calculation
        /// </summary>
        public enum CalculationType
        {
            /// <summary>
            /// Addition + 
            /// </summary>
            Addition,

            /// <summary>
            /// Subtraction -
            /// </summary>
            Subtraction,

            /// <summary>
            /// Division /
            /// </summary>
            Division,

            /// <summary>
            /// Division *
            /// </summary>
            Multiplication,
        }

        /// <summary>
        /// Gets a value indicating whether the projection is grouped
        /// </summary>
        public override bool IsGrouped
        {
            get { return false; }
        }

        /// <summary>
        /// Gets a value indicating whether IsAggregate.
        /// </summary>
        public override bool IsAggregate
        {
            get { return false; }
        }

        /// <summary>
        /// Converts the calculation into a string
        /// </summary>
        /// <returns>The string representation of the calculation</returns>
        public override string ToString()
        {
            var firstValue = this.firstProjection != null ? this.firstProjection.ToString() : this.firstPropertyName;
            var secondValue = this.secondProjection != null ? this.secondProjection.ToString() : this.secondPropertyName;

            return "(" + firstValue + TypeToString(this.calculationType) + secondValue + ")";
        }

        /// <summary>
        /// Gets the types involved in the query
        /// </summary>
        /// <param name="criteria">The current criteria</param>
        /// <param name="criteriaQuery">The criteria query</param>
        /// <returns>An array of IType</returns>
        public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery)
        {
            var types = new List<IType>();

            if (this.firstProjection != null)
            {
                types.AddRange(this.firstProjection.GetTypes(criteria, criteriaQuery));
            }
            else
            {
                types.Add(criteriaQuery.GetType(criteria, this.firstPropertyName));
            }

            if (this.secondProjection != null)
            {
                types.AddRange(this.secondProjection.GetTypes(criteria, criteriaQuery));
            }
            else
            {
                types.Add(criteriaQuery.GetType(criteria, this.secondPropertyName));
            }

            return types.ToArray();
        }

        /// <summary>
        /// Converts the objects to an sql string representation
        /// </summary>
        /// <param name="criteria">The criteria being used in the query</param>
        /// <param name="loc">The location in the query</param>
        /// <param name="criteriaQuery">The criteria query</param>
        /// <param name="enabledFilters">List of enabled filters</param>
        /// <returns>The calculation as an sql string</returns>
        public override SqlString ToSqlString(ICriteria criteria, int loc, ICriteriaQuery criteriaQuery, IDictionary<string, IFilter> enabledFilters)
        {
            string first, second;

            if ((this.firstProjection != null) && (this.secondProjection != null))
            {
                first = global::NHibernate.Util.StringHelper.RemoveAsAliasesFromSql(this.firstProjection.ToSqlString(criteria, loc, criteriaQuery, enabledFilters)).ToString();
                second = global::NHibernate.Util.StringHelper.RemoveAsAliasesFromSql(this.secondProjection.ToSqlString(criteria, loc, criteriaQuery, enabledFilters)).ToString();
            }
            else
            {
                first = criteriaQuery.GetColumn(criteria, this.firstPropertyName);
                second = criteriaQuery.GetColumn(criteria, this.secondPropertyName);
            }

            return new SqlString(new object[] { "(", first, TypeToString(this.calculationType), second, ") as y", loc.ToString(), "_" });
        }

        /// <summary>
        /// Converts the objects to an sql string representation
        /// </summary>
        /// <param name="criteria">The criteria being used in the query</param>
        /// <param name="criteriaQuery">The criteria query</param>
        /// <param name="enabledFilters">List of enabled filters</param>
        /// <returns>The calculation as an sql string</returns>
        public override SqlString ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery, IDictionary<string, IFilter> enabledFilters)
        {
            var sb = new StringBuilder();

            return new SqlString(new object[] { sb.ToString() });
        }

        /// <summary>
        /// Returns the string symbol of calculation type
        /// </summary>
        /// <param name="type">The type to use</param>
        /// <returns>The string representation</returns>
        private static string TypeToString(CalculationType type)
        {
            switch (type)
            {
                case CalculationType.Addition: return "+";
                case CalculationType.Subtraction: return "-";
                case CalculationType.Multiplication: return "*";
                case CalculationType.Division: return "/";
                default: return "+";
            }
        }
    }
}

I am now trying to use this projection from within another using the QueryOver syntax as below:

public override IList<DaySummaryDetail> Execute()
{
    // ReSharper disable PossibleNullReferenceException
    var calculated = Custom.Projections.Calculated(CalculatedProjection.CalculationType.Subtraction, "Duration.EndTime", "Duration.StartTime");

    DaySummaryDetail detail = null;

    return Session.QueryOver<WorkingDay>()
        .SelectList(list => list
        .Select(Projections.Group<WorkingDay>(x => x.Date).WithAlias(() => detail.Date))
        .Select(Projections.Sum(calculated)).WithAlias(() => detail.TotalMinutes))
        .Where(x => x.Person.Id == 1)
        .TransformUsing(Transformers.AliasToBean<DaySummaryDetail>())
        .List<DaySummaryDetail>();

    // ReSharper restore PossibleNullReferenceException
}

The query generated is correct:

SELECT
    this_.DATE_TIME as y0_,
    sum((this_.END_MINS-this_.START_MINS)) as y1_ 
FROM
    WORKINGDAY this_ 
WHERE
    this_.PERSON_ID = @p0 
GROUP BY
    this_.DATE_TIME;
@p0 = 1 [Type: Int64 (0)]

The issue I have is that I get the following exception:

NHibernate.Exceptions.GenericADOException : could not execute query [ SELECT this_.DATE_TIME as y0_, sum((this_.END_MINS-this_.START_MINS)) as y1_ FROM WORKINGDAY this_ WHERE this_.PERSON_ID = @p0 GROUP BY this_.DATE_TIME ] Name:cp0 - Value:8977 [SQL: SELECT this_.DATE_TIME as y0_, sum((this_.END_MINS-this_.START_MINS)) as y1_ FROM WORKINGDAY this_ WHERE this_.PERSON_ID = @p0 GROUP BY this_.DATE_TIME] ----> System.IndexOutOfRangeException : y2_

StackTrace:

at System.Data.SqlClient.SqlDataReader.GetOrdinal(String name)
at NHibernate.Type.NullableType.NullSafeGet(IDataReader rs, String name)
at NHibernate.Loader.Criteria.CriteriaLoader.GetResultColumnOrRow(Object[] row, IResultTransformer customResultTransformer, IDataReader rs, ISessionImplementor session)
at NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
at NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
at NHibernate.Loader.Loader.DoList(ISessionImplementor session, QueryParameters queryParameters) 
at NHibernate.Loader.Loader.ListIgnoreQueryCache(ISessionImplementor session, QueryParameters queryParameters)
at NHibernate.Loader.Criteria.CriteriaLoader.List(ISessionImplementor session)
at NHibernate.Impl.SessionImpl.List(CriteriaImpl criteria, IList results)
at NHibernate.Impl.CriteriaImpl.List(IList results)
at NHibernate.Impl.CriteriaImpl.List()

As you can see from the the query the aliasing is using two values (y0_ and y1_) yet somehow it is looking for y2_. I assume this is because I am using a nested projection here which it can not deal with OR is it that I have implemented or called the projection incorrectly?

Any help or alternate suggestions would be great. I realise this can be done in a number of ways using HQL etc. but was particularly interested in using QueryOver

Thanks in advance

War es hilfreich?

Lösung

School boy error.

The GetTypes method on of the projection should return the types involved in the result of the projection. I was returning the types of the fields involved.

Should look like this:

public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery)
{
    return new IType[] { NHibernateUtil.Int32 };
}

Hope this saves someone some time

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top