GridView, NHibernate and sorting

If you’re using an ASP.NET GridView with a custom DAL you might get this error when trying to sort the grid by clicking the column headers:

The data source does not support sorting with IEnumerable data. Automatic sorting is only supported with DataView, DataTable, and DataSet.

The web has some solutions to this problem, however I’ve found them either not working, sorting only in memory or too complex.

I’m using NHibernate and the simplest solution was to parse the sortExpression from the ObjectDataSource into a list of NHibernate.Criterion.Order, then add the Orders to the ICriteria.

This way, I only have to add SortParameterName=”sortExpression” to the ObjectDataSource and the corresponding code in the Select method.

The ObjectDataSource:

<asp:ObjectDataSource ID="odsProjects" EnablePaging="true" runat="server"
    SelectMethod="FindAll"
    SortParameterName="sortExpression"
    SelectCountMethod="GetCountOfProjects" TypeName="Model.Project">
</asp:ObjectDataSource>

The GridView:

<asp:TemplateField HeaderText="Title" SortExpression="Title">
    <ItemTemplate>
        <asp:LinkButton ID="lnkProjectTitle" runat="server"
         CommandName="select" Text='<%# Eval("Title") %>'
         CausesValidation="False"></asp:LinkButton>
    </ItemTemplate>
</asp:TemplateField>

The Find method in the Project class:

public static IList<Project> FindAll ( int startRowIndex, int maximumRows, string sortExpression)
{
    ISessionFactoryHolder sessionHolder = ActiveRecordMediator.GetSessionFactoryHolder();
    ISession session = sessionHolder.CreateSession( typeof( Project ) );

    try
    {
        ICriteria crit = session.CreateCriteria( typeof( Project ) );

        crit.SetFirstResult( startRowIndex )
            .SetMaxResults( maximumRows );
        // the important thing
        if ( string.IsNullOrEmpty( sortExpression ) )
            crit.AddOrder( Order.Desc( "Id" ) );
        else
        {
            foreach ( Order order in Extensions.CreateOrdersFrom( sortExpression ) )
            {
                crit.AddOrder( order );
            }
        }

        return crit.SetCacheable( true ).List<Project>();
    }
    catch ( Exception ex )
    {
        log.ErrorException( "FindAll: ", ex );
        return new List<Project>();
    }
    finally
    {
        sessionHolder.ReleaseSession( session );
    }
}

The CreateOrdersFrom method:

/// <summary>
/// Creates a list of NHibernate.Criterion.Order from an sql sort expression. If sort is missing, ASC is assumed.
/// </summary>
/// <param name="sqlSortExpression">Id DESC, Number ASC or Id, Name desc</param>
/// <returns></returns>
public static List<Order> CreateOrdersFrom ( string sqlSortExpression )
{
    if ( string.IsNullOrEmpty( sqlSortExpression ) )
        return new List<Order>();

    List<Order> orders = new List<Order>();
    string[] sortExpressions = sqlSortExpression.Trim().Split( ',' );

    Array.ForEach( sortExpressions, sortExpression =>
    {
        log.Debug( "expr: {0}.", sortExpression );

        string[] columnAndSort = sortExpression.Trim().Split( ' ' );

        string column = "", sort = "asc";

        if ( columnAndSort.Length == 1 )
        {
            column = columnAndSort[0];
        }
        if ( columnAndSort.Length == 2 )
        {
            column = columnAndSort[0];
            sort = columnAndSort[1];
        }
        log.Debug( "{0} x {1}.", column, sort );

        orders.Add( sort.ToLowerInvariant() == "asc" ? Order.Asc( column ) : Order.Desc( column ) );
    } );

    return orders;
}

Where’s the bug?

Where’s the bug?

public virtual void RemoveOfferItems()
{
    for ( int i = 0; i < offerItems.Count; i++ )
    {
        OfferItem offerItem = offerItems[i];
        log.Trace( "offer item: {0}, {1}", offerItem.Id, offerItem.Description );

        offerItems.Remove( offerItem );
        offerItem.Delete();
    }
    Save();
}

But don’t write a test for this…