2

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!

phil
  • 33
  • 2
  • 1
    I found the solution to my immediate problem. `Projections.Max` is overloaded several times, one overload can simply take another projection, in this case `Projections.Cast`. So The line needed to be this: `Projections.Max( Projections.Cast( NHibernateUtil.Int32, Projections.Property( "major_journal" ) ) .WithAlias( () => report.EverPublished )` My original attempt was using an unnecessarily complex version of `Projections.Max`. – phil Oct 01 '13 at 16:09

1 Answers1

0

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 ;)

MichaC
  • 13,104
  • 2
  • 44
  • 56
  • What I'm looking for is slightly different, although I'd have no doubt the linq approach would probably work for it; I want to return all the (distinct) authors, and then a 0 or 1 which indicates whether they're published. I haven't been asked to separate the published and non-published authors into two separate queries. Thanks for your answer, I'll be glad to accept it as long as it still captures that original intent. I also added a comment to the original question with the solution that I found myself. (I can't submit an answer because I'm brand new here.) – phil Oct 01 '13 at 18:02
  • I really appreciate such a detailed example for an answer, thanks again. – phil Oct 01 '13 at 18:05
  • @phil I've added a few more findings to my answer, maybe also interesting for you – MichaC Oct 02 '13 at 13:15
  • thanks again! Do you know a good resource to refer to in order to learn how NHibernate and Linq play together and what the relative strengths are of each? They seem to be very similar ways to do more or less the same things, perhaps with some differences in capabilities. – phil Oct 02 '13 at 20:52