Question

Let's say I had a database with a table of academic papers, and there was a column which indicated whether the paper was published in a "major" journal (as a bit). I might want to run a query to list every author and whether they'd ever been published in one of them. It might look like:

select author, max( cast( major_journal as INT )) as ever_published
from AcademicPapers
group by author;

Cool! now I want to do that with NHibernate. Cutting out the rest of the query, and focusing on the max(cast( ... )) part, I tried this:

Projections.Max<AcademicPaper>( 
  m => Projections.Cast( NHibernateUtil.Int32, Projections.Property( "major_journal" ) ) )
  .WithAlias( () => report.EverPublished )

However, when I run this, I get an exception that is more or less incomprehensible to me:

No persister for: NHibernate.Criterion.CastProjection

I know with 100% certainty that I've structured this Projection business incorrectly, but I haven't been able to find a good reference for NHibernate yet; every time I search for one, I just find StackOverflow. I'd love to get either a hand with this particular problem or a link to a decent writeup of what's actually going on here.

Thank you kindly!

Was it helpful?

Solution

I hope I understood your issue correctly, so you just want to get all Authors which have at least one paper which has this flag set to true, right?

Why do you not just use Linq, its way easier to write and should work for such simple scenarios. I would also map your flag to a bool, so I guess there is no need to do a Max operation at all... Example:

var authorsWithPublications = session.Query<Paper>()
     .Select(p => new { Author = p.Author, HasPublished = p.HasPublished })
     .Where(p => p.HasPublished == true).ToList();

I used this simple scenario, let me know if this doesn't match your issue:

Entity + Mapping:

public class Paper
{
    public virtual int Id { get; set; }
    public virtual string Author { get; set; }
    public virtual bool HasPublished { get; set; }
    public virtual string Description { get; set; }
    public virtual string Something { get; set; }
    public virtual string SomethingElse { get; set; }
}

public class PaperMap : ClassMap<Paper>
{
    public PaperMap()
    {
        Id<int>("Id");

        Map(p => p.Author);
        Map(p => p.HasPublished);
        Map(p => p.Description);
        Map(p => p.Something);
        Map(p => p.SomethingElse);
    }
}

Creation of some test data and the query

using (var session = sessionFactory.OpenSession())
{
    Random r1 = new Random();

    for (int i = 0; i < 100; i++)
    {
        session.Save(new Paper()
        {
            Author = "Author" + i,
            HasPublished = r1.Next(0, 2) == 0 ? false : true,
            Description = "Description" +i,
            Something = "Something" + i,
            SomethingElse = "something else" + i
        });
    }
    session.Flush();

    var authorsWithPublications = session.Query<Paper>()
                .Select(p => new { Author = p.Author, HasPublished = p.HasPublished })
                .Where(p => p.HasPublished == true).ToList();
}

It actually returns me exactly those authors... You could process this further to have just a distinct result...

:edit starts: to query all authors with the maximum value of the flag, it becomes a little bit tricky with linq, the following linq query would return that result:

var authorsWithPublications = session.Query<Paper>()
                .GroupBy(p => new { Author = p.Author })
                .Select(p => new { 
                    Author = p.Key, 
                    HasPublished = p.Max(c=> c.HasPublished) 
                })
                .ToList();

But if c.HasPublished is a bit field in SqlServer, it will give you the sql exception that max is not allowed on bit fields.

Trying to convert the bool to int wihtin the linq statement like

 ...HasPublished = p.Max(c => c.HasPublished == true ? 1 : 0)

will throw an exception Code supposed to be unreachable, because it is not supported by nHibernate...

The only way I found to get the first Linq query running is by specifying a formula within the mapping:

Map(p => p.HasPublished).Formula("cast (HasPublished as int)");

Now this formula will be applied to all select statements, statement will look like this:

select paper0_.Author as col_0_0_, max(cast (paper0_.HasPublished as int)) as col_1_0_ 
from [Paper] paper0_ 
group by paper0_.Author

Anyways you already found the solution and the following does actually the same without the need of a formula

var criteria = session.CreateCriteria<Paper>();
criteria.SetProjection(
    Projections.Group<Paper>(p=>p.Author),
    Projections.Max(
        Projections.Cast(NHibernateUtil.Int32, Projections.Property("HasPublished")))
    );
var result = criteria.List();

But maybe we both learned something ;)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top