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; } } }