diff --git a/.gitattributes b/.gitattributes
index 91dd591ef91..3d3a1d3bd79 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -11,6 +11,7 @@
*.cmd text
*.msbuild text
*.md text
+*.sql text
*.sln text eol=crlf
*.csproj text eol=crlf
diff --git a/src/NHibernate.DomainModel/NHibernate.DomainModel.csproj b/src/NHibernate.DomainModel/NHibernate.DomainModel.csproj
index 95134f11e0c..49fc73c5cec 100644
--- a/src/NHibernate.DomainModel/NHibernate.DomainModel.csproj
+++ b/src/NHibernate.DomainModel/NHibernate.DomainModel.csproj
@@ -15,4 +15,7 @@
+
+
+
diff --git a/src/NHibernate.DomainModel/Northwind/Entities/AnotherEntity.cs b/src/NHibernate.DomainModel/Northwind/Entities/AnotherEntity.cs
index c715b86aef1..fa5330ef265 100644
--- a/src/NHibernate.DomainModel/Northwind/Entities/AnotherEntity.cs
+++ b/src/NHibernate.DomainModel/Northwind/Entities/AnotherEntity.cs
@@ -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; }
}
-}
\ No newline at end of file
+}
diff --git a/src/NHibernate.DomainModel/Northwind/Entities/CompositeIdEntity.cs b/src/NHibernate.DomainModel/Northwind/Entities/CompositeIdEntity.cs
new file mode 100644
index 00000000000..ad49b917469
--- /dev/null
+++ b/src/NHibernate.DomainModel/Northwind/Entities/CompositeIdEntity.cs
@@ -0,0 +1,58 @@
+using System;
+
+namespace NHibernate.DomainModel.Northwind.Entities
+{
+ public class CompositeId : IComparable
+ {
+ 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; }
+ }
+}
diff --git a/src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs b/src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs
index bda526da00e..aa3de99c3c0 100755
--- a/src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs
+++ b/src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs
@@ -59,10 +59,10 @@ public IQueryable Timesheets
get { return _session.Query(); }
}
- public IQueryable Animals
- {
- get { return _session.Query(); }
- }
+ public IQueryable Animals
+ {
+ get { return _session.Query(); }
+ }
public IQueryable Mammals
{
@@ -113,5 +113,10 @@ public IQueryable IUsers
{
get { return _session.Query(); }
}
+
+ public IQueryable AnotherEntity
+ {
+ get { return _session.Query(); }
+ }
}
}
diff --git a/src/NHibernate.DomainModel/Northwind/Mappings/AnotherEntity.hbm.xml b/src/NHibernate.DomainModel/Northwind/Mappings/AnotherEntity.hbm.xml
index d0ccaa7fb69..0dcc71ced7d 100644
--- a/src/NHibernate.DomainModel/Northwind/Mappings/AnotherEntity.hbm.xml
+++ b/src/NHibernate.DomainModel/Northwind/Mappings/AnotherEntity.hbm.xml
@@ -6,5 +6,9 @@
+
+
+
+
diff --git a/src/NHibernate.DomainModel/Northwind/Mappings/CompositeIdEntity.hbm.xml b/src/NHibernate.DomainModel/Northwind/Mappings/CompositeIdEntity.hbm.xml
new file mode 100644
index 00000000000..57188ca8790
--- /dev/null
+++ b/src/NHibernate.DomainModel/Northwind/Mappings/CompositeIdEntity.hbm.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/NHibernate.Test/Async/CompositeId/CompositeIdFixture.cs b/src/NHibernate.Test/Async/CompositeId/CompositeIdFixture.cs
index 647e0cb39d8..620fa732b10 100644
--- a/src/NHibernate.Test/Async/CompositeId/CompositeIdFixture.cs
+++ b/src/NHibernate.Test/Async/CompositeId/CompositeIdFixture.cs
@@ -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()
+ // 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().Select(o => o.LineItems.Any()).ToListAsync());
}
}
+
+ public async Task NullCompositeIdAsync(CancellationToken cancellationToken = default(CancellationToken))
+ {
+ using (var s = OpenSession())
+ {
+ await (s.Query().Where(o => o.LineItems.Any()).ToListAsync(cancellationToken));
+ await (s.Query().Select(o => o.LineItems.Any()).ToListAsync(cancellationToken));
+ }
+ }
}
}
diff --git a/src/NHibernate.Test/Async/Linq/JoinTests.cs b/src/NHibernate.Test/Async/Linq/JoinTests.cs
index 4582dd7652b..4f52e7cacb2 100644
--- a/src/NHibernate.Test/Async/Linq/JoinTests.cs
+++ b/src/NHibernate.Test/Async/Linq/JoinTests.cs
@@ -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)
{
diff --git a/src/NHibernate.Test/Async/QueryTranslator/CustomQueryLoaderFixture.cs b/src/NHibernate.Test/Async/QueryTranslator/CustomQueryLoaderFixture.cs
index 67d15ddb2b4..61972f3dfad 100644
--- a/src/NHibernate.Test/Async/QueryTranslator/CustomQueryLoaderFixture.cs
+++ b/src/NHibernate.Test/Async/QueryTranslator/CustomQueryLoaderFixture.cs
@@ -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";
diff --git a/src/NHibernate.Test/CompositeId/CompositeIdFixture.cs b/src/NHibernate.Test/CompositeId/CompositeIdFixture.cs
index e2192233234..63cfbba45a5 100644
--- a/src/NHibernate.Test/CompositeId/CompositeIdFixture.cs
+++ b/src/NHibernate.Test/CompositeId/CompositeIdFixture.cs
@@ -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()
+ // 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().Select(o => o.LineItems.Any()).ToList();
}
}
+
+ public void NullCompositeId()
+ {
+ using (var s = OpenSession())
+ {
+ s.Query().Where(o => o.LineItems.Any()).ToList();
+ s.Query().Select(o => o.LineItems.Any()).ToList();
+ }
+ }
}
}
diff --git a/src/NHibernate.Test/CompositeId/NullableId.cs b/src/NHibernate.Test/CompositeId/NullableId.cs
new file mode 100644
index 00000000000..8e5d238b5e0
--- /dev/null
+++ b/src/NHibernate.Test/CompositeId/NullableId.cs
@@ -0,0 +1,58 @@
+using System;
+
+namespace NHibernate.Test.CompositeId
+{
+ public class NullableId : IComparable
+ {
+ 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);
+ }
+ }
+}
diff --git a/src/NHibernate.Test/CompositeId/Order.cs b/src/NHibernate.Test/CompositeId/Order.cs
index db637e781cd..9379f2ed6d3 100644
--- a/src/NHibernate.Test/CompositeId/Order.cs
+++ b/src/NHibernate.Test/CompositeId/Order.cs
@@ -48,6 +48,7 @@ public override int GetHashCode()
private Customer customer;
private IList lineItems = new List();
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;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/NHibernate.Test/CompositeId/Order.hbm.xml b/src/NHibernate.Test/CompositeId/Order.hbm.xml
index e04949a6202..724f7fe8513 100644
--- a/src/NHibernate.Test/CompositeId/Order.hbm.xml
+++ b/src/NHibernate.Test/CompositeId/Order.hbm.xml
@@ -43,6 +43,11 @@
insert="false"
update="false"
not-null="true"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/NHibernate.Test/Criteria/ReadonlyTests/CriteriaNorthwindReadonlyTestCase.cs b/src/NHibernate.Test/Criteria/ReadonlyTests/CriteriaNorthwindReadonlyTestCase.cs
index cd8cfd50fba..f99165e5924 100644
--- a/src/NHibernate.Test/Criteria/ReadonlyTests/CriteriaNorthwindReadonlyTestCase.cs
+++ b/src/NHibernate.Test/Criteria/ReadonlyTests/CriteriaNorthwindReadonlyTestCase.cs
@@ -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"
};
}
}
diff --git a/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyCreateScript.sql b/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyCreateScript.sql
index cb42443637d..493c7e112dd 100644
Binary files a/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyCreateScript.sql and b/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyCreateScript.sql differ
diff --git a/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyDropScript.sql b/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyDropScript.sql
index 766d9f34375..a26fe1990ca 100644
Binary files a/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyDropScript.sql and b/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyDropScript.sql differ
diff --git a/src/NHibernate.Test/DbScripts/MsSql2012DialectLinqReadonlyCreateScript.sql b/src/NHibernate.Test/DbScripts/MsSql2012DialectLinqReadonlyCreateScript.sql
index ec1b580e231..493c7e112dd 100644
--- a/src/NHibernate.Test/DbScripts/MsSql2012DialectLinqReadonlyCreateScript.sql
+++ b/src/NHibernate.Test/DbScripts/MsSql2012DialectLinqReadonlyCreateScript.sql
@@ -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])
diff --git a/src/NHibernate.Test/DbScripts/MsSql2012DialectLinqReadonlyDropScript.sql b/src/NHibernate.Test/DbScripts/MsSql2012DialectLinqReadonlyDropScript.sql
index bab84a61127..a26fe1990ca 100644
--- a/src/NHibernate.Test/DbScripts/MsSql2012DialectLinqReadonlyDropScript.sql
+++ b/src/NHibernate.Test/DbScripts/MsSql2012DialectLinqReadonlyDropScript.sql
@@ -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]
diff --git a/src/NHibernate.Test/DbScripts/PostgreSQL83DialectLinqReadonlyCreateScript.sql b/src/NHibernate.Test/DbScripts/PostgreSQL83DialectLinqReadonlyCreateScript.sql
index 0ac95140fc2..5d6e0d85ba5 100644
Binary files a/src/NHibernate.Test/DbScripts/PostgreSQL83DialectLinqReadonlyCreateScript.sql and b/src/NHibernate.Test/DbScripts/PostgreSQL83DialectLinqReadonlyCreateScript.sql differ
diff --git a/src/NHibernate.Test/DbScripts/PostgreSQL83DialectLinqReadonlyDropScript.sql b/src/NHibernate.Test/DbScripts/PostgreSQL83DialectLinqReadonlyDropScript.sql
index a9b1f2642ee..405b6d4ac63 100644
Binary files a/src/NHibernate.Test/DbScripts/PostgreSQL83DialectLinqReadonlyDropScript.sql and b/src/NHibernate.Test/DbScripts/PostgreSQL83DialectLinqReadonlyDropScript.sql differ
diff --git a/src/NHibernate.Test/Linq/JoinTests.cs b/src/NHibernate.Test/Linq/JoinTests.cs
index 0030f8295f0..fc816a94404 100644
--- a/src/NHibernate.Test/Linq/JoinTests.cs
+++ b/src/NHibernate.Test/Linq/JoinTests.cs
@@ -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)
{
diff --git a/src/NHibernate.Test/Linq/LinqReadonlyTestsContext.cs b/src/NHibernate.Test/Linq/LinqReadonlyTestsContext.cs
index aed51a8e15c..af1ed24fa8a 100644
--- a/src/NHibernate.Test/Linq/LinqReadonlyTestsContext.cs
+++ b/src/NHibernate.Test/Linq/LinqReadonlyTestsContext.cs
@@ -44,7 +44,8 @@ private IEnumerable 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"
};
}
}
diff --git a/src/NHibernate.Test/Linq/LinqTestCase.cs b/src/NHibernate.Test/Linq/LinqTestCase.cs
index 529b263fdcc..5860cfee54a 100755
--- a/src/NHibernate.Test/Linq/LinqTestCase.cs
+++ b/src/NHibernate.Test/Linq/LinqTestCase.cs
@@ -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"
};
}
}
diff --git a/src/NHibernate.Test/QueryTranslator/CustomQueryLoaderFixture.cs b/src/NHibernate.Test/QueryTranslator/CustomQueryLoaderFixture.cs
index a2d353e0988..c448d268a90 100644
--- a/src/NHibernate.Test/QueryTranslator/CustomQueryLoaderFixture.cs
+++ b/src/NHibernate.Test/QueryTranslator/CustomQueryLoaderFixture.cs
@@ -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";
diff --git a/src/NHibernate.sln.DotSettings b/src/NHibernate.sln.DotSettings
index d78384192e9..99072181f80 100644
--- a/src/NHibernate.sln.DotSettings
+++ b/src/NHibernate.sln.DotSettings
@@ -21,6 +21,9 @@
<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
<Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
+ <Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy>
+ <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy>
True
True
True
@@ -29,6 +32,7 @@
True
True
True
+ True
True
True
True
diff --git a/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs b/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs
index a664bb0d101..009b7382175 100644
--- a/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs
+++ b/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs
@@ -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;
}
}
}