Skip to content

Don't join fkey table when linq comparison with composite id #3629

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

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@
*.cmd text
*.msbuild text
*.md text
*.sql text

*.sln text eol=crlf
*.csproj text eol=crlf
3 changes: 3 additions & 0 deletions src/NHibernate.DomainModel/NHibernate.DomainModel.csproj
Original file line number Diff line number Diff line change
@@ -15,4 +15,7 @@
<ItemGroup>
<ProjectReference Include="..\NHibernate\NHibernate.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.1" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ public class AnotherEntity
{
public virtual int Id { get; set; }
public virtual string Output { get; set; }
public virtual string Input { get; set; }
public virtual string Input { get; set; }
public virtual CompositeIdEntity CompositeIdEntity { get; set; }
}
}
}
58 changes: 58 additions & 0 deletions src/NHibernate.DomainModel/Northwind/Entities/CompositeIdEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;

namespace NHibernate.DomainModel.Northwind.Entities
{
public class CompositeId : IComparable<CompositeId>
{
public int ObjectId { get; set; }
public int TenantId { get; set; }

public CompositeId() { }
public CompositeId(int objectId, int tenantId)
{
ObjectId = objectId;
TenantId = tenantId;
}

public override string ToString() => ObjectId + "|" + TenantId;
protected bool Equals(CompositeId other) => ObjectId == other.ObjectId && TenantId == other.TenantId;
public static bool operator ==(CompositeId left, CompositeId right) => Equals(left, right);
public static bool operator !=(CompositeId left, CompositeId right) => !Equals(left, right);

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj) || obj.GetType() != this.GetType())
{
return false;
}
return ReferenceEquals(this, obj) || Equals((CompositeId)obj);
}

public override int GetHashCode() => HashCode.Combine(ObjectId, TenantId);

public int CompareTo(CompositeId other)
{
if (ReferenceEquals(this, other))
{
return 0;
}
else if (ReferenceEquals(other, null))
{
return 1;
}

var idComparison = ObjectId.CompareTo(other.ObjectId);
if (idComparison != 0)
{
return idComparison;
}

return TenantId.CompareTo(other);
}
}
public class CompositeIdEntity
{
public virtual CompositeId Id { get; set; }
public virtual string Name { get; set; }
}
}
13 changes: 9 additions & 4 deletions src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs
Original file line number Diff line number Diff line change
@@ -59,10 +59,10 @@ public IQueryable<Timesheet> Timesheets
get { return _session.Query<Timesheet>(); }
}

public IQueryable<Animal> Animals
{
get { return _session.Query<Animal>(); }
}
public IQueryable<Animal> Animals
{
get { return _session.Query<Animal>(); }
}

public IQueryable<Mammal> Mammals
{
@@ -113,5 +113,10 @@ public IQueryable<IUser> IUsers
{
get { return _session.Query<IUser>(); }
}

public IQueryable<AnotherEntity> AnotherEntity
{
get { return _session.Query<AnotherEntity>(); }
}
}
}
Original file line number Diff line number Diff line change
@@ -6,5 +6,9 @@
</id>
<property name="Output" />
<property name="Input" />
<many-to-one name="CompositeIdEntity" fetch="select">
<column name="CompositeObjectId" />
<column name="CompositeTenantId" />
</many-to-one>
</class>
</hibernate-mapping>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHibernate.DomainModel.Northwind.Entities" assembly="NHibernate.DomainModel">
<class name="CompositeIdEntity" table="CompositeIdEntity">
<composite-id name="Id">
<key-property name="ObjectId" column="ObjectId" />
<key-property name="TenantId" column="TenantId" />
</composite-id>
<property name="Name" length="128" />
</class>
</hibernate-mapping>
31 changes: 29 additions & 2 deletions src/NHibernate.Test/Async/CompositeId/CompositeIdFixture.cs
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@
namespace NHibernate.Test.CompositeId
{
using System.Threading.Tasks;
using System.Threading;
[TestFixture]
public class CompositeIdFixtureAsync : TestCase
{
@@ -33,7 +34,7 @@ protected override string[] Mappings
return new string[]
{
"CompositeId.Customer.hbm.xml", "CompositeId.Order.hbm.xml", "CompositeId.LineItem.hbm.xml",
"CompositeId.Product.hbm.xml"
"CompositeId.Product.hbm.xml", "CompositeId.Shipper.hbm.xml"
};
}
}
@@ -76,9 +77,13 @@ public async Task CompositeIdsAsync()

Order o = new Order(c);
o.OrderDate = DateTime.Today;
o.Shipper = new Shipper() { Id = new NullableId(null, 13) };
await (s.PersistAsync(o));

LineItem li = new LineItem(o, p);
li.Quantity = 2;

await (s.PersistAsync(li));

await (t.CommitAsync());
}

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

using (s = OpenSession())
{
t = s.BeginTransaction();
var noShippersForWarehouse = await (s.Query<Order>()
// NOTE: .Where(x => x.Shipper.Id == new NullableId(null, 13)) improperly renders
// "where (ShipperId = @p1 and WarehouseId = @p2)" with @p1 = NULL (needs to be is null)
// But the effort to fix is pretty high due to how component tuples are managed in linq / hql.
.Where(x => x.Shipper.Id.WarehouseId == 13 && x.Shipper.Id.Id == null)
.ToListAsync());
Assert.AreEqual(1, noShippersForWarehouse.Count);
await (t.CommitAsync());
}

using (s = OpenSession())
{
t = s.BeginTransaction();
@@ -303,5 +321,14 @@ public async Task AnyOnCompositeIdAsync()
await (s.Query<Order>().Select(o => o.LineItems.Any()).ToListAsync());
}
}

public async Task NullCompositeIdAsync(CancellationToken cancellationToken = default(CancellationToken))
{
using (var s = OpenSession())
{
await (s.Query<Order>().Where(o => o.LineItems.Any()).ToListAsync(cancellationToken));
await (s.Query<Order>().Select(o => o.LineItems.Any()).ToListAsync(cancellationToken));
}
}
}
}
11 changes: 11 additions & 0 deletions src/NHibernate.Test/Async/Linq/JoinTests.cs
Original file line number Diff line number Diff line change
@@ -297,6 +297,17 @@ public async Task OrderLinesWithSelectingCustomerNameInCaseShouldProduceTwoJoins
Assert.That(countJoins, Is.EqualTo(2));
}
}

[Test]
public async Task ShouldConstipateJoinsWhenOnlyComparingCompositeIdPropertiesAsync()
{
using (var spy = new SqlLogSpy())
{
await (db.AnotherEntity.Where(x => x.CompositeIdEntity.Id.TenantId == 3).ToListAsync());
var countJoins = CountJoins(spy);
Assert.That(countJoins, Is.EqualTo(0));
}
}

private static int CountJoins(LogSpy sqlLog)
{
Original file line number Diff line number Diff line change
@@ -42,7 +42,8 @@ internal sealed class CustomQueryLoaderFixtureAsync : TestCase
"Northwind.Mappings.TimeSheet.hbm.xml",
"Northwind.Mappings.Animal.hbm.xml",
"Northwind.Mappings.Patient.hbm.xml",
"Northwind.Mappings.NumericEntity.hbm.xml"
"Northwind.Mappings.NumericEntity.hbm.xml",
"Northwind.Mappings.CompositeIdEntity.hbm.xml"
};

protected override string MappingsAssembly => "NHibernate.DomainModel";
30 changes: 28 additions & 2 deletions src/NHibernate.Test/CompositeId/CompositeIdFixture.cs
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ protected override string[] Mappings
return new string[]
{
"CompositeId.Customer.hbm.xml", "CompositeId.Order.hbm.xml", "CompositeId.LineItem.hbm.xml",
"CompositeId.Product.hbm.xml"
"CompositeId.Product.hbm.xml", "CompositeId.Shipper.hbm.xml"
};
}
}
@@ -64,9 +64,13 @@ public void CompositeIds()

Order o = new Order(c);
o.OrderDate = DateTime.Today;
o.Shipper = new Shipper() { Id = new NullableId(null, 13) };
s.Persist(o);

LineItem li = new LineItem(o, p);
li.Quantity = 2;

s.Persist(li);

t.Commit();
}

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

using (s = OpenSession())
{
t = s.BeginTransaction();
var noShippersForWarehouse = s.Query<Order>()
// NOTE: .Where(x => x.Shipper.Id == new NullableId(null, 13)) improperly renders
// "where (ShipperId = @p1 and WarehouseId = @p2)" with @p1 = NULL (needs to be is null)
// But the effort to fix is pretty high due to how component tuples are managed in linq / hql.
.Where(x => x.Shipper.Id.WarehouseId == 13 && x.Shipper.Id.Id == null)
.ToList();
Assert.AreEqual(1, noShippersForWarehouse.Count);
t.Commit();
}

using (s = OpenSession())
{
t = s.BeginTransaction();
@@ -291,5 +308,14 @@ public void AnyOnCompositeId()
s.Query<Order>().Select(o => o.LineItems.Any()).ToList();
}
}

public void NullCompositeId()
{
using (var s = OpenSession())
{
s.Query<Order>().Where(o => o.LineItems.Any()).ToList();
s.Query<Order>().Select(o => o.LineItems.Any()).ToList();
}
}
}
}
58 changes: 58 additions & 0 deletions src/NHibernate.Test/CompositeId/NullableId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;

namespace NHibernate.Test.CompositeId
{
public class NullableId : IComparable<NullableId>
{
public int? Id { get; set; }
public int WarehouseId { get; set; }

public NullableId() { }
public NullableId(int? id, int warehouseId)
{
Id = id;
WarehouseId = warehouseId;
}

public override string ToString() => Id + "|" + WarehouseId;
protected bool Equals(NullableId other) => Id == other.Id && WarehouseId == other.WarehouseId;
public static bool operator ==(NullableId left, NullableId right) => Equals(left, right);
public static bool operator !=(NullableId left, NullableId right) => !Equals(left, right);

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj) || obj.GetType() != this.GetType())
{
return false;
}

return ReferenceEquals(this, obj) || Equals((NullableId)obj);
}

public override int GetHashCode() => HashCode.Combine(Id, WarehouseId);

public int CompareTo(NullableId other)
{
if (ReferenceEquals(this, other))
{
return 0;
}
else if (ReferenceEquals(other, null) || !other.Id.HasValue)
{
return 1;
}
else if (!Id.HasValue)
{
return -1;
}

var idComparison = Id.Value.CompareTo(other.Id);
if (idComparison != 0)
{
return idComparison;
}

return WarehouseId.CompareTo(other.WarehouseId);
}
}
}
9 changes: 8 additions & 1 deletion src/NHibernate.Test/CompositeId/Order.cs
Original file line number Diff line number Diff line change
@@ -48,6 +48,7 @@ public override int GetHashCode()
private Customer customer;
private IList<LineItem> lineItems = new List<LineItem>();
private decimal total;
private Shipper shipper;

public Order() {}
public Order(Customer customer)
@@ -87,6 +88,12 @@ public virtual decimal Total
get { return total; }
set { total = value; }
}

public virtual Shipper Shipper
{
get { return shipper; }
set { shipper = value; }
}

public virtual LineItem GenerateLineItem(Product product, int quantity)
{
@@ -96,4 +103,4 @@ public virtual LineItem GenerateLineItem(Product product, int quantity)
return li;
}
}
}
}
5 changes: 5 additions & 0 deletions src/NHibernate.Test/CompositeId/Order.hbm.xml
Original file line number Diff line number Diff line change
@@ -43,6 +43,11 @@
insert="false"
update="false"
not-null="true"/>

<many-to-one name="Shipper" fetch="select">
<column name="ShipperId" not-null="false"/>
<column name="WarehouseId" />
</many-to-one>

<bag name="LineItems"
fetch="join"
8 changes: 8 additions & 0 deletions src/NHibernate.Test/CompositeId/Shipper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace NHibernate.Test.CompositeId
{
public class Shipper
{
public virtual NullableId Id { get; set; }
public virtual string Name { get; set; }
}
}
15 changes: 15 additions & 0 deletions src/NHibernate.Test/CompositeId/Shipper.hbm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="NHibernate.Test.CompositeId"
assembly="NHibernate.Test">

<class name="Shipper">
<composite-id name="Id">
<key-property name="Id" column="Id" />
<key-property name="WarehouseId" column="WarehouseId" />
</composite-id>

<property name="Name" length="128"/>
</class>

</hibernate-mapping>
Original file line number Diff line number Diff line change
@@ -32,7 +32,8 @@ protected override string[] Mappings
"Northwind.Mappings.User.hbm.xml",
"Northwind.Mappings.TimeSheet.hbm.xml",
"Northwind.Mappings.Animal.hbm.xml",
"Northwind.Mappings.Patient.hbm.xml"
"Northwind.Mappings.Patient.hbm.xml",
"Northwind.Mappings.CompositeIdEntity.hbm.xml"
};
}
}
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -149,6 +149,8 @@ CREATE TABLE [dbo].[AnotherEntity](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Output] [nvarchar](255) NULL,
[Input] [nvarchar](255) NULL,
[CompositeObjectId] INT NULL,
[CompositeTenantId] INT NULL,
PRIMARY KEY CLUSTERED
(
[Id] ASC
@@ -159,9 +161,27 @@ SET IDENTITY_INSERT [dbo].[AnotherEntity] ON
INSERT [dbo].[AnotherEntity] ([Id], [Output]) VALUES (1, N'output')
INSERT [dbo].[AnotherEntity] ([Id], [Input]) VALUES (2, N'input')
INSERT [dbo].[AnotherEntity] ([Id], [Input], [Output]) VALUES (3, N'i/o', N'i/o')
INSERT [dbo].[AnotherEntity] ([Id], [Input], [Output]) VALUES (4, N'input', N'output')
INSERT [dbo].[AnotherEntity] ([Id], [Input], [Output], [CompositeObjectId], [CompositeTenantId]) VALUES (4, N'input', N'output', 1, 10)
INSERT [dbo].[AnotherEntity] ([Id], [Input], [Output]) VALUES (5, NULL, NULL)
SET IDENTITY_INSERT [dbo].[AnotherEntity] OFF
/****** Object: Table [dbo].[CompositeIdEntity] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[CompositeIdEntity](
[ObjectId] [int] NOT NULL,
[TenantId] [int] NOT NULL,
[Name] [nvarchar](128) NULL
PRIMARY KEY CLUSTERED
(
[ObjectId] ASC,
[TenantId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
INSERT [dbo].[CompositeIdEntity] ([ObjectId], [TenantId], [Name]) VALUES (1, 10, N'Jack Stephan')

/****** Object: Table [dbo].[Animal] Script Date: 06/17/2010 13:08:54 ******/
SET ANSI_NULLS ON
GO
@@ -3953,6 +3973,12 @@ REFERENCES [dbo].[AnotherEntity] ([Id])
GO
ALTER TABLE [dbo].[Roles] CHECK CONSTRAINT [FK1A2E670F36A436]
GO
/****** Object: ForeignKey [FK_AnotherEntity_CompositeIdEntity] ******/
ALTER TABLE [dbo].[AnotherEntity] WITH CHECK ADD CONSTRAINT [FK_AnotherEntity_CompositeIdEntity] FOREIGN KEY([CompositeObjectId], [CompositeTenantId])
REFERENCES [dbo].[CompositeIdEntity] ([ObjectId], [TenantId])
GO
ALTER TABLE [dbo].[AnotherEntity] CHECK CONSTRAINT [FK_AnotherEntity_CompositeIdEntity]
GO
/****** Object: ForeignKey [FK1A2E670F9E248253] Script Date: 06/17/2010 13:08:54 ******/
ALTER TABLE [dbo].[Roles] WITH CHECK ADD CONSTRAINT [FK1A2E670F9E248253] FOREIGN KEY([ParentId])
REFERENCES [dbo].[Roles] ([Id])
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@ ALTER TABLE [dbo].[TimesheetEntries] DROP CONSTRAINT [FK7E222050C7D0B317]
ALTER TABLE [dbo].[TimeSheetUsers] DROP CONSTRAINT [FKA6EEF73795E61DFF]
ALTER TABLE [dbo].[TimeSheetUsers] DROP CONSTRAINT [FKA6EEF737C7D0B317]
ALTER TABLE [dbo].[Users] DROP CONSTRAINT [FK2C1C7FE5D8C957C7]
ALTER TABLE [dbo].[AnotherEntity] DROP CONSTRAINT [FK_AnotherEntity_CompositeIdEntity]
DROP TABLE [dbo].[TimeSheetUsers]
DROP TABLE [dbo].[Users]
DROP TABLE [dbo].[OrderLines]
@@ -48,6 +49,7 @@ DROP TABLE [dbo].[Categories]
DROP TABLE [dbo].[Customers]
DROP TABLE [dbo].[Animal]
DROP TABLE [dbo].[AnotherEntity]
DROP TABLE [dbo].[CompositeIdEntity]
DROP TABLE [dbo].[Shippers]
DROP TABLE [dbo].[States]
DROP TABLE [dbo].[Suppliers]
Binary file not shown.
Binary file not shown.
11 changes: 11 additions & 0 deletions src/NHibernate.Test/Linq/JoinTests.cs
Original file line number Diff line number Diff line change
@@ -315,6 +315,17 @@ public void OrderLinesWithSelectingCustomerNameInCaseShouldProduceTwoJoinsAltern
Assert.That(countJoins, Is.EqualTo(2));
}
}

[Test]
public void ShouldConstipateJoinsWhenOnlyComparingCompositeIdProperties()
{
using (var spy = new SqlLogSpy())
{
db.AnotherEntity.Where(x => x.CompositeIdEntity.Id.TenantId == 3).ToList();
var countJoins = CountJoins(spy);
Assert.That(countJoins, Is.EqualTo(0));
}
}

private static int CountJoins(LogSpy sqlLog)
{
3 changes: 2 additions & 1 deletion src/NHibernate.Test/Linq/LinqReadonlyTestsContext.cs
Original file line number Diff line number Diff line change
@@ -44,7 +44,8 @@ private IEnumerable<string> Mappings
"Northwind.Mappings.TimeSheet.hbm.xml",
"Northwind.Mappings.Animal.hbm.xml",
"Northwind.Mappings.Patient.hbm.xml",
"Northwind.Mappings.NumericEntity.hbm.xml"
"Northwind.Mappings.NumericEntity.hbm.xml",
"Northwind.Mappings.CompositeIdEntity.hbm.xml"
};
}
}
3 changes: 2 additions & 1 deletion src/NHibernate.Test/Linq/LinqTestCase.cs
Original file line number Diff line number Diff line change
@@ -37,7 +37,8 @@ protected override string[] Mappings
"Northwind.Mappings.Patient.hbm.xml",
"Northwind.Mappings.DynamicUser.hbm.xml",
"Northwind.Mappings.NumericEntity.hbm.xml",
"Northwind.Mappings.CompositeOrder.hbm.xml"
"Northwind.Mappings.CompositeOrder.hbm.xml",
"Northwind.Mappings.CompositeIdEntity.hbm.xml"
};
}
}
Original file line number Diff line number Diff line change
@@ -30,7 +30,8 @@ internal sealed class CustomQueryLoaderFixture : TestCase
"Northwind.Mappings.TimeSheet.hbm.xml",
"Northwind.Mappings.Animal.hbm.xml",
"Northwind.Mappings.Patient.hbm.xml",
"Northwind.Mappings.NumericEntity.hbm.xml"
"Northwind.Mappings.NumericEntity.hbm.xml",
"Northwind.Mappings.CompositeIdEntity.hbm.xml"
};

protected override string MappingsAssembly => "NHibernate.DomainModel";
4 changes: 4 additions & 0 deletions src/NHibernate.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -21,6 +21,9 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=236f7aa5_002D7b06_002D43ca_002Dbf2a_002D9b31bfcff09a/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="CONSTANT_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=4a98fdf6_002D7d98_002D4f5a_002Dafeb_002Dea44ad98c70c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
@@ -29,6 +32,7 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EFormat_002ESettingsUpgrade_002EAlignmentTabFillStyleMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/UnitTesting/SeparateAppDomainPerAssembly/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=0BE95D01E0F2244E97F5FEFAD1EB1A63/@KeyIndexDefined">True</s:Boolean>
34 changes: 30 additions & 4 deletions src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs
Original file line number Diff line number Diff line change
@@ -4,6 +4,8 @@
using NHibernate.Engine;
using NHibernate.Linq.Clauses;
using NHibernate.Linq.Visitors;
using NHibernate.Persister.Entity;
using NHibernate.Type;
using NHibernate.Util;
using Remotion.Linq;
using Remotion.Linq.Clauses;
@@ -92,11 +94,35 @@ public bool IsIdentifier(System.Type type, string propertyName)

bool IIsEntityDecider.IsEntity(MemberExpression expression, out bool isIdentifier)
{
isIdentifier =
ExpressionsHelper.TryGetMappedType(_sessionFactory, expression, out var mappedType, out var entityPersister, out _, out var memberPath)
&& entityPersister?.IdentifierPropertyName == memberPath;
if (ExpressionsHelper.TryGetMappedType(_sessionFactory, expression, out var mappedType, out var entityPersister, out var componentType, out var memberPath))
{
isIdentifier = IsIdentifierPath(entityPersister, componentType, memberPath);
return mappedType?.IsEntityType == true;
}
isIdentifier = false;
return false;
}

return mappedType?.IsEntityType == true;
bool IsIdentifierPath(IEntityPersister entityPersister, IAbstractComponentType componentType, string memberPath)
{
if (entityPersister == null)
{
return false;
}
if (entityPersister.IdentifierPropertyName == memberPath)
{
return true;
}
// Don't bother to add the join if we're just comparing properties of the composite id
if (componentType != null)
{
var pathParts = memberPath.Split('.');
return pathParts.Length == 2
&& pathParts[0] == entityPersister.IdentifierPropertyName
&& componentType.PropertyNames.Any(name => name == pathParts[1]);
}

return false;
}
}
}