Skip to content

Commit 02e4094

Browse files
authored
Merge pull request #379 from DataObjects-NET/6.0-same-param-in-inner-outer-selector
Denies Join/LeftJoin to use the same expression instance for both inner and outer selectors
2 parents 7193d7a + 2e44cde commit 02e4094

File tree

5 files changed

+191
-0
lines changed

5 files changed

+191
-0
lines changed

ChangeLog/6.0.13_dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[main] Fixed certain cases of bad translation of casts via 'as' operator in LINQ queries
22
[main] Addressed certain issues of translation connected with comparison with local entity instace within LINQ queries
33
[main] Fixed rare issues of incorrect translation of filtered index expressions including conditional expressions
4+
[main] Join/LeftJoin is denied to have the same expression instance for both inner/outer selector.
45
[postgresql] Fixed issue of incorrect translation of contitional expressions including comparison with nullable fields
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
// Copyright (C) 2024 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
4+
5+
using System;
6+
using System.Linq;
7+
using System.Linq.Expressions;
8+
using NUnit.Framework;
9+
using Xtensive.Orm.Configuration;
10+
using Xtensive.Orm.Tests.Issues.IssueJira0803_ReuseOfJoiningExpressionCausesWrongTranslationModel;
11+
12+
namespace Xtensive.Orm.Tests.Issues.IssueJira0803_ReuseOfJoiningExpressionCausesWrongTranslationModel
13+
{
14+
[HierarchyRoot]
15+
public class TestEntity : Entity
16+
{
17+
[Field, Key]
18+
public int Id { get; private set; }
19+
20+
[Field(Nullable = false)]
21+
public string Name { get; set; }
22+
23+
[Field]
24+
public string Description { get; set; }
25+
26+
[Field]
27+
public string Text { get; set; }
28+
29+
public TestEntity(Session session)
30+
: base(session)
31+
{
32+
}
33+
}
34+
}
35+
36+
namespace Xtensive.Orm.Tests.Issues
37+
{
38+
public sealed class IssueJira0803_ReuseOfJoiningExpressionCausesWrongTranslation : AutoBuildTest
39+
{
40+
protected override DomainConfiguration BuildConfiguration()
41+
{
42+
var configuration = base.BuildConfiguration();
43+
configuration.UpgradeMode = DomainUpgradeMode.Recreate;
44+
configuration.Types.Register(typeof(TestEntity));
45+
return configuration;
46+
}
47+
48+
protected override void PopulateData()
49+
{
50+
using (var session = Domain.OpenSession())
51+
using (var tx = session.OpenTransaction()) {
52+
_ = new TestEntity(session) { Name = "1", Description = "test", Text = "text" };
53+
_ = new TestEntity(session) { Name = "1", Description = "test", Text = "text" };
54+
_ = new TestEntity(session) { Name = "1", Description = null, Text = "text" };
55+
56+
tx.Complete();
57+
}
58+
}
59+
60+
[Test]
61+
public void LeftJoinOneVariableUsage()
62+
{
63+
Expression<Func<TestEntity, int>> key = it => it.Id;
64+
65+
using (var session = Domain.OpenSession())
66+
using (var tx = session.OpenTransaction()) {
67+
68+
var leftJoinWithExpression = session.Query.All<TestEntity>()
69+
.LeftJoin(session.Query.All<TestEntity>().Where(it => it.Description == null),
70+
o => o.Id,
71+
key,
72+
(o, i) => o)
73+
.Where(it => it.Text != null)
74+
.Select(it => it.Id)
75+
.Distinct()
76+
.ToList();
77+
78+
Assert.AreEqual(3, leftJoinWithExpression.Count);
79+
80+
leftJoinWithExpression = session.Query.All<TestEntity>()
81+
.LeftJoin(session.Query.All<TestEntity>().Where(it => it.Description == null),
82+
key,
83+
i => i.Id,
84+
(o, i) => o)
85+
.Where(it => it.Text != null)
86+
.Select(it => it.Id)
87+
.Distinct()
88+
.ToList();
89+
90+
Assert.AreEqual(3, leftJoinWithExpression.Count);
91+
}
92+
}
93+
94+
[Test]
95+
public void LeftJoinTwoVariableUsage()
96+
{
97+
Expression<Func<TestEntity, int>> key = it => it.Id;
98+
99+
using (var session = Domain.OpenSession())
100+
using (var tx = session.OpenTransaction()) {
101+
102+
var ex = Assert.Throws<QueryTranslationException>(() =>
103+
_ = session.Query.All<TestEntity>()
104+
.LeftJoin(session.Query.All<TestEntity>().Where(it => it.Description == null),
105+
key,
106+
key,
107+
(o, i) => o)
108+
.Where(it => it.Text != null)
109+
.Select(it => it.Id)
110+
.Distinct()
111+
.ToList());
112+
113+
Assert.That(ex.InnerException, Is.InstanceOf<NotSupportedException>());
114+
}
115+
}
116+
117+
[Test]
118+
public void InnerJoinOneVariableUsage()
119+
{
120+
Expression<Func<TestEntity, int>> key = it => it.Id;
121+
122+
using (var session = Domain.OpenSession())
123+
using (var tx = session.OpenTransaction()) {
124+
125+
var leftJoinWithExpression = session.Query.All<TestEntity>()
126+
.Join(session.Query.All<TestEntity>().Where(it => it.Description == null),
127+
o => o.Id,
128+
key,
129+
(o, i) => o)
130+
.Where(it => it.Text != null)
131+
.Select(it => it.Id)
132+
.Distinct()
133+
.ToList();
134+
135+
Assert.AreEqual(1, leftJoinWithExpression.Count);
136+
137+
leftJoinWithExpression = session.Query.All<TestEntity>()
138+
.Join(session.Query.All<TestEntity>().Where(it => it.Description == null),
139+
key,
140+
i => i.Id,
141+
(o, i) => o)
142+
.Where(it => it.Text != null)
143+
.Select(it => it.Id)
144+
.Distinct()
145+
.ToList();
146+
147+
Assert.AreEqual(1, leftJoinWithExpression.Count);
148+
}
149+
}
150+
151+
[Test]
152+
public void InnerJoinTwoVariableUsage()
153+
{
154+
Expression<Func<TestEntity, int>> key = it => it.Id;
155+
156+
using (var session = Domain.OpenSession())
157+
using (var tx = session.OpenTransaction()) {
158+
159+
var ex = Assert.Throws<QueryTranslationException>(() =>
160+
_ = session.Query.All<TestEntity>()
161+
.Join(session.Query.All<TestEntity>().Where(it => it.Description == null),
162+
key,
163+
key,
164+
(o, i) => o)
165+
.Where(it => it.Text != null)
166+
.Select(it => it.Id)
167+
.Distinct()
168+
.ToList());
169+
170+
Assert.That(ex.InnerException, Is.InstanceOf<NotSupportedException>());
171+
}
172+
}
173+
}
174+
}

Orm/Xtensive.Orm/Orm/Linq/Translator.Queryable.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,10 @@ private ProjectionExpression VisitJoin(Expression outerSource, Expression innerS
10071007
{
10081008
var outerParameter = outerKey.Parameters[0];
10091009
var innerParameter = innerKey.Parameters[0];
1010+
if (innerParameter == outerParameter) {
1011+
throw new NotSupportedException(Strings.ExJoinHasSameInnerAndOuterParameterInstances);
1012+
}
1013+
10101014
var outerSequence = VisitSequence(outerSource);
10111015
var innerSequence = VisitSequence(innerSource);
10121016
using (context.Bindings.Add(outerParameter, outerSequence))

Orm/Xtensive.Orm/Strings.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Orm/Xtensive.Orm/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3476,4 +3476,7 @@ Error: {1}</value>
34763476
<data name="ComparisonOfTwoEntityFieldsIsNotSupported" xml:space="preserve">
34773477
<value>Comparison of two entity fields is not supported.</value>
34783478
</data>
3479+
<data name="ExJoinHasSameInnerAndOuterParameterInstances" xml:space="preserve">
3480+
<value>Inner and outer selector expressions have the same parameter instance. Probably you use the same lambda expression for both selectors, which is currently not supported.</value>
3481+
</data>
34793482
</root>

0 commit comments

Comments
 (0)