Skip to content

Commit d70038b

Browse files
Obsolete MultiQuery and MultiCriteria
The QueryBatch can replace them both, and does not suffer of some of their bugs.
1 parent f3be548 commit d70038b

File tree

42 files changed

+3045
-155
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+3045
-155
lines changed

doc/reference/modules/performance.xml

Lines changed: 148 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,130 +1216,193 @@ sessionFactory.EvictCollection("Eg.Cat.Kittens");]]></programlisting>
12161216
</sect1>
12171217

12181218
<sect1 id="performance-multi-query">
1219-
<title>Multi Query</title>
1220-
1219+
<title>Query batch</title>
1220+
12211221
<para>
1222-
This functionality allows you to execute several HQL queries in one round-trip
1222+
This functionality allows you to execute several queries in one round-trip
12231223
against the database server. A simple use case is executing a paged query while
1224-
also getting the total count of results, in a single round-trip. Here is a simple
1224+
also getting the total count of results, in a single round-trip. Here is an
12251225
example:
12261226
</para>
1227+
1228+
<programlisting><![CDATA[using NHibernate.Multi;
1229+
1230+
...
1231+
1232+
IQueryBatch queries = s.CreateQueryBatch()
1233+
.Add<Item>(
1234+
s.CreateQuery("from Item i where i.Id > ?")
1235+
.SetInt32(0, 50).SetFirstResult(10))
1236+
.Add<long>(
1237+
s.CreateQuery("select count(*) from Item i where i.Id > :id")
1238+
.SetInt32("id", 50));
1239+
IList<Item> items = queries.GetResult<Item>(0);
1240+
long count = queries.GetResult<long>(1).Single();]]></programlisting>
1241+
1242+
<para>
1243+
The results are got by index, ordered according to the order of queries
1244+
added to the query batch. Instead of relying on this ordering, a key can be
1245+
associated with each query for later retrieval:
1246+
</para>
1247+
1248+
<programlisting><![CDATA[using NHibernate.Multi;
1249+
1250+
...
1251+
1252+
var queries = s.CreateQueryBatch()
1253+
.Add("list", s.Query<Item>().Where(i => i.Id > 50))
1254+
.Add("count", s.Query<Item>().Where(i => i.Id > 50), q => q.Count());
1255+
var count = queries.GetResult<int>("count").Single();
1256+
var items = queries.GetResult<Item>("list");]]></programlisting>
1257+
1258+
<para>
1259+
The namespace <literal>NHibernate.Multi</literal> has to be imported since most query
1260+
batch methods are extension methods.
1261+
</para>
1262+
1263+
<para>
1264+
Criteria queries are also supported by the query batch:
1265+
</para>
12271266

1228-
<programlisting><![CDATA[IMultiQuery multiQuery = s.CreateMultiQuery()
1229-
.Add(s.CreateQuery("from Item i where i.Id > ?")
1230-
.SetInt32(0, 50).SetFirstResult(10))
1231-
.Add(s.CreateQuery("select count(*) from Item i where i.Id > ?")
1232-
.SetInt32(0, 50));
1233-
IList results = multiQuery.List();
1234-
IList items = (IList)results[0];
1235-
long count = (long)((IList)results[1])[0];]]></programlisting>
1267+
<programlisting><![CDATA[var queries = s.CreateQueryBatch()
1268+
.Add<Item>(
1269+
s.CreateCriteria(typeof(Item))
1270+
.Add(Expression.Gt("Id", 50))
1271+
.SetFirstResult(10))
1272+
.Add<long>(
1273+
s.CreateCriteria(typeof(Item))
1274+
.Add(Expression.Gt("Id", 50))
1275+
.SetProject(Projections.RowCount()));
1276+
var items = queries.GetResult<Item>(0);
1277+
var count = queries.GetResult<long>(1).Single();]]></programlisting>
12361278

12371279
<para>
1238-
The result is a list of query results, ordered according to the order of queries
1239-
added to the multi query. Named parameters can be set on the multi query, and are
1240-
shared among all the queries contained in the multi query, like this:
1280+
You can add <literal>ICriteria</literal> or <literal>DetachedCriteria</literal> to the query batch.
1281+
In fact, using DetachedCriteria in this fashion has some interesting implications.
12411282
</para>
1283+
<programlisting><![CDATA[DetachedCriteria customersCriteria = AuthorizationService.GetAssociatedCustomersQuery();
1284+
IQueryBatch queries = session.CreateQueryBatch()
1285+
.Add<Customer>(customersCriteria)
1286+
.Add<Policy>(DetachedCriteria.For<Policy>()
1287+
.Add(Subqueries.PropertyIn("id",
1288+
CriteriaTransformer.Clone(customersCriteria)
1289+
.SetProjection(Projections.Id())
1290+
)));
12421291
1243-
<programlisting><![CDATA[IList results = s.CreateMultiQuery()
1244-
.Add(s.CreateQuery("from Item i where i.Id > :id")
1245-
.SetFirstResult(10))
1246-
.Add("select count(*) from Item i where i.Id > :id")
1247-
.SetInt32("id", 50)
1248-
.List();
1249-
IList items = (IList)results[0];
1250-
long count = (long)((IList)results[1])[0];]]></programlisting>
1292+
IList<Customer> customers = queries.GetResult<Customer>(0);
1293+
IList<Policy> policies = queries.GetResult<Policy>(1);]]></programlisting>
12511294

12521295
<para>
1253-
Positional parameters are not supported on the multi query, only on the individual
1254-
queries.
1296+
We get a query that represents the customers we can access, and then we can utilize this
1297+
query further in order to perform additional logic (getting the policies of the customers we are
1298+
associated with), all in a single database round-trip.
12551299
</para>
12561300

12571301
<para>
1258-
As shown above, if you do not need to configure the query separately, you can simply
1259-
pass the HQL directly to the <literal>IMultiQuery.Add()</literal> method.
1302+
The query batch also supports QueryOver and sql queries. All kind of queries can be mixed in the
1303+
same batch.
12601304
</para>
12611305

1306+
<programlisting><![CDATA[using NHibernate.Multi;
1307+
1308+
...
1309+
1310+
var queries = s.CreateQueryBatch()
1311+
.Add("queryOverList", s.QueryOver<Item>().Where(i => i.Category == "Food"))
1312+
.Add<long>("sqlCount",
1313+
s.CreateSQLQuery("select count(*) as count from Item i where i.Category = :cat")
1314+
.AddScalar("count", NHibernateUtil.Int64)
1315+
.SetString("cat", "Food"));
1316+
var count = queries.GetResult<long>("sqlCount").Single();
1317+
var items = queries.GetResult<Item>("queryOverList");]]></programlisting>
1318+
1319+
<para>
1320+
Second level cache is supported by the query batch. Queries flagged as cacheable will be retrieved
1321+
from the cache if already cached, otherwise their results will be put in the cache.
1322+
</para>
1323+
1324+
<programlisting><![CDATA[using NHibernate.Multi;
1325+
1326+
...
1327+
1328+
var queries = s.CreateQueryBatch()
1329+
.Add("list",
1330+
s.Query<Item>()
1331+
.Where(i => i.Id > 50)
1332+
.WithOptions(o => o.SetCacheable(true)))
1333+
.Add<long>("count",
1334+
s.CreateQuery("select count(*) from Item i where i.Id > :id")
1335+
.SetInt32("id", 50)
1336+
.SetCacheable(true));
1337+
var count = queries.GetResult<long>("count").Single();
1338+
var items = queries.GetResult<Item>("list");]]></programlisting>
1339+
12621340
<para>
12631341
Multi query is executed by concatenating the queries and sending the query to the database
12641342
as a single string. This means that the database should support returning several result sets
1265-
in a single query. At the moment this functionality is only enabled for Microsoft SQL Server and SQLite.
1343+
in a single query. Otherwise each query will be individually executed instead.
12661344
</para>
12671345

12681346
<para>
1269-
Note that the database server is likely to impose a limit on the maximum number of parameters
1270-
in a query, in which case the limit applies to the multi query as a whole. Queries using
1347+
The first <literal>GetResult</literal> call triggers execution of the whole query batch, which
1348+
then stores all results. Later calls only retrieve the stored results. A query batch can be
1349+
re-executed by calling its <literal>Execute</literal> method. Once executed, no new query can be
1350+
added to the batch.
1351+
</para>
1352+
1353+
<para>
1354+
Note that the database server is likely to enforce a limit on the maximum number of parameters
1355+
in a query, in which case the limit applies to the query batch as a whole. Queries using
12711356
<literal>in</literal> with a large number of arguments passed as parameters may easily exceed
12721357
this limit. For example, SQL Server has a limit of 2,100 parameters per round-trip, and will
12731358
throw an exception executing this query:
12741359
</para>
1275-
1276-
<programlisting><![CDATA[IList allEmployeesId = ...; //1,500 items
1277-
IMultiQuery multiQuery = s.CreateMultiQuery()
1278-
.Add(s.CreateQuery("from Employee e where e.Id in :empIds")
1279-
.SetParameter("empIds", allEmployeesId).SetFirstResult(10))
1280-
.Add(s.CreateQuery("select count(*) from Employee e where e.Id in :empIds")
1281-
.SetParameter("empIds", allEmployeesId));
1282-
IList results = multiQuery.List(); // will throw an exception from SQL Server]]></programlisting>
1360+
1361+
<programlisting><![CDATA[int[] allEmployeesId = ...; // 1,500 items
1362+
var queries = s.CreateQueryBatch()
1363+
.Add<Employee>(
1364+
s.CreateQuery("from Employee e where e.Id in :empIds")
1365+
.SetParameterList("empIds", allEmployeesId)
1366+
.SetFirstResult(10))
1367+
.Add<long>(
1368+
s.CreateQuery("select count(*) from Employee e where e.Id in :empIds")
1369+
.SetParameterList("empIds", allEmployeesId));
1370+
queries.Execute(); // will throw an exception from SQL Server]]></programlisting>
12831371

12841372
<para>
1285-
An interesting usage of this feature is to load several collections of an object in one
1373+
An interesting usage of the query batch is to load several collections of an object in one
12861374
round-trip, without an expensive cartesian product (blog * users * posts).
12871375
</para>
12881376

1289-
<programlisting><![CDATA[Blog blog = s.CreateMultiQuery()
1290-
.Add("select b from Blog b left join fetch b.Users where b.Id = :id")
1291-
.Add("select b from Blog b left join fetch b.Posts where b.Id = :id")
1292-
.SetInt32("id", 123)
1293-
.UniqueResult<Blog>();]]></programlisting>
1377+
<programlisting><![CDATA[Blog blog = s.CreateQueryBatch()
1378+
.Add(
1379+
s.CreateQuery("select b from Blog b left join fetch b.Users where b.Id = :id")
1380+
.SetInt32("id", 123))
1381+
.Add(
1382+
s.CreateQuery("select b from Blog b left join fetch b.Posts where b.Id = :id")
1383+
.SetInt32("id", 123))
1384+
.GetResult<Blog>(0).FirstOrDefault();]]></programlisting>
12941385

1295-
</sect1>
1296-
1297-
<sect1 id="performance-multi-criteria">
1298-
<title>Multi Criteria</title>
1299-
13001386
<para>
1301-
This is the counter-part to Multi Query, and allows you to perform several criteria queries
1302-
in a single round trip. A simple use case is executing a paged query while
1303-
also getting the total count of results, in a single round-trip. Here is a simple
1304-
example:
1387+
You can also add queries as future queries to a query batch:
13051388
</para>
1306-
1307-
<programlisting><![CDATA[IMultiCriteria multiCrit = s.CreateMultiCriteria()
1308-
.Add(s.CreateCriteria(typeof(Item))
1309-
.Add(Expression.Gt("Id", 50))
1310-
.SetFirstResult(10))
1311-
.Add(s.CreateCriteria(typeof(Item))
1312-
.Add(Expression.Gt("Id", 50))
1313-
.SetProject(Projections.RowCount()));
1314-
IList results = multiCrit.List();
1315-
IList items = (IList)results[0];
1316-
long count = (long)((IList)results[1])[0];]]></programlisting>
13171389

1318-
<para>
1319-
The result is a list of query results, ordered according to the order of queries
1320-
added to the multi criteria.
1321-
</para>
1390+
<programlisting><![CDATA[using NHibernate.Multi;
13221391
1323-
<para>
1324-
You can add <literal>ICriteria</literal> or <literal>DetachedCriteria</literal> to the Multi Criteria query.
1325-
In fact, using DetachedCriteria in this fashion has some interesting implications.
1326-
</para>
1327-
<programlisting><![CDATA[DetachedCriteria customersCriteria = AuthorizationService.GetAssociatedCustomersQuery();
1328-
IList results = session.CreateMultiCriteria()
1329-
.Add(customersCriteria)
1330-
.Add(DetachedCriteria.For<Policy>()
1331-
.Add(Subqueries.PropertyIn("id",
1332-
CriteriaTransformer.Clone(customersCriteria)
1333-
.SetProjection(Projections.Id())
1334-
)))
1335-
.List();
1392+
...
13361393
1337-
ICollection<Customer> customers = CollectionHelper.ToArray<Customer>(results[0]);
1338-
ICollection<Policy> policies = CollectionHelper.ToArray<Policy>(results[1]);]]></programlisting>
1394+
var queries = s.CreateQueryBatch();
1395+
var list = queries.AddAsFuture(s.Query<Item>().Where(i => i.Id > 50)));
1396+
var countValue = queries.AddAsFutureValue<long>(
1397+
s.CreateQuery("select count(*) from Item i where i.Id > :id")
1398+
.SetInt32("id", 50));
1399+
var count = countValue.Value;
1400+
var items = list.GetEnumerable();]]></programlisting>
13391401

13401402
<para>
1341-
As you see, we get a query that represents the customers we can access, and then we can utilize this query further in order to
1342-
perform additional logic (getting the policies of the customers we are associated with), all in a single database round-trip.
1403+
Futures built from a query batch are executed together the first time the result of one of
1404+
them is accessed. They are independent of futures obtained directly from the queries.
13431405
</para>
1406+
13441407
</sect1>
13451408
</chapter>

src/NHibernate.Test/Async/Criteria/Lambda/IntegrationFixture.cs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using NUnit.Framework;
1717

1818
using NHibernate.Criterion;
19+
using NHibernate.Multi;
1920

2021
namespace NHibernate.Test.Criteria.Lambda
2122
{
@@ -352,7 +353,7 @@ public async Task FunctionsOrderAsync()
352353
}
353354
}
354355

355-
[Test]
356+
[Test, Obsolete]
356357
public async Task MultiCriteriaAsync()
357358
{
358359
var driver = Sfi.ConnectionProvider.Driver;
@@ -408,6 +409,62 @@ public async Task MultiCriteriaAsync()
408409
}
409410
}
410411

412+
[Test]
413+
public async Task MultiQueryAsync()
414+
{
415+
var driver = Sfi.ConnectionProvider.Driver;
416+
if (!driver.SupportsMultipleQueries)
417+
Assert.Ignore("Driver {0} does not support multi-queries", driver.GetType().FullName);
418+
419+
await (SetupPagingDataAsync());
420+
421+
using (var s = OpenSession())
422+
{
423+
var query =
424+
s.QueryOver<Person>()
425+
.JoinQueryOver(p => p.Children)
426+
.OrderBy(c => c.Age).Desc
427+
.Skip(2)
428+
.Take(1);
429+
430+
var multiQuery =
431+
s.CreateQueryBatch()
432+
.Add("page", query)
433+
.Add<int>("count", query.ToRowCountQuery());
434+
435+
var pageResults = await (multiQuery.GetResultAsync<Person>("page", CancellationToken.None));
436+
var countResults = await (multiQuery.GetResultAsync<int>("count", CancellationToken.None));
437+
438+
Assert.That(pageResults.Count, Is.EqualTo(1));
439+
Assert.That(pageResults[0].Name, Is.EqualTo("Name 3"));
440+
Assert.That(countResults.Count, Is.EqualTo(1));
441+
Assert.That(countResults[0], Is.EqualTo(4));
442+
}
443+
444+
using (var s = OpenSession())
445+
{
446+
var query =
447+
QueryOver.Of<Person>()
448+
.JoinQueryOver(p => p.Children)
449+
.OrderBy(c => c.Age).Desc
450+
.Skip(2)
451+
.Take(1);
452+
453+
var multiCriteria =
454+
s.CreateQueryBatch()
455+
.Add("page", query)
456+
.Add<int>("count", query.ToRowCountQuery());
457+
458+
var pageResults = await (multiCriteria.GetResultAsync<Person>("page", CancellationToken.None));
459+
var countResults = await (multiCriteria.GetResultAsync<int>("count", CancellationToken.None));
460+
461+
Assert.That(pageResults.Count, Is.EqualTo(1));
462+
Assert.That(pageResults[0].Name, Is.EqualTo("Name 3"));
463+
Assert.That(countResults.Count, Is.EqualTo(1));
464+
Assert.That(countResults[0], Is.EqualTo(4));
465+
}
466+
}
467+
411468
private async Task SetupPagingDataAsync(CancellationToken cancellationToken = default(CancellationToken))
412469
{
413470
using (ISession s = OpenSession())

0 commit comments

Comments
 (0)