Skip to content

Commit e1f5b61

Browse files
committed
Add left join support for Linq query provider
1 parent 4cee217 commit e1f5b61

14 files changed

+720
-37
lines changed

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

+59-4
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

+185
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()
@@ -1030,6 +1070,21 @@ join o in db.Orders on
10301070
}
10311071
}
10321072

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

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

1148+
[Category("JOIN")]
1149+
[TestCase(Description = "This sample explictly joins two tables with a composite key and projects results from both tables.")]
1150+
public async Task DLinqJoin5fLeftJoinAsync()
1151+
{
1152+
var q =
1153+
from o in db.Orders
1154+
join c in db.Customers on
1155+
new { o.Customer.CustomerId, HasContractTitle = o.Customer.ContactTitle != null } equals
1156+
new { c.CustomerId, HasContractTitle = c.ContactTitle != null } into customers
1157+
from c in customers.DefaultIfEmpty()
1158+
select new { c.ContactName, o.OrderId };
1159+
1160+
using (var sqlSpy = new SqlLogSpy())
1161+
{
1162+
await (ObjectDumper.WriteAsync(q));
1163+
1164+
var sql = sqlSpy.GetWholeLog();
1165+
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2));
1166+
Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(0));
1167+
}
1168+
}
1169+
10731170
[Category("JOIN")]
10741171
[Test(Description = "This sample explictly joins three tables and projects results from each of them.")]
10751172
public async Task DLinqJoin6Async()
@@ -1092,6 +1189,28 @@ join e in db.Employees on c.Address.City equals e.Address.City into emps
10921189
}
10931190
}
10941191

1192+
[Category("JOIN")]
1193+
[Test(
1194+
Description =
1195+
"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."
1196+
)]
1197+
public async Task DLinqJoin7Async()
1198+
{
1199+
var q =
1200+
from e in db.Employees
1201+
join o in db.Orders on e equals o.Employee into ords
1202+
from o in ords.DefaultIfEmpty()
1203+
select new {e.FirstName, e.LastName, Order = o};
1204+
1205+
using (var sqlSpy = new SqlLogSpy())
1206+
{
1207+
await (ObjectDumper.WriteAsync(q));
1208+
1209+
var sql = sqlSpy.GetWholeLog();
1210+
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1));
1211+
}
1212+
}
1213+
10951214
[Category("JOIN")]
10961215
[Test(Description = "This sample projects a 'let' expression resulting from a join.")]
10971216
public async Task DLinqJoin8Async()
@@ -1156,6 +1275,51 @@ from d in details
11561275
Assert.AreEqual(expected.Count, actual.Count);
11571276
}
11581277

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+
11591323
[Category("JOIN")]
11601324
[Test(Description = "This sample shows a join which is then grouped")]
11611325
public async Task DLinqJoin9bAsync()
@@ -1186,5 +1350,26 @@ join s2 in db.Employees on s.Superior.EmployeeId equals s2.EmployeeId
11861350
Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2));
11871351
}
11881352
}
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+
}
11891374
}
11901375
}

0 commit comments

Comments
 (0)