问题
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