Skip to content

Commit ec74061

Browse files
author
Jeff Hagen
committed
#3628 - Don't join when linq comparison with composite id
1 parent 0c9e442 commit ec74061

23 files changed

+276
-9
lines changed

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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ public class AnotherEntity
55
public virtual int Id { get; set; }
66
public virtual string Output { get; set; }
77
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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/CompositeId/CompositeIdFixture.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ protected override string[] Mappings
2121
return new string[]
2222
{
2323
"CompositeId.Customer.hbm.xml", "CompositeId.Order.hbm.xml", "CompositeId.LineItem.hbm.xml",
24-
"CompositeId.Product.hbm.xml"
24+
"CompositeId.Product.hbm.xml", "CompositeId.Shipper.hbm.xml"
2525
};
2626
}
2727
}
@@ -64,9 +64,13 @@ public void CompositeIds()
6464

6565
Order o = new Order(c);
6666
o.OrderDate = DateTime.Today;
67+
o.Shipper = new Shipper() { Id = new NullableId(null, 13) };
68+
s.Persist(o);
69+
6770
LineItem li = new LineItem(o, p);
6871
li.Quantity = 2;
69-
72+
s.Persist(li);
73+
7074
t.Commit();
7175
}
7276

@@ -123,6 +127,19 @@ public void CompositeIds()
123127
t.Commit();
124128
}
125129

130+
using (s = OpenSession())
131+
{
132+
t = s.BeginTransaction();
133+
var noShippersForWarehouse = s.Query<Order>()
134+
// NOTE: .Where(x => x.Shipper.Id == new NullableId(null, 13)) improperly renders
135+
// "where (ShipperId = @p1 and WarehouseId = @p2)" with @p1 = NULL (needs to be is null)
136+
// But the effort to fix is pretty high due to how component tuples are managed in linq / hql.
137+
.Where(x => x.Shipper.Id.WarehouseId == 13 && x.Shipper.Id.Id == null)
138+
.ToList();
139+
Assert.AreEqual(1, noShippersForWarehouse.Count);
140+
t.Commit();
141+
}
142+
126143
using (s = OpenSession())
127144
{
128145
t = s.BeginTransaction();
@@ -291,5 +308,14 @@ public void AnyOnCompositeId()
291308
s.Query<Order>().Select(o => o.LineItems.Any()).ToList();
292309
}
293310
}
311+
312+
public void NullCompositeId()
313+
{
314+
using (var s = OpenSession())
315+
{
316+
s.Query<Order>().Where(o => o.LineItems.Any()).ToList();
317+
s.Query<Order>().Select(o => o.LineItems.Any()).ToList();
318+
}
319+
}
294320
}
295321
}
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.Test.CompositeId
4+
{
5+
public class NullableId : IComparable<NullableId>
6+
{
7+
public int? Id { get; set; }
8+
public int WarehouseId { get; set; }
9+
10+
public NullableId() { }
11+
public NullableId(int? id, int warehouseId)
12+
{
13+
Id = id;
14+
WarehouseId = warehouseId;
15+
}
16+
17+
public override string ToString() => Id + "|" + WarehouseId;
18+
protected bool Equals(NullableId other) => Id == other.Id && WarehouseId == other.WarehouseId;
19+
public static bool operator ==(NullableId left, NullableId right) => Equals(left, right);
20+
public static bool operator !=(NullableId left, NullableId 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+
29+
return ReferenceEquals(this, obj) || Equals((NullableId)obj);
30+
}
31+
32+
public override int GetHashCode() => HashCode.Combine(Id, WarehouseId);
33+
34+
public int CompareTo(NullableId other)
35+
{
36+
if (ReferenceEquals(this, other))
37+
{
38+
return 0;
39+
}
40+
else if (ReferenceEquals(other, null) || !other.Id.HasValue)
41+
{
42+
return 1;
43+
}
44+
else if (!Id.HasValue)
45+
{
46+
return -1;
47+
}
48+
49+
var idComparison = Id.Value.CompareTo(other.Id);
50+
if (idComparison != 0)
51+
{
52+
return idComparison;
53+
}
54+
55+
return WarehouseId.CompareTo(other.WarehouseId);
56+
}
57+
}
58+
}

src/NHibernate.Test/CompositeId/Order.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public override int GetHashCode()
4848
private Customer customer;
4949
private IList<LineItem> lineItems = new List<LineItem>();
5050
private decimal total;
51+
private Shipper shipper;
5152

5253
public Order() {}
5354
public Order(Customer customer)
@@ -87,6 +88,12 @@ public virtual decimal Total
8788
get { return total; }
8889
set { total = value; }
8990
}
91+
92+
public virtual Shipper Shipper
93+
{
94+
get { return shipper; }
95+
set { shipper = value; }
96+
}
9097

9198
public virtual LineItem GenerateLineItem(Product product, int quantity)
9299
{
@@ -96,4 +103,4 @@ public virtual LineItem GenerateLineItem(Product product, int quantity)
96103
return li;
97104
}
98105
}
99-
}
106+
}

src/NHibernate.Test/CompositeId/Order.hbm.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
insert="false"
4444
update="false"
4545
not-null="true"/>
46+
47+
<many-to-one name="Shipper" fetch="select">
48+
<column name="ShipperId" not-null="false"/>
49+
<column name="WarehouseId" />
50+
</many-to-one>
4651

4752
<bag name="LineItems"
4853
fetch="join"

0 commit comments

Comments
 (0)