Skip to content

Commit 5282202

Browse files
authored
Fix casting to unmapped interface in LINQ (#2879)
1 parent beb7b4b commit 5282202

File tree

7 files changed

+464
-7
lines changed

7 files changed

+464
-7
lines changed

src/NHibernate.DomainModel/Northwind/Entities/Animal.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ public class Animal
1616
public virtual Animal FatherOrMother => Father ?? Mother;
1717
}
1818

19-
public abstract class Reptile : Animal
19+
public interface IReptile
20+
{
21+
EnumStoredAsString Enum1 { get; }
22+
}
23+
24+
public abstract class Reptile : Animal, IReptile
2025
{
2126
public virtual double BodyTemperature { get; set; }
2227

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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;
12+
using System.Collections.Generic;
13+
using System.Linq;
14+
using NHibernate.Cfg.MappingSchema;
15+
using NHibernate.Mapping.ByCode;
16+
using NUnit.Framework;
17+
using NHibernate.Linq;
18+
19+
namespace NHibernate.Test.NHSpecificTest.GH2858
20+
{
21+
using System.Threading.Tasks;
22+
[TestFixture]
23+
public class ByCodeFixtureAsync : TestCaseMappingByCode
24+
{
25+
protected override HbmMapping GetMappings()
26+
{
27+
var mapper = new ModelMapper();
28+
29+
mapper.Class<Department>(
30+
rc =>
31+
{
32+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
33+
rc.Property(x => x.Name);
34+
});
35+
mapper.Class<Project>(
36+
rc =>
37+
{
38+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
39+
rc.Property(x => x.Name);
40+
rc.ManyToOne(x => x.Department, m => m.Column("DepartmentId"));
41+
});
42+
mapper.Class<Issue>(
43+
rc =>
44+
{
45+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
46+
rc.Property(x => x.Name);
47+
rc.IdBag(
48+
x => x.Departments,
49+
m => m.Table("IssuesToDepartments"),
50+
r => r.ManyToMany());
51+
rc.ManyToOne(x => x.Project, m => m.Column("ProjectId"));
52+
});
53+
mapper.Class<TimeChunk>(
54+
rc =>
55+
{
56+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
57+
rc.ManyToOne(x => x.Issue, m => m.Column("IssueId"));
58+
rc.Property(x => x.Seconds);
59+
});
60+
61+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
62+
}
63+
64+
protected override void OnSetUp()
65+
{
66+
using (var session = OpenSession())
67+
using (var transaction = session.BeginTransaction())
68+
{
69+
var deptA = new Department {Name = "A"};
70+
session.Save(deptA);
71+
var deptB = new Department {Name = "B"};
72+
session.Save(deptB);
73+
var deptC = new Department {Name = "C"};
74+
session.Save(deptC);
75+
var deptD = new Department {Name = "D"};
76+
session.Save(deptD);
77+
var deptE = new Department {Name = "E"};
78+
session.Save(deptE);
79+
80+
var projectX = new Project {Name = "X", Department = deptA};
81+
session.Save(projectX);
82+
var projectY = new Project {Name = "Y", Department = deptC};
83+
session.Save(projectY);
84+
var projectZ = new Project {Name = "Z", Department = deptE};
85+
session.Save(projectZ);
86+
87+
var issue1 = new Issue {Name = "TEST-1", Project = projectX,};
88+
session.Save(issue1);
89+
var issue2 = new Issue {Name = "TEST-2", Project = projectX, Departments = {deptA},};
90+
session.Save(issue2);
91+
var issue3 = new Issue {Name = "TEST-3", Project = projectX, Departments = {deptA, deptB},};
92+
session.Save(issue3);
93+
var issue4 = new Issue {Name = "TEST-4", Project = projectY,};
94+
session.Save(issue4);
95+
var issue5 = new Issue {Name = "TEST-5", Project = projectY, Departments = {deptD}};
96+
session.Save(issue5);
97+
98+
session.Save(new TimeChunk {Issue = issue1});
99+
session.Save(new TimeChunk {Issue = issue1});
100+
session.Save(new TimeChunk {Issue = issue2});
101+
session.Save(new TimeChunk {Issue = issue2});
102+
session.Save(new TimeChunk {Issue = issue3});
103+
session.Save(new TimeChunk {Issue = issue3});
104+
session.Save(new TimeChunk {Issue = issue4});
105+
session.Save(new TimeChunk {Issue = issue4});
106+
session.Save(new TimeChunk {Issue = issue5});
107+
session.Save(new TimeChunk {Issue = issue5});
108+
109+
transaction.Commit();
110+
}
111+
}
112+
113+
protected override void OnTearDown()
114+
{
115+
using (var session = OpenSession())
116+
using (var transaction = session.BeginTransaction())
117+
{
118+
session.Delete("from System.Object");
119+
transaction.Commit();
120+
}
121+
}
122+
123+
[Test(Description = "GH-2857")]
124+
public async Task GroupLevelQueryAsync()
125+
{
126+
using (var session = OpenSession())
127+
{
128+
var query = session.Query<ITimeChunk>()
129+
.Select(x => new object[] {(object) x})
130+
.GroupBy(g => new object[] {(Guid?) (((ITimeChunk) g[0]).Issue.Project.Id)}, v => (ITimeChunk) v[0])
131+
.Select(r => new object[] {r.Key, r.Sum(t => (int?) t.Seconds)});
132+
133+
var results = await (query.ToListAsync());
134+
Assert.That(results, Has.Count.EqualTo(2));
135+
}
136+
}
137+
138+
[Test(Description = "GH-2857")]
139+
public async Task GroupLevelQuery_SimplifiedAsync()
140+
{
141+
using (var session = OpenSession())
142+
{
143+
var query = session.Query<ITimeChunk>()
144+
.Select(x => new object[] {x})
145+
.GroupBy(g => new object[] {((ITimeChunk) g[0]).Issue.Project.Id}, v => (ITimeChunk) v[0])
146+
.Select(r => new object[] {r.Key, r.Sum(t => (int?) t.Seconds)});
147+
148+
var results = await (query.ToListAsync());
149+
Assert.That(results, Has.Count.EqualTo(2));
150+
}
151+
}
152+
153+
[Test]
154+
public async Task SelectManySubQueryWithCoalesceAsync()
155+
{
156+
using (var session = OpenSession())
157+
{
158+
var usedDepartments = session.Query<ITimeChunk>()
159+
.SelectMany(x => ((IEnumerable<object>) x.Issue.Departments).DefaultIfEmpty().Select(d => (object) ((Guid?) ((Guid?) (((IDepartment) d).Id) ?? x.Issue.Project.Department.Id))))
160+
.Where(id => id != null)
161+
.Select(id => (Guid?) id);
162+
163+
var result = session.Query<IDepartment>()
164+
.Where(d => usedDepartments.Contains(d.Id))
165+
.Select(d => new {d.Id, d.Name});
166+
167+
Assert.That(await (result.ToListAsync()), Has.Count.EqualTo(4));
168+
}
169+
}
170+
171+
[Test]
172+
public async Task SelectManySubQueryWithCoalesce_SimplifiedAsync()
173+
{
174+
using (var session = OpenSession())
175+
{
176+
var usedDepartments = session.Query<ITimeChunk>()
177+
.SelectMany(x => ((IEnumerable<object>) x.Issue.Departments).DefaultIfEmpty().Select(d => (Guid?) ((IDepartment) d).Id ?? x.Issue.Project.Department.Id));
178+
179+
var result = session.Query<IDepartment>()
180+
.Where(d => usedDepartments.Contains(d.Id))
181+
.Select(d => new {d.Id, d.Name});
182+
183+
Assert.That(await (result.ToListAsync()), Has.Count.EqualTo(4));
184+
}
185+
}
186+
}
187+
}

src/NHibernate.Test/Linq/ParameterTypeLocatorTests.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,16 @@ public void SubClassStringEnumTest()
9797
);
9898
}
9999

100+
[Test]
101+
public void SubClassAsUnamppedInterfaceStringEnumTest()
102+
{
103+
AssertResults(
104+
new Dictionary<string, Predicate<IType>> {{"0", o => o is EnumStoredAsStringType}},
105+
db.Animals.Where(o => o.Children.OfType<IReptile>().Any(r => r.Enum1 == EnumStoredAsString.Unspecified)),
106+
db.Animals.Where(o => o.Children.OfType<IReptile>().Any(r => EnumStoredAsString.Unspecified == r.Enum1))
107+
);
108+
}
109+
100110
[Test]
101111
public void EqualsMethodStringTest()
102112
{

src/NHibernate.Test/Linq/TryGetMappedTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public void SelfCastNotMappedTest()
124124
false,
125125
typeof(A).FullName,
126126
null,
127-
o => o is SerializableType serializableType && serializableType.ReturnedClass == typeof(object));
127+
o => o is EntityType entityType && entityType.ReturnedClass == typeof(A));
128128
}
129129

130130
[Test]
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace NHibernate.Test.NHSpecificTest.GH2858
5+
{
6+
public interface IDepartment
7+
{
8+
Guid Id { get; set; }
9+
string Name { get; set; }
10+
}
11+
12+
public class Department : IDepartment
13+
{
14+
public virtual Guid Id { get; set; }
15+
public virtual string Name { get; set; }
16+
}
17+
18+
public interface IProject
19+
{
20+
Guid Id { get; set; }
21+
string Name { get; set; }
22+
Department Department { get; set; }
23+
}
24+
25+
public class Project : IProject
26+
{
27+
public virtual Guid Id { get; set; }
28+
public virtual string Name { get; set; }
29+
public virtual Department Department { get; set; }
30+
}
31+
32+
public interface IIssue
33+
{
34+
Guid Id { get; set; }
35+
string Name { get; set; }
36+
Project Project { get; set; }
37+
IList<Department> Departments { get; set; }
38+
}
39+
40+
public class Issue : IIssue
41+
{
42+
public virtual Guid Id { get; set; }
43+
public virtual string Name { get; set; }
44+
public virtual IList<Department> Departments { get; set; } = new List<Department>();
45+
public virtual Project Project { get; set; }
46+
}
47+
48+
public interface ITimeChunk
49+
{
50+
Guid Id { get; set; }
51+
Issue Issue { get; set; }
52+
int Seconds { get; set; }
53+
}
54+
55+
public class TimeChunk : ITimeChunk
56+
{
57+
public virtual Guid Id { get; set; }
58+
public virtual Issue Issue { get; set; }
59+
public virtual int Seconds { get; set; }
60+
}
61+
}

0 commit comments

Comments
 (0)