Skip to content

Commit 75b8255

Browse files
committed
Add left join support for Linq query provider
1 parent 7e611e1 commit 75b8255

14 files changed

+696
-51
lines changed

src/NHibernate.Test/Async/Linq/ByMethod/JoinTests.cs

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
using System.Reflection;
1414
using NHibernate.Cfg;
1515
using NHibernate.Engine.Query;
16+
using NHibernate.Linq;
1617
using NHibernate.Util;
1718
using NSubstitute;
1819
using NUnit.Framework;
19-
using NHibernate.Linq;
2020

2121
namespace NHibernate.Test.Linq.ByMethod
2222
{
@@ -27,15 +27,70 @@ public class JoinTestsAsync : LinqTestCase
2727
[Test]
2828
public async Task MultipleLinqJoinsWithSameProjectionNamesAsync()
2929
{
30-
var orders = await (db.Orders
30+
using (var sqlSpy = new SqlLogSpy())
31+
{
32+
var orders = await (db.Orders
3133
.Join(db.Orders, x => x.OrderId, x => x.OrderId - 1, (order, order1) => new { order, order1 })
3234
.Select(x => new { First = x.order, Second = x.order1 })
3335
.Join(db.Orders, x => x.First.OrderId, x => x.OrderId - 2, (order, order1) => new { order, order1 })
3436
.Select(x => new { FirstId = x.order.First.OrderId, SecondId = x.order.Second.OrderId, ThirdId = x.order1.OrderId })
3537
.ToListAsync());
3638

37-
Assert.That(orders.Count, Is.EqualTo(828));
38-
Assert.IsTrue(orders.All(x => x.FirstId == x.SecondId - 1 && x.SecondId == x.ThirdId - 1));
39+
var sql = sqlSpy.GetWholeLog();
40+
Assert.That(orders.Count, Is.EqualTo(828));
41+
Assert.IsTrue(orders.All(x => x.FirstId == x.SecondId - 1 && x.SecondId == x.ThirdId - 1));
42+
Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2));
43+
}
44+
}
45+
46+
[Test]
47+
public async Task MultipleLinqJoinsWithSameProjectionNamesWithLeftJoinAsync()
48+
{
49+
using (var sqlSpy = new SqlLogSpy())
50+
{
51+
var orders = await (db.Orders
52+
.GroupJoin(db.Orders, x => x.OrderId, x => x.OrderId - 1, (order, order1) => new { order, order1 })
53+
.SelectMany(x => x.order1.DefaultIfEmpty(), (x, order1) => new { First = x.order, Second = order1 })
54+
.GroupJoin(db.Orders, x => x.First.OrderId, x => x.OrderId - 2, (order, order1) => new { order, order1 })
55+
.SelectMany(x => x.order1.DefaultIfEmpty(), (x, order1) => new
56+
{
57+
FirstId = x.order.First.OrderId,
58+
SecondId = (int?) x.order.Second.OrderId,
59+
ThirdId = (int?) order1.OrderId
60+
})
61+
.ToListAsync());
62+
63+
var sql = sqlSpy.GetWholeLog();
64+
Assert.That(orders.Count, Is.EqualTo(830));
65+
Assert.IsTrue(orders.Where(x => x.SecondId.HasValue && x.ThirdId.HasValue)
66+
.All(x => x.FirstId == x.SecondId - 1 && x.SecondId == x.ThirdId - 1));
67+
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2));
68+
}
69+
}
70+
71+
[Test]
72+
public async Task MultipleLinqJoinsWithSameProjectionNamesWithLeftJoinExtensionMethodAsync()
73+
{
74+
using (var sqlSpy = new SqlLogSpy())
75+
{
76+
var orders = await (db.Orders
77+
.LeftJoin(db.Orders, x => x.OrderId, x => x.OrderId - 1, (order, order1) => new { order, order1 })
78+
.Select(x => new { First = x.order, Second = x.order1 })
79+
.LeftJoin(db.Orders, x => x.First.OrderId, x => x.OrderId - 2, (order, order1) => new { order, order1 })
80+
.Select(x => new
81+
{
82+
FirstId = x.order.First.OrderId,
83+
SecondId = (int?) x.order.Second.OrderId,
84+
ThirdId = (int?) x.order1.OrderId
85+
})
86+
.ToListAsync());
87+
88+
var sql = sqlSpy.GetWholeLog();
89+
Assert.That(orders.Count, Is.EqualTo(830));
90+
Assert.IsTrue(orders.Where(x => x.SecondId.HasValue && x.ThirdId.HasValue)
91+
.All(x => x.FirstId == x.SecondId - 1 && x.SecondId == x.ThirdId - 1));
92+
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2));
93+
}
3994
}
4095

4196
[TestCase(false)]

src/NHibernate.Test/Async/Linq/LinqQuerySamples.cs

Lines changed: 173 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,26 @@ from o in c.Orders
768768
}
769769
}
770770

771+
[Category("JOIN")]
772+
[Test(Description = "This sample uses foreign key navigation in the " +
773+
"from clause to select all orders for customers in London.")]
774+
public async Task DLinqJoin1LeftJoinAsync()
775+
{
776+
IQueryable<Order> q =
777+
from c in db.Customers
778+
from o in c.Orders.DefaultIfEmpty()
779+
where c.Address.City == "London"
780+
select o;
781+
782+
using (var sqlSpy = new SqlLogSpy())
783+
{
784+
await (ObjectDumper.WriteAsync(q));
785+
786+
var sql = sqlSpy.GetWholeLog();
787+
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1));
788+
}
789+
}
790+
771791
[Category("JOIN")]
772792
[Test(Description = "This sample shows how to construct a join where one side is nullable and the other isn't.")]
773793
public async Task DLinqJoin10Async()
@@ -974,6 +994,26 @@ join o in db.Orders on c.CustomerId equals o.Customer.CustomerId
974994
}
975995
}
976996

997+
[Category("JOIN")]
998+
[Test(Description = "This sample explictly joins two tables and projects results from both tables.")]
999+
public async Task DLinqJoin5aLeftJoinAsync()
1000+
{
1001+
var q =
1002+
from c in db.Customers
1003+
join o in db.Orders on c.CustomerId equals o.Customer.CustomerId into orders
1004+
from o in orders.DefaultIfEmpty()
1005+
where o != null
1006+
select new { c.ContactName, o.OrderId };
1007+
1008+
using (var sqlSpy = new SqlLogSpy())
1009+
{
1010+
await (ObjectDumper.WriteAsync(q));
1011+
1012+
var sql = sqlSpy.GetWholeLog();
1013+
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1));
1014+
}
1015+
}
1016+
9771017
[Category("JOIN")]
9781018
[Test(Description = "This sample explictly joins two tables and projects results from both tables using a group join.")]
9791019
public async Task DLinqJoin5bAsync()
@@ -1032,6 +1072,21 @@ join o in db.Orders on
10321072
}
10331073
}
10341074

1075+
[Category("JOIN")]
1076+
[Test(Description = "This sample explictly joins two tables with a composite key and projects results from both tables.")]
1077+
public void DLinqJoin5dLeftJoinAsync()
1078+
{
1079+
var q =
1080+
from c in db.Customers
1081+
join o in db.Orders on
1082+
new { c.CustomerId, HasContractTitle = c.ContactTitle != null } equals
1083+
new { o.Customer.CustomerId, HasContractTitle = o.Customer.ContactTitle != null } into orders
1084+
from o in orders.DefaultIfEmpty()
1085+
select new { c.ContactName, o.OrderId };
1086+
1087+
Assert.ThrowsAsync<NotSupportedException>(() => ObjectDumper.WriteAsync(q));
1088+
}
1089+
10351090
[Category("JOIN")]
10361091
[Test(Description = "This sample joins two tables and projects results from the first table.")]
10371092
public async Task DLinqJoin5eAsync()
@@ -1051,6 +1106,26 @@ join o in db.Orders on c.CustomerId equals o.Customer.CustomerId
10511106
}
10521107
}
10531108

1109+
[Category("JOIN")]
1110+
[Test(Description = "This sample joins two tables and projects results from the first table.")]
1111+
public async Task DLinqJoin5eLeftJoinAsync()
1112+
{
1113+
var q =
1114+
from c in db.Customers
1115+
join o in db.Orders on c.CustomerId equals o.Customer.CustomerId into orders
1116+
from o in orders.DefaultIfEmpty()
1117+
where c.ContactName != null
1118+
select o;
1119+
1120+
using (var sqlSpy = new SqlLogSpy())
1121+
{
1122+
await (ObjectDumper.WriteAsync(q));
1123+
1124+
var sql = sqlSpy.GetWholeLog();
1125+
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1));
1126+
}
1127+
}
1128+
10541129
[Category("JOIN")]
10551130
[TestCase(Description = "This sample explictly joins two tables with a composite key and projects results from both tables.")]
10561131
public async Task DLinqJoin5fAsync()
@@ -1073,21 +1148,24 @@ join c in db.Customers on
10731148
}
10741149

10751150
[Category("JOIN")]
1076-
[Test(Description = "This sample joins two tables and projects results from the first table.")]
1077-
public async Task DLinqJoin5eAsync()
1151+
[TestCase(Description = "This sample explictly joins two tables with a composite key and projects results from both tables.")]
1152+
public async Task DLinqJoin5fLeftJoinAsync()
10781153
{
10791154
var q =
1080-
from c in db.Customers
1081-
join o in db.Orders on c.CustomerId equals o.Customer.CustomerId
1082-
where c.ContactName != null
1083-
select o;
1155+
from o in db.Orders
1156+
join c in db.Customers on
1157+
new { o.Customer.CustomerId, HasContractTitle = o.Customer.ContactTitle != null } equals
1158+
new { c.CustomerId, HasContractTitle = c.ContactTitle != null } into customers
1159+
from c in customers.DefaultIfEmpty()
1160+
select new { c.ContactName, o.OrderId };
10841161

10851162
using (var sqlSpy = new SqlLogSpy())
10861163
{
10871164
await (ObjectDumper.WriteAsync(q));
10881165

10891166
var sql = sqlSpy.GetWholeLog();
1090-
Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(1));
1167+
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2));
1168+
Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(0));
10911169
}
10921170
}
10931171

@@ -1113,6 +1191,28 @@ join e in db.Employees on c.Address.City equals e.Address.City into emps
11131191
}
11141192
}
11151193

1194+
[Category("JOIN")]
1195+
[Test(
1196+
Description =
1197+
"This sample shows how to get LEFT OUTER JOIN by using DefaultIfEmpty(). The DefaultIfEmpty() method returns null when there is no Order for the Employee."
1198+
)]
1199+
public async Task DLinqJoin7Async()
1200+
{
1201+
var q =
1202+
from e in db.Employees
1203+
join o in db.Orders on e equals o.Employee into ords
1204+
from o in ords.DefaultIfEmpty()
1205+
select new {e.FirstName, e.LastName, Order = o};
1206+
1207+
using (var sqlSpy = new SqlLogSpy())
1208+
{
1209+
await (ObjectDumper.WriteAsync(q));
1210+
1211+
var sql = sqlSpy.GetWholeLog();
1212+
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1));
1213+
}
1214+
}
1215+
11161216
[Category("JOIN")]
11171217
[Test(Description = "This sample projects a 'let' expression resulting from a join.")]
11181218
public async Task DLinqJoin8Async()
@@ -1175,6 +1275,51 @@ from d in details
11751275
}
11761276
}
11771277

1278+
[Category("JOIN")]
1279+
[TestCase(true, Description = "This sample shows a group left join with a composite key.")]
1280+
[TestCase(false, Description = "This sample shows a group left join with a composite key.")]
1281+
public async Task DLinqJoin9LeftJoinAsync(bool useCrossJoin)
1282+
{
1283+
if (useCrossJoin && !Dialect.SupportsCrossJoin)
1284+
{
1285+
Assert.Ignore("Dialect does not support cross join.");
1286+
}
1287+
1288+
ICollection expected, actual;
1289+
expected =
1290+
(from o in db.Orders.ToList()
1291+
from p in db.Products.ToList()
1292+
join d in db.OrderLines.ToList()
1293+
on new {o.OrderId, p.ProductId} equals new {d.Order.OrderId, d.Product.ProductId}
1294+
into details
1295+
from d in details.DefaultIfEmpty()
1296+
where d != null && d.UnitPrice > 50
1297+
select new {o.OrderId, p.ProductId, d.UnitPrice}).ToList();
1298+
1299+
using (var substitute = SubstituteDialect())
1300+
using (var sqlSpy = new SqlLogSpy())
1301+
{
1302+
ClearQueryPlanCache();
1303+
substitute.Value.SupportsCrossJoin.Returns(useCrossJoin);
1304+
1305+
actual =
1306+
await ((from o in db.Orders
1307+
from p in db.Products
1308+
join d in db.OrderLines
1309+
on new {o.OrderId, p.ProductId} equals new {d.Order.OrderId, d.Product.ProductId}
1310+
into details
1311+
from d in details.DefaultIfEmpty()
1312+
where d != null && d.UnitPrice > 50
1313+
select new {o.OrderId, p.ProductId, d.UnitPrice}).ToListAsync());
1314+
1315+
var sql = sqlSpy.GetWholeLog();
1316+
Assert.That(sql, Does.Contain(useCrossJoin ? "cross join" : "inner join"));
1317+
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1));
1318+
}
1319+
1320+
Assert.AreEqual(expected.Count, actual.Count);
1321+
}
1322+
11781323
[Category("JOIN")]
11791324
[Test(Description = "This sample shows a join which is then grouped")]
11801325
public async Task DLinqJoin9bAsync()
@@ -1205,5 +1350,26 @@ join s2 in db.Employees on s.Superior.EmployeeId equals s2.EmployeeId
12051350
Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2));
12061351
}
12071352
}
1353+
1354+
[Category("JOIN")]
1355+
[Test(Description = "This sample shows how to join multiple tables using a left join.")]
1356+
public async Task DLinqJoin10aLeftJoinAsync()
1357+
{
1358+
var q =
1359+
from e in db.Employees
1360+
join s in db.Employees on e.Superior.EmployeeId equals s.EmployeeId into sup
1361+
from s in sup.DefaultIfEmpty()
1362+
join s2 in db.Employees on s.Superior.EmployeeId equals s2.EmployeeId into sup2
1363+
from s2 in sup2.DefaultIfEmpty()
1364+
select new { e.FirstName, SuperiorName = s.FirstName, Superior2Name = s2.FirstName };
1365+
1366+
using (var sqlSpy = new SqlLogSpy())
1367+
{
1368+
await (ObjectDumper.WriteAsync(q));
1369+
1370+
var sql = sqlSpy.GetWholeLog();
1371+
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2));
1372+
}
1373+
}
12081374
}
12091375
}

0 commit comments

Comments
 (0)