How to query the first entry in each group in NHibernate

无人久伴 提交于 2020-01-30 04:38:21

问题


The following code using LINQ in NHibernate returns a different result from in-memory LINQ and EF LINQ. What is the correct way to do this in NHibernate? It is fine to use QueryOver if the LINQ version is indeed broken.

using (var session = factory.OpenSession())
using (var transaction = session.BeginTransaction())
{
    for (int i = 0; i < 10; ++i)
    {
        session.Save(new A()
        {
            X = i % 2,
            Y = i / 2,
        });
    }
    transaction.Commit();
}
using (var session = factory.OpenSession())
using (var transaction = session.BeginTransaction())
{
    //=====================================
    var expected = session.Query<A>()
        .ToList() // <-- copy to memory
        .GroupBy(a => a.X)
        .Select(g => g.OrderBy(y => y.Y).First())
        .ToList();
    Console.WriteLine(string.Join(" ", expected.Select(a => a.Id)));
    //=====================================
    var actual = session.Query<A>()
        .GroupBy(a => a.X)
        .Select(g => g.OrderBy(y => y.Y).First())
        .ToList();
    Console.WriteLine(string.Join(" ", actual.Select(a => a.Id)));
}

public class A
{
    public int Id { get; set; }
    public int X { get; set; } // indexed
    public int Y { get; set; } // indexed
}

Expected results

1 2

Actual results

1 1

Logged SQL

NHibernate: select (select program_a0_.Id as id1_0_ from "A" program_a0_ order by program_a0_.Y asc limit 1) as col_0_0_ from "A" program_a0_ group by program_a0_.X

The full code is in the bug report Incorrect result when using GroupBy with First


Update 2019-8-9

The query should not use ID. I have changed it to a non-unique property. I would appreciate if the solution only query once to SQLite.


回答1:


It seems latest NHibernate 5.2 LINQ provider supports only aggregate functions (MIN, MAX, COUNT...) in Select for "group by" query.

I believe in HQL your query can be rewritten using aggregate functions as:

session.CreateQuery(
    "select a from A a where a.Id in "
        + "(select min(ga.Id) from A ga group by ga.X)").List<A>();

But unfortunatly it can't be expressed in LINQ in one query due to this bug (in NHibernate 5.2 group by subquery throws exception). So for now in LINQ it can be done via 2 queries:

var subquery = session.Query<A>()
    .GroupBy(ga => ga.X)
    .Select(ga => ga.Min(a => a.Id))
    .ToList();//when bug is fixed this ToList call can be omitted and make results retrieved in single db call

var results = session.Query<A>().Where(a => subquery.Contains(a.Id)).ToList();

Also it seems "group by" in subquery can be also avoided so single query LINQ version is also possible:

var results = session.Query<A>()
     .Where(a => a.Id == session.Query<A>().Where(sa => sa.X == a.X).Select(sa => sa.Id).Min())
     .ToList();

//or version closer to your original query:
var results = session.Query<A>()
     .Where(a => a == session.Query<A>().Where(sa => sa.X == a.X).OrderBy(sa => sa.Id).First())
     .ToList();

Update So with your latest update you still can use non group by LINQ version:

var results = session.Query<A>()
     .Where(a => a == session.Query<A>()
                         .Where(sa => sa.X == a.X) //Group BY key is here
                         .OrderBy(sa => sa.Y) //Order By key is here
                         .First())
     .ToList();


来源:https://stackoverflow.com/questions/57268100/how-to-query-the-first-entry-in-each-group-in-nhibernate

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!