Skip to content

Commit ed0e599

Browse files
committed
Minimize Linq query parameters
1 parent 450313b commit ed0e599

File tree

5 files changed

+271
-4
lines changed

5 files changed

+271
-4
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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.Collections.Generic;
12+
using System.Linq;
13+
using System.Text.RegularExpressions;
14+
using NUnit.Framework;
15+
using NHibernate.Linq;
16+
17+
namespace NHibernate.Test.Linq
18+
{
19+
using System.Threading.Tasks;
20+
using System.Threading;
21+
[TestFixture]
22+
public class ParameterTestsAsync : LinqTestCase
23+
{
24+
[Test]
25+
public async Task UsingSameArrayParameterTwiceAsync()
26+
{
27+
var ids = new[] {11008, 11019, 11039};
28+
await (AssertTotalParametersAsync(
29+
db.Orders.Where(o => ids.Contains(o.OrderId) && ids.Contains(o.OrderId)),
30+
ids.Length));
31+
}
32+
33+
[Test]
34+
public async Task UsingDifferentArrayParametersAsync()
35+
{
36+
var ids = new[] { 11008, 11019, 11039 };
37+
var ids2 = new[] { 11008, 11019, 11039 };
38+
await (AssertTotalParametersAsync(
39+
db.Orders.Where(o => ids.Contains(o.OrderId) && ids2.Contains(o.OrderId)),
40+
ids.Length + ids2.Length));
41+
}
42+
43+
[Test]
44+
public async Task UsingSameListParameterTwiceAsync()
45+
{
46+
var ids = new List<int> { 11008, 11019, 11039 };
47+
await (AssertTotalParametersAsync(
48+
db.Orders.Where(o => ids.Contains(o.OrderId) && ids.Contains(o.OrderId)),
49+
ids.Count));
50+
}
51+
52+
[Test]
53+
public async Task UsingDifferentListParametersAsync()
54+
{
55+
var ids = new List<int> { 11008, 11019, 11039 };
56+
var ids2 = new List<int> { 11008, 11019, 11039 };
57+
await (AssertTotalParametersAsync(
58+
db.Orders.Where(o => ids.Contains(o.OrderId) && ids2.Contains(o.OrderId)),
59+
ids.Count + ids2.Count));
60+
}
61+
62+
[Test]
63+
public async Task UsingSameEntityParameterTwiceAsync()
64+
{
65+
var order = await (db.Orders.FirstAsync());
66+
await (AssertTotalParametersAsync(
67+
db.Orders.Where(o => o == order && o != order),
68+
1));
69+
}
70+
71+
[Test]
72+
public async Task UsingDifferentEntityParametersAsync()
73+
{
74+
var order = await (db.Orders.FirstAsync());
75+
var order2 = await (db.Orders.Skip(1).FirstAsync());
76+
await (AssertTotalParametersAsync(
77+
db.Orders.Where(o => o == order && o != order2),
78+
2));
79+
}
80+
81+
[Test]
82+
public async Task UsingSameValueTypeParameterTwiceAsync()
83+
{
84+
var value = 1;
85+
await (AssertTotalParametersAsync(
86+
db.Orders.Where(o => o.OrderId == value && o.OrderId != value),
87+
1));
88+
}
89+
90+
[Test]
91+
public async Task UsingDifferentValueTypeParametersAsync()
92+
{
93+
var value = 1;
94+
var value2 = 2;
95+
await (AssertTotalParametersAsync(
96+
db.Orders.Where(o => o.OrderId == value && o.OrderId != value2),
97+
2));
98+
}
99+
100+
[Test]
101+
public async Task UsingSameStringParameterTwiceAsync()
102+
{
103+
var value = "test";
104+
await (AssertTotalParametersAsync(
105+
db.Products.Where(o => o.Name == value && o.Name != value),
106+
1));
107+
}
108+
109+
[Test]
110+
public async Task UsingDifferentStringParametersAsync()
111+
{
112+
var value = "test";
113+
var value2 = "test2";
114+
await (AssertTotalParametersAsync(
115+
db.Products.Where(o => o.Name == value && o.Name != value2),
116+
2));
117+
}
118+
119+
private static async Task AssertTotalParametersAsync<T>(IQueryable<T> query, int parameterNumber, CancellationToken cancellationToken = default(CancellationToken))
120+
{
121+
using (var sqlSpy = new SqlLogSpy())
122+
{
123+
await (query.ToListAsync(cancellationToken));
124+
var sqlParameters = sqlSpy.GetWholeLog().Split(';')[1];
125+
var matches = Regex.Matches(sqlParameters, @"([\d\w]+)[\s]+\=", RegexOptions.IgnoreCase);
126+
127+
// Due to ODBC drivers not supporting parameter names, we have to do a distinct of parameter names.
128+
var distinctParameters = matches.Select(m => m.Groups[1].Value.Trim()).Distinct().ToList();
129+
Assert.That(distinctParameters, Has.Count.EqualTo(parameterNumber));
130+
}
131+
}
132+
}
133+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Text.RegularExpressions;
4+
using NUnit.Framework;
5+
6+
namespace NHibernate.Test.Linq
7+
{
8+
[TestFixture]
9+
public class ParameterTests : LinqTestCase
10+
{
11+
[Test]
12+
public void UsingSameArrayParameterTwice()
13+
{
14+
var ids = new[] {11008, 11019, 11039};
15+
AssertTotalParameters(
16+
db.Orders.Where(o => ids.Contains(o.OrderId) && ids.Contains(o.OrderId)),
17+
ids.Length);
18+
}
19+
20+
[Test]
21+
public void UsingDifferentArrayParameters()
22+
{
23+
var ids = new[] { 11008, 11019, 11039 };
24+
var ids2 = new[] { 11008, 11019, 11039 };
25+
AssertTotalParameters(
26+
db.Orders.Where(o => ids.Contains(o.OrderId) && ids2.Contains(o.OrderId)),
27+
ids.Length + ids2.Length);
28+
}
29+
30+
[Test]
31+
public void UsingSameListParameterTwice()
32+
{
33+
var ids = new List<int> { 11008, 11019, 11039 };
34+
AssertTotalParameters(
35+
db.Orders.Where(o => ids.Contains(o.OrderId) && ids.Contains(o.OrderId)),
36+
ids.Count);
37+
}
38+
39+
[Test]
40+
public void UsingDifferentListParameters()
41+
{
42+
var ids = new List<int> { 11008, 11019, 11039 };
43+
var ids2 = new List<int> { 11008, 11019, 11039 };
44+
AssertTotalParameters(
45+
db.Orders.Where(o => ids.Contains(o.OrderId) && ids2.Contains(o.OrderId)),
46+
ids.Count + ids2.Count);
47+
}
48+
49+
[Test]
50+
public void UsingSameEntityParameterTwice()
51+
{
52+
var order = db.Orders.First();
53+
AssertTotalParameters(
54+
db.Orders.Where(o => o == order && o != order),
55+
1);
56+
}
57+
58+
[Test]
59+
public void UsingDifferentEntityParameters()
60+
{
61+
var order = db.Orders.First();
62+
var order2 = db.Orders.Skip(1).First();
63+
AssertTotalParameters(
64+
db.Orders.Where(o => o == order && o != order2),
65+
2);
66+
}
67+
68+
[Test]
69+
public void UsingSameValueTypeParameterTwice()
70+
{
71+
var value = 1;
72+
AssertTotalParameters(
73+
db.Orders.Where(o => o.OrderId == value && o.OrderId != value),
74+
1);
75+
}
76+
77+
[Test]
78+
public void UsingDifferentValueTypeParameters()
79+
{
80+
var value = 1;
81+
var value2 = 2;
82+
AssertTotalParameters(
83+
db.Orders.Where(o => o.OrderId == value && o.OrderId != value2),
84+
2);
85+
}
86+
87+
[Test]
88+
public void UsingSameStringParameterTwice()
89+
{
90+
var value = "test";
91+
AssertTotalParameters(
92+
db.Products.Where(o => o.Name == value && o.Name != value),
93+
1);
94+
}
95+
96+
[Test]
97+
public void UsingDifferentStringParameters()
98+
{
99+
var value = "test";
100+
var value2 = "test2";
101+
AssertTotalParameters(
102+
db.Products.Where(o => o.Name == value && o.Name != value2),
103+
2);
104+
}
105+
106+
private static void AssertTotalParameters<T>(IQueryable<T> query, int parameterNumber)
107+
{
108+
using (var sqlSpy = new SqlLogSpy())
109+
{
110+
query.ToList();
111+
var sqlParameters = sqlSpy.GetWholeLog().Split(';')[1];
112+
var matches = Regex.Matches(sqlParameters, @"([\d\w]+)[\s]+\=", RegexOptions.IgnoreCase);
113+
114+
// Due to ODBC drivers not supporting parameter names, we have to do a distinct of parameter names.
115+
var distinctParameters = matches.Select(m => m.Groups[1].Value.Trim()).Distinct().ToList();
116+
Assert.That(distinctParameters, Has.Count.EqualTo(parameterNumber));
117+
}
118+
}
119+
}
120+
}

src/NHibernate/Linq/NhLinqExpression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public NhLinqExpression(Expression expression, ISessionFactoryImplementor sessio
4949

5050
_constantToParameterMap = ExpressionParameterVisitor.Visit(ref _expression, sessionFactory);
5151

52-
ParameterValuesByName = _constantToParameterMap.Values.ToDictionary(p => p.Name,
52+
ParameterValuesByName = _constantToParameterMap.Values.Distinct().ToDictionary(p => p.Name,
5353
p => System.Tuple.Create(p.Value, p.Type));
5454

5555
Key = ExpressionKeyVisitor.Visit(_expression, _constantToParameterMap);

src/NHibernate/Linq/Visitors/ExpressionParameterVisitor.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace NHibernate.Linq.Visitors
1717
public class ExpressionParameterVisitor : RelinqExpressionVisitor
1818
{
1919
private readonly Dictionary<ConstantExpression, NamedParameter> _parameters = new Dictionary<ConstantExpression, NamedParameter>();
20+
private readonly Dictionary<object, NamedParameter> _valueParameters = new Dictionary<object, NamedParameter>();
2021
private readonly ISessionFactoryImplementor _sessionFactory;
2122

2223
private static readonly MethodInfo QueryableSkipDefinition =
@@ -122,7 +123,19 @@ protected override Expression VisitConstant(ConstantExpression expression)
122123
// comes up, it would be nice to combine the HQL parameter type determination code
123124
// and the Expression information.
124125

125-
_parameters.Add(expression, new NamedParameter("p" + (_parameters.Count + 1), value, type));
126+
// Create only one parameter for the same value
127+
if (value == null || !_valueParameters.TryGetValue(value, out var parameter))
128+
{
129+
parameter = new NamedParameter("p" + (_parameters.Count + 1), value, type);
130+
if (value != null)
131+
{
132+
_valueParameters.Add(value, parameter);
133+
}
134+
}
135+
136+
_parameters.Add(expression, parameter);
137+
138+
return base.VisitConstant(expression);
126139
}
127140

128141
return base.VisitConstant(expression);

src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessContains.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@ private static HqlAlias GetFromAlias(HqlTreeNode node)
5959
private static bool IsEmptyList(HqlParameter source, VisitorParameters parameters)
6060
{
6161
var parameterName = source.NodesPreOrder.Single(n => n is HqlIdent).AstNode.Text;
62-
var parameterValue = parameters.ConstantToParameterMap.Single(p => p.Value.Name == parameterName).Key.Value;
62+
// Multiple constants may be linked to the same parameter, take the first matching parameter
63+
var parameterValue = parameters.ConstantToParameterMap.First(p => p.Value.Name == parameterName).Key.Value;
6364
return !((IEnumerable)parameterValue).Cast<object>().Any();
6465
}
6566
}
66-
}
67+
}

0 commit comments

Comments
 (0)