Skip to content

Add left join support for Linq query provider #2328

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 80 additions & 4 deletions src/NHibernate.Test/Async/Linq/ByMethod/JoinTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
using System.Reflection;
using NHibernate.Cfg;
using NHibernate.Engine.Query;
using NHibernate.Linq;
using NHibernate.Util;
using NSubstitute;
using NUnit.Framework;
using NHibernate.Linq;

namespace NHibernate.Test.Linq.ByMethod
{
Expand All @@ -27,15 +27,91 @@ public class JoinTestsAsync : LinqTestCase
[Test]
public async Task MultipleLinqJoinsWithSameProjectionNamesAsync()
{
var orders = await (db.Orders
using (var sqlSpy = new SqlLogSpy())
{
var orders = await (db.Orders
.Join(db.Orders, x => x.OrderId, x => x.OrderId - 1, (order, order1) => new { order, order1 })
.Select(x => new { First = x.order, Second = x.order1 })
.Join(db.Orders, x => x.First.OrderId, x => x.OrderId - 2, (order, order1) => new { order, order1 })
.Select(x => new { FirstId = x.order.First.OrderId, SecondId = x.order.Second.OrderId, ThirdId = x.order1.OrderId })
.ToListAsync());

Assert.That(orders.Count, Is.EqualTo(828));
Assert.IsTrue(orders.All(x => x.FirstId == x.SecondId - 1 && x.SecondId == x.ThirdId - 1));
var sql = sqlSpy.GetWholeLog();
Assert.That(orders.Count, Is.EqualTo(828));
Assert.IsTrue(orders.All(x => x.FirstId == x.SecondId - 1 && x.SecondId == x.ThirdId - 1));
Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2));
}
}

[Test]
public async Task MultipleLinqJoinsWithSameProjectionNamesWithLeftJoinAsync()
{
using (var sqlSpy = new SqlLogSpy())
{
var orders = await (db.Orders
.GroupJoin(db.Orders, x => x.OrderId, x => x.OrderId - 1, (order, order1) => new { order, order1 })
.SelectMany(x => x.order1.DefaultIfEmpty(), (x, order1) => new { First = x.order, Second = order1 })
.GroupJoin(db.Orders, x => x.First.OrderId, x => x.OrderId - 2, (order, order1) => new { order, order1 })
.SelectMany(x => x.order1.DefaultIfEmpty(), (x, order1) => new
{
FirstId = x.order.First.OrderId,
SecondId = (int?) x.order.Second.OrderId,
ThirdId = (int?) order1.OrderId
})
.ToListAsync());

var sql = sqlSpy.GetWholeLog();
Assert.That(orders.Count, Is.EqualTo(830));
Assert.IsTrue(orders.Where(x => x.SecondId.HasValue && x.ThirdId.HasValue)
.All(x => x.FirstId == x.SecondId - 1 && x.SecondId == x.ThirdId - 1));
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2));
}
}

[Test]
public async Task MultipleLinqJoinsWithSameProjectionNamesWithLeftJoinExtensionMethodAsync()
{
using (var sqlSpy = new SqlLogSpy())
{
var orders = await (db.Orders
.LeftJoin(db.Orders, x => x.OrderId, x => x.OrderId - 1, (order, order1) => new { order, order1 })
.Select(x => new { First = x.order, Second = x.order1 })
.LeftJoin(db.Orders, x => x.First.OrderId, x => x.OrderId - 2, (order, order1) => new { order, order1 })
.Select(x => new
{
FirstId = x.order.First.OrderId,
SecondId = (int?) x.order.Second.OrderId,
ThirdId = (int?) x.order1.OrderId
})
.ToListAsync());

var sql = sqlSpy.GetWholeLog();
Assert.That(orders.Count, Is.EqualTo(830));
Assert.IsTrue(orders.Where(x => x.SecondId.HasValue && x.ThirdId.HasValue)
.All(x => x.FirstId == x.SecondId - 1 && x.SecondId == x.ThirdId - 1));
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2));
}
}

[Test]
public async Task LeftJoinExtensionMethodWithMultipleKeyPropertiesAsync()
{
using (var sqlSpy = new SqlLogSpy())
{
var orders = await (db.Orders
.LeftJoin(
db.Orders,
x => new {x.OrderId, x.Customer.CustomerId},
x => new {x.OrderId, x.Customer.CustomerId},
(order, order1) => new {order, order1})
.Select(x => new {FirstId = x.order.OrderId, SecondId = x.order1.OrderId})
.ToListAsync());

var sql = sqlSpy.GetWholeLog();
Assert.That(orders.Count, Is.EqualTo(830));
Assert.IsTrue(orders.All(x => x.FirstId == x.SecondId));
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1));
}
}

[TestCase(false)]
Expand Down
184 changes: 184 additions & 0 deletions src/NHibernate.Test/Async/Linq/LinqQuerySamples.cs
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,26 @@ from o in c.Orders
}
}

[Category("JOIN")]
[Test(Description = "This sample uses foreign key navigation in the " +
"from clause to select all orders for customers in London.")]
public async Task DLinqJoin1LeftJoinAsync()
{
IQueryable<Order> q =
from c in db.Customers
from o in c.Orders.DefaultIfEmpty()
where c.Address.City == "London"
select o;

using (var sqlSpy = new SqlLogSpy())
{
await (ObjectDumper.WriteAsync(q));

var sql = sqlSpy.GetWholeLog();
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1));
}
}

[Category("JOIN")]
[Test(Description = "This sample shows how to construct a join where one side is nullable and the other isn't.")]
public async Task DLinqJoin10Async()
Expand Down Expand Up @@ -974,6 +994,26 @@ join o in db.Orders on c.CustomerId equals o.Customer.CustomerId
}
}

[Category("JOIN")]
[Test(Description = "This sample explictly joins two tables and projects results from both tables.")]
public async Task DLinqJoin5aLeftJoinAsync()
{
var q =
from c in db.Customers
join o in db.Orders on c.CustomerId equals o.Customer.CustomerId into orders
from o in orders.DefaultIfEmpty()
where o != null
select new { c.ContactName, o.OrderId };

using (var sqlSpy = new SqlLogSpy())
{
await (ObjectDumper.WriteAsync(q));

var sql = sqlSpy.GetWholeLog();
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1));
}
}

[Category("JOIN")]
[Test(Description = "This sample explictly joins two tables and projects results from both tables using a group join.")]
public async Task DLinqJoin5bAsync()
Expand Down Expand Up @@ -1032,6 +1072,21 @@ join o in db.Orders on
}
}

[Category("JOIN")]
[Test(Description = "This sample explictly joins two tables with a composite key and projects results from both tables.")]
public void DLinqJoin5dLeftJoinAsync()
{
var q =
from c in db.Customers
join o in db.Orders on
new { c.CustomerId, HasContractTitle = c.ContactTitle != null } equals
new { o.Customer.CustomerId, HasContractTitle = o.Customer.ContactTitle != null } into orders
from o in orders.DefaultIfEmpty()
select new { c.ContactName, o.OrderId };

Assert.ThrowsAsync<NotSupportedException>(() => ObjectDumper.WriteAsync(q));
}

[Category("JOIN")]
[Test(Description = "This sample joins two tables and projects results from the first table.")]
public async Task DLinqJoin5eAsync()
Expand All @@ -1051,6 +1106,26 @@ join o in db.Orders on c.CustomerId equals o.Customer.CustomerId
}
}

[Category("JOIN")]
[Test(Description = "This sample joins two tables and projects results from the first table.")]
public async Task DLinqJoin5eLeftJoinAsync()
{
var q =
from c in db.Customers
join o in db.Orders on c.CustomerId equals o.Customer.CustomerId into orders
from o in orders.DefaultIfEmpty()
where c.ContactName != null
select o;

using (var sqlSpy = new SqlLogSpy())
{
await (ObjectDumper.WriteAsync(q));

var sql = sqlSpy.GetWholeLog();
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1));
}
}

[Category("JOIN")]
[TestCase(Description = "This sample explictly joins two tables with a composite key and projects results from both tables.")]
public async Task DLinqJoin5fAsync()
Expand All @@ -1072,6 +1147,28 @@ join c in db.Customers on
}
}

[Category("JOIN")]
[TestCase(Description = "This sample explictly joins two tables with a composite key and projects results from both tables.")]
public async Task DLinqJoin5fLeftJoinAsync()
{
var q =
from o in db.Orders
join c in db.Customers on
new { o.Customer.CustomerId, HasContractTitle = o.Customer.ContactTitle != null } equals
new { c.CustomerId, HasContractTitle = c.ContactTitle != null } into customers
from c in customers.DefaultIfEmpty()
select new { c.ContactName, o.OrderId };

using (var sqlSpy = new SqlLogSpy())
{
await (ObjectDumper.WriteAsync(q));

var sql = sqlSpy.GetWholeLog();
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2));
Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(0));
}
}

[Category("JOIN")]
[Test(Description = "This sample explictly joins three tables and projects results from each of them.")]
public async Task DLinqJoin6Async()
Expand All @@ -1094,6 +1191,28 @@ join e in db.Employees on c.Address.City equals e.Address.City into emps
}
}

[Category("JOIN")]
[Test(
Description =
"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."
)]
public async Task DLinqJoin7Async()
{
var q =
from e in db.Employees
join o in db.Orders on e equals o.Employee into ords
from o in ords.DefaultIfEmpty()
select new {e.FirstName, e.LastName, Order = o};

using (var sqlSpy = new SqlLogSpy())
{
await (ObjectDumper.WriteAsync(q));

var sql = sqlSpy.GetWholeLog();
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1));
}
}

[Category("JOIN")]
[Test(Description = "This sample projects a 'let' expression resulting from a join.")]
public async Task DLinqJoin8Async()
Expand Down Expand Up @@ -1156,6 +1275,50 @@ from d in details
}
}

[Category("JOIN")]
[TestCase(true, Description = "This sample shows a group left join with a composite key.")]
[TestCase(false, Description = "This sample shows a group left join with a composite key.")]
public async Task DLinqJoin9LeftJoinAsync(bool useCrossJoin)
{
if (useCrossJoin && !Dialect.SupportsCrossJoin)
{
Assert.Ignore("Dialect does not support cross join.");
}

// The expected collection can be obtained from the below Linq to Objects query.
//var expected =
// (from o in db.Orders.ToList()
// from p in db.Products.ToList()
// join d in db.OrderLines.ToList()
// on new { o.OrderId, p.ProductId } equals new { d.Order.OrderId, d.Product.ProductId }
// into details
// from d in details.DefaultIfEmpty()
// where d != null && d.UnitPrice > 50
// select new { o.OrderId, p.ProductId, d.UnitPrice }).ToList();

using (var substitute = SubstituteDialect())
using (var sqlSpy = new SqlLogSpy())
{
ClearQueryPlanCache();
substitute.Value.SupportsCrossJoin.Returns(useCrossJoin);

var actual =
await ((from o in db.Orders
from p in db.Products
join d in db.OrderLines
on new {o.OrderId, p.ProductId} equals new {d.Order.OrderId, d.Product.ProductId}
into details
from d in details.DefaultIfEmpty()
where d != null && d.UnitPrice > 50
select new {o.OrderId, p.ProductId, d.UnitPrice}).ToListAsync());

var sql = sqlSpy.GetWholeLog();
Assert.That(actual.Count, Is.EqualTo(163));
Assert.That(sql, Does.Contain(useCrossJoin ? "cross join" : "inner join"));
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1));
}
}

[Category("JOIN")]
[Test(Description = "This sample shows a join which is then grouped")]
public async Task DLinqJoin9bAsync()
Expand Down Expand Up @@ -1186,5 +1349,26 @@ join s2 in db.Employees on s.Superior.EmployeeId equals s2.EmployeeId
Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2));
}
}

[Category("JOIN")]
[Test(Description = "This sample shows how to join multiple tables using a left join.")]
public async Task DLinqJoin10aLeftJoinAsync()
{
var q =
from e in db.Employees
join s in db.Employees on e.Superior.EmployeeId equals s.EmployeeId into sup
from s in sup.DefaultIfEmpty()
join s2 in db.Employees on s.Superior.EmployeeId equals s2.EmployeeId into sup2
from s2 in sup2.DefaultIfEmpty()
select new { e.FirstName, SuperiorName = s.FirstName, Superior2Name = s2.FirstName };

using (var sqlSpy = new SqlLogSpy())
{
await (ObjectDumper.WriteAsync(q));

var sql = sqlSpy.GetWholeLog();
Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2));
}
}
}
}
Loading