Skip to content

Commit c8e1129

Browse files
authored
Don't join fkey table when linq comparison with composite id (#3629)
1 parent 67d06ee commit c8e1129

29 files changed

+334
-21
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*.cmd text
1212
*.msbuild text
1313
*.md text
14+
*.sql text
1415

1516
*.sln text eol=crlf
1617
*.csproj text eol=crlf

src/NHibernate.DomainModel/NHibernate.DomainModel.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@
1515
<ItemGroup>
1616
<ProjectReference Include="..\NHibernate\NHibernate.csproj" />
1717
</ItemGroup>
18+
<ItemGroup>
19+
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.1" />
20+
</ItemGroup>
1821
</Project>

src/NHibernate.DomainModel/Northwind/Entities/AnotherEntity.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ public class AnotherEntity
44
{
55
public virtual int Id { get; set; }
66
public virtual string Output { get; set; }
7-
public virtual string Input { get; set; }
7+
public virtual string Input { get; set; }
8+
public virtual CompositeIdEntity CompositeIdEntity { get; set; }
89
}
9-
}
10+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
3+
namespace NHibernate.DomainModel.Northwind.Entities
4+
{
5+
public class CompositeId : IComparable<CompositeId>
6+
{
7+
public int ObjectId { get; set; }
8+
public int TenantId { get; set; }
9+
10+
public CompositeId() { }
11+
public CompositeId(int objectId, int tenantId)
12+
{
13+
ObjectId = objectId;
14+
TenantId = tenantId;
15+
}
16+
17+
public override string ToString() => ObjectId + "|" + TenantId;
18+
protected bool Equals(CompositeId other) => ObjectId == other.ObjectId && TenantId == other.TenantId;
19+
public static bool operator ==(CompositeId left, CompositeId right) => Equals(left, right);
20+
public static bool operator !=(CompositeId left, CompositeId right) => !Equals(left, right);
21+
22+
public override bool Equals(object obj)
23+
{
24+
if (ReferenceEquals(null, obj) || obj.GetType() != this.GetType())
25+
{
26+
return false;
27+
}
28+
return ReferenceEquals(this, obj) || Equals((CompositeId)obj);
29+
}
30+
31+
public override int GetHashCode() => HashCode.Combine(ObjectId, TenantId);
32+
33+
public int CompareTo(CompositeId other)
34+
{
35+
if (ReferenceEquals(this, other))
36+
{
37+
return 0;
38+
}
39+
else if (ReferenceEquals(other, null))
40+
{
41+
return 1;
42+
}
43+
44+
var idComparison = ObjectId.CompareTo(other.ObjectId);
45+
if (idComparison != 0)
46+
{
47+
return idComparison;
48+
}
49+
50+
return TenantId.CompareTo(other);
51+
}
52+
}
53+
public class CompositeIdEntity
54+
{
55+
public virtual CompositeId Id { get; set; }
56+
public virtual string Name { get; set; }
57+
}
58+
}

src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@ public IQueryable<Timesheet> Timesheets
5959
get { return _session.Query<Timesheet>(); }
6060
}
6161

62-
public IQueryable<Animal> Animals
63-
{
64-
get { return _session.Query<Animal>(); }
65-
}
62+
public IQueryable<Animal> Animals
63+
{
64+
get { return _session.Query<Animal>(); }
65+
}
6666

6767
public IQueryable<Mammal> Mammals
6868
{
@@ -113,5 +113,10 @@ public IQueryable<IUser> IUsers
113113
{
114114
get { return _session.Query<IUser>(); }
115115
}
116+
117+
public IQueryable<AnotherEntity> AnotherEntity
118+
{
119+
get { return _session.Query<AnotherEntity>(); }
120+
}
116121
}
117122
}

src/NHibernate.DomainModel/Northwind/Mappings/AnotherEntity.hbm.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@
66
</id>
77
<property name="Output" />
88
<property name="Input" />
9+
<many-to-one name="CompositeIdEntity" fetch="select">
10+
<column name="CompositeObjectId" />
11+
<column name="CompositeTenantId" />
12+
</many-to-one>
913
</class>
1014
</hibernate-mapping>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHibernate.DomainModel.Northwind.Entities" assembly="NHibernate.DomainModel">
3+
<class name="CompositeIdEntity" table="CompositeIdEntity">
4+
<composite-id name="Id">
5+
<key-property name="ObjectId" column="ObjectId" />
6+
<key-property name="TenantId" column="TenantId" />
7+
</composite-id>
8+
<property name="Name" length="128" />
9+
</class>
10+
</hibernate-mapping>

src/NHibernate.Test/Async/CompositeId/CompositeIdFixture.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
namespace NHibernate.Test.CompositeId
1919
{
2020
using System.Threading.Tasks;
21+
using System.Threading;
2122
[TestFixture]
2223
public class CompositeIdFixtureAsync : TestCase
2324
{
@@ -33,7 +34,7 @@ protected override string[] Mappings
3334
return new string[]
3435
{
3536
"CompositeId.Customer.hbm.xml", "CompositeId.Order.hbm.xml", "CompositeId.LineItem.hbm.xml",
36-
"CompositeId.Product.hbm.xml"
37+
"CompositeId.Product.hbm.xml", "CompositeId.Shipper.hbm.xml"
3738
};
3839
}
3940
}
@@ -76,9 +77,13 @@ public async Task CompositeIdsAsync()
7677

7778
Order o = new Order(c);
7879
o.OrderDate = DateTime.Today;
80+
o.Shipper = new Shipper() { Id = new NullableId(null, 13) };
81+
await (s.PersistAsync(o));
82+
7983
LineItem li = new LineItem(o, p);
8084
li.Quantity = 2;
81-
85+
await (s.PersistAsync(li));
86+
8287
await (t.CommitAsync());
8388
}
8489

@@ -135,6 +140,19 @@ public async Task CompositeIdsAsync()
135140
await (t.CommitAsync());
136141
}
137142

143+
using (s = OpenSession())
144+
{
145+
t = s.BeginTransaction();
146+
var noShippersForWarehouse = await (s.Query<Order>()
147+
// NOTE: .Where(x => x.Shipper.Id == new NullableId(null, 13)) improperly renders
148+
// "where (ShipperId = @p1 and WarehouseId = @p2)" with @p1 = NULL (needs to be is null)
149+
// But the effort to fix is pretty high due to how component tuples are managed in linq / hql.
150+
.Where(x => x.Shipper.Id.WarehouseId == 13 && x.Shipper.Id.Id == null)
151+
.ToListAsync());
152+
Assert.AreEqual(1, noShippersForWarehouse.Count);
153+
await (t.CommitAsync());
154+
}
155+
138156
using (s = OpenSession())
139157
{
140158
t = s.BeginTransaction();
@@ -303,5 +321,14 @@ public async Task AnyOnCompositeIdAsync()
303321
await (s.Query<Order>().Select(o => o.LineItems.Any()).ToListAsync());
304322
}
305323
}
324+
325+
public async Task NullCompositeIdAsync(CancellationToken cancellationToken = default(CancellationToken))
326+
{
327+
using (var s = OpenSession())
328+
{
329+
await (s.Query<Order>().Where(o => o.LineItems.Any()).ToListAsync(cancellationToken));
330+
await (s.Query<Order>().Select(o => o.LineItems.Any()).ToListAsync(cancellationToken));
331+
}
332+
}
306333
}
307334
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,17 @@ public async Task OrderLinesWithSelectingCustomerNameInCaseShouldProduceTwoJoins
297297
Assert.That(countJoins, Is.EqualTo(2));
298298
}
299299
}
300+
301+
[Test]
302+
public async Task ShouldConstipateJoinsWhenOnlyComparingCompositeIdPropertiesAsync()
303+
{
304+
using (var spy = new SqlLogSpy())
305+
{
306+
await (db.AnotherEntity.Where(x => x.CompositeIdEntity.Id.TenantId == 3).ToListAsync());
307+
var countJoins = CountJoins(spy);
308+
Assert.That(countJoins, Is.EqualTo(0));
309+
}
310+
}
300311

301312
private static int CountJoins(LogSpy sqlLog)
302313
{

src/NHibernate.Test/Async/QueryTranslator/CustomQueryLoaderFixture.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ internal sealed class CustomQueryLoaderFixtureAsync : TestCase
4242
"Northwind.Mappings.TimeSheet.hbm.xml",
4343
"Northwind.Mappings.Animal.hbm.xml",
4444
"Northwind.Mappings.Patient.hbm.xml",
45-
"Northwind.Mappings.NumericEntity.hbm.xml"
45+
"Northwind.Mappings.NumericEntity.hbm.xml",
46+
"Northwind.Mappings.CompositeIdEntity.hbm.xml"
4647
};
4748

4849
protected override string MappingsAssembly => "NHibernate.DomainModel";

0 commit comments

Comments
 (0)