Skip to content

Commit 21701a0

Browse files
maca88bahusoid
andauthored
Fix wrong column selection when using OfType in a Linq query (#2669)
Fixes #2626 Co-authored-by: Roman Artiukhin <bahusdrive@gmail.com>
1 parent d6a4859 commit 21701a0

File tree

5 files changed

+198
-0
lines changed

5 files changed

+198
-0
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System.Linq;
12+
using NUnit.Framework;
13+
using NHibernate.Linq;
14+
15+
namespace NHibernate.Test.NHSpecificTest.GH2626
16+
{
17+
using System.Threading.Tasks;
18+
[TestFixture]
19+
public class FixtureAsync : BugTestCase
20+
{
21+
protected override void OnSetUp()
22+
{
23+
}
24+
25+
protected override void OnTearDown()
26+
{
27+
using (var session = OpenSession())
28+
using (var transaction = session.BeginTransaction())
29+
{
30+
// The HQL delete does all the job inside the database without loading the entities, but it does
31+
// not handle delete order for avoiding violating constraints if any. Use
32+
// session.Delete("from System.Object");
33+
// instead if in need of having NHibernate ordering the deletes, but this will cause
34+
// loading the entities in the session.
35+
session.CreateQuery("delete from System.Object").ExecuteUpdate();
36+
37+
transaction.Commit();
38+
}
39+
}
40+
41+
[Test]
42+
public async Task SubqueryWithSelectOnSubclassPropertyAsync()
43+
{
44+
using(var logSpy = new SqlLogSpy())
45+
using (var session = OpenSession())
46+
{
47+
var capabilitiesQuery = session
48+
.Query<UserCapabilityAssignment>()
49+
.Where(x => x.Name == "aaa")
50+
.Select(x => x.UserId);
51+
52+
await (session.Query<ApplicationUser>()
53+
.Where(x => capabilitiesQuery.Contains(x.Id))
54+
.ToListAsync());
55+
Assert.That(logSpy.GetWholeLog(), Does.Contain("UserId").IgnoreCase);
56+
}
57+
}
58+
59+
[Test]
60+
public async Task SubqueryWithOfTypeAndSelectOnSubclassPropertyAsync()
61+
{
62+
using(var logSpy = new SqlLogSpy())
63+
using (var session = OpenSession())
64+
{
65+
var capabilitiesQuery = session
66+
.Query<CapabilityAssignment>().OfType<UserCapabilityAssignment>()
67+
.Where(x => x.Name == "aaa")
68+
.Select(x => x.UserId);
69+
70+
await (session.Query<ApplicationUser>()
71+
.Where(x => capabilitiesQuery.Contains(x.Id))
72+
.ToListAsync());
73+
Assert.That(logSpy.GetWholeLog(), Does.Contain("UserId").IgnoreCase);
74+
}
75+
}
76+
}
77+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
3+
namespace NHibernate.Test.NHSpecificTest.GH2626
4+
{
5+
abstract class CapabilityAssignment
6+
{
7+
public virtual Guid Id { get; set; }
8+
public virtual string Name { get; set; }
9+
}
10+
11+
class ApplicationUser
12+
{
13+
public virtual Guid Id { get; set; }
14+
public virtual string UserName { get; set; }
15+
}
16+
17+
class RoleCapabilityAssignment : CapabilityAssignment
18+
{
19+
public virtual Guid RoleId { get; set; }
20+
}
21+
22+
class UserCapabilityAssignment : CapabilityAssignment
23+
{
24+
public virtual Guid UserId { get; set; }
25+
}
26+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System.Linq;
2+
using NUnit.Framework;
3+
4+
namespace NHibernate.Test.NHSpecificTest.GH2626
5+
{
6+
[TestFixture]
7+
public class Fixture : BugTestCase
8+
{
9+
protected override void OnSetUp()
10+
{
11+
}
12+
13+
protected override void OnTearDown()
14+
{
15+
using (var session = OpenSession())
16+
using (var transaction = session.BeginTransaction())
17+
{
18+
// The HQL delete does all the job inside the database without loading the entities, but it does
19+
// not handle delete order for avoiding violating constraints if any. Use
20+
// session.Delete("from System.Object");
21+
// instead if in need of having NHibernate ordering the deletes, but this will cause
22+
// loading the entities in the session.
23+
session.CreateQuery("delete from System.Object").ExecuteUpdate();
24+
25+
transaction.Commit();
26+
}
27+
}
28+
29+
[Test]
30+
public void SubqueryWithSelectOnSubclassProperty()
31+
{
32+
using(var logSpy = new SqlLogSpy())
33+
using (var session = OpenSession())
34+
{
35+
var capabilitiesQuery = session
36+
.Query<UserCapabilityAssignment>()
37+
.Where(x => x.Name == "aaa")
38+
.Select(x => x.UserId);
39+
40+
session.Query<ApplicationUser>()
41+
.Where(x => capabilitiesQuery.Contains(x.Id))
42+
.ToList();
43+
Assert.That(logSpy.GetWholeLog(), Does.Contain("UserId").IgnoreCase);
44+
}
45+
}
46+
47+
[Test]
48+
public void SubqueryWithOfTypeAndSelectOnSubclassProperty()
49+
{
50+
using(var logSpy = new SqlLogSpy())
51+
using (var session = OpenSession())
52+
{
53+
var capabilitiesQuery = session
54+
.Query<CapabilityAssignment>().OfType<UserCapabilityAssignment>()
55+
.Where(x => x.Name == "aaa")
56+
.Select(x => x.UserId);
57+
58+
session.Query<ApplicationUser>()
59+
.Where(x => capabilitiesQuery.Contains(x.Id))
60+
.ToList();
61+
Assert.That(logSpy.GetWholeLog(), Does.Contain("UserId").IgnoreCase);
62+
}
63+
}
64+
}
65+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernate.Test"
3+
namespace="NHibernate.Test.NHSpecificTest.GH2626">
4+
5+
<class name="CapabilityAssignment" table="CapabilityAssignments" discriminator-value="null" abstract="true">
6+
<id name="Id" generator="guid.comb" />
7+
<discriminator type="int" column="Type" not-null="true" />
8+
9+
<property name="Name" length="256" not-null="true" />
10+
11+
<subclass name="UserCapabilityAssignment" discriminator-value="1">
12+
<property name="UserId" not-null="true" />
13+
</subclass>
14+
<subclass name="RoleCapabilityAssignment" discriminator-value="2">
15+
<property name="RoleId" not-null="true" />
16+
</subclass>
17+
</class>
18+
19+
<class name="ApplicationUser" table="AspNetUsers" dynamic-insert="true" dynamic-update="true">
20+
<id name="Id" generator="guid.comb" />
21+
<property name="UserName" length="256" not-null="true" />
22+
</class>
23+
24+
</hibernate-mapping>

src/NHibernate/Util/ExpressionsHelper.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,12 @@ protected override Expression VisitQuerySourceReference(QuerySourceReferenceExpr
697697
{
698698
if (node.ReferencedQuerySource is IFromClause fromClause)
699699
{
700+
// Types will be different when OfType method is used (e.g. Query<A>().OfType<B>())
701+
if (fromClause.ItemType != node.Type)
702+
{
703+
_convertType = node.Type;
704+
}
705+
700706
return base.Visit(fromClause.FromExpression);
701707
}
702708

0 commit comments

Comments
 (0)