Skip to content

Commit 500a8b8

Browse files
authored
Minimize Linq query parameters (#2335)
1 parent e0de3ab commit 500a8b8

14 files changed

+1260
-51
lines changed
Lines changed: 398 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,398 @@
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 System.Linq.Expressions;
15+
using System.Reflection;
16+
using System.Text.RegularExpressions;
17+
using NHibernate.DomainModel.Northwind.Entities;
18+
using NHibernate.Engine.Query;
19+
using NHibernate.Linq;
20+
using NHibernate.Util;
21+
using NUnit.Framework;
22+
23+
namespace NHibernate.Test.Linq
24+
{
25+
using System.Threading.Tasks;
26+
using System.Threading;
27+
[TestFixture]
28+
public class ParameterTestsAsync : LinqTestCase
29+
{
30+
[Test]
31+
public async Task UsingArrayParameterTwiceAsync()
32+
{
33+
var ids = new[] {11008, 11019, 11039};
34+
await (AssertTotalParametersAsync(
35+
db.Orders.Where(o => ids.Contains(o.OrderId) && ids.Contains(o.OrderId)),
36+
ids.Length,
37+
1));
38+
}
39+
40+
[Test]
41+
public async Task UsingTwoArrayParametersAsync()
42+
{
43+
var ids = new[] {11008, 11019, 11039};
44+
var ids2 = new[] {11008, 11019, 11039};
45+
await (AssertTotalParametersAsync(
46+
db.Orders.Where(o => ids.Contains(o.OrderId) && ids2.Contains(o.OrderId)),
47+
ids.Length + ids2.Length,
48+
2));
49+
}
50+
51+
[Test]
52+
public async Task UsingListParameterTwiceAsync()
53+
{
54+
var ids = new List<int> {11008, 11019, 11039};
55+
await (AssertTotalParametersAsync(
56+
db.Orders.Where(o => ids.Contains(o.OrderId) && ids.Contains(o.OrderId)),
57+
ids.Count,
58+
1));
59+
}
60+
61+
[Test]
62+
public async Task UsingTwoListParametersAsync()
63+
{
64+
var ids = new List<int> {11008, 11019, 11039};
65+
var ids2 = new List<int> {11008, 11019, 11039};
66+
await (AssertTotalParametersAsync(
67+
db.Orders.Where(o => ids.Contains(o.OrderId) && ids2.Contains(o.OrderId)),
68+
ids.Count + ids2.Count,
69+
2));
70+
}
71+
72+
[Test]
73+
public async Task UsingEntityParameterTwiceAsync()
74+
{
75+
var order = await (db.Orders.FirstAsync());
76+
await (AssertTotalParametersAsync(
77+
db.Orders.Where(o => o == order && o != order),
78+
1));
79+
}
80+
81+
[Test]
82+
public async Task UsingTwoEntityParametersAsync()
83+
{
84+
var order = await (db.Orders.FirstAsync());
85+
var order2 = await (db.Orders.FirstAsync());
86+
await (AssertTotalParametersAsync(
87+
db.Orders.Where(o => o == order && o != order2),
88+
2));
89+
}
90+
91+
[Test]
92+
public async Task UsingValueTypeParameterTwiceAsync()
93+
{
94+
var value = 1;
95+
await (AssertTotalParametersAsync(
96+
db.Orders.Where(o => o.OrderId == value && o.OrderId != value),
97+
1));
98+
}
99+
100+
[Test]
101+
public async Task UsingNegateValueTypeParameterTwiceAsync()
102+
{
103+
var value = 1;
104+
await (AssertTotalParametersAsync(
105+
db.Orders.Where(o => o.OrderId == -value && o.OrderId != -value),
106+
1));
107+
}
108+
109+
[Test]
110+
public async Task UsingNegateValueTypeParameterAsync()
111+
{
112+
var value = 1;
113+
await (AssertTotalParametersAsync(
114+
db.Orders.Where(o => o.OrderId == value && o.OrderId != -value),
115+
1));
116+
}
117+
118+
[Test]
119+
public async Task UsingValueTypeParameterInArrayAsync()
120+
{
121+
var id = 11008;
122+
await (AssertTotalParametersAsync(
123+
db.Orders.Where(o => new[] {id, 11019}.Contains(o.OrderId) && new[] {id, 11019}.Contains(o.OrderId)),
124+
4,
125+
2));
126+
}
127+
128+
[Test]
129+
public async Task UsingTwoValueTypeParametersAsync()
130+
{
131+
var value = 1;
132+
var value2 = 1;
133+
await (AssertTotalParametersAsync(
134+
db.Orders.Where(o => o.OrderId == value && o.OrderId != value2),
135+
2));
136+
}
137+
138+
[Test]
139+
public async Task UsingStringParameterTwiceAsync()
140+
{
141+
var value = "test";
142+
await (AssertTotalParametersAsync(
143+
db.Products.Where(o => o.Name == value && o.Name != value),
144+
1));
145+
}
146+
147+
[Test]
148+
public async Task UsingTwoStringParametersAsync()
149+
{
150+
var value = "test";
151+
var value2 = "test";
152+
await (AssertTotalParametersAsync(
153+
db.Products.Where(o => o.Name == value && o.Name != value2),
154+
2));
155+
}
156+
157+
[Test]
158+
public async Task UsingObjectPropertyParameterTwiceAsync()
159+
{
160+
var value = new Product {Name = "test"};
161+
await (AssertTotalParametersAsync(
162+
db.Products.Where(o => o.Name == value.Name && o.Name != value.Name),
163+
1));
164+
}
165+
166+
[Test]
167+
public async Task UsingTwoObjectPropertyParametersAsync()
168+
{
169+
var value = new Product {Name = "test"};
170+
var value2 = new Product {Name = "test"};
171+
await (AssertTotalParametersAsync(
172+
db.Products.Where(o => o.Name == value.Name && o.Name != value2.Name),
173+
2));
174+
}
175+
176+
[Test]
177+
public async Task UsingParameterInWhereSkipTakeAsync()
178+
{
179+
var value3 = 1;
180+
var q1 = db.Products.Where(o => o.ProductId < value3).Take(value3).Skip(value3);
181+
await (AssertTotalParametersAsync(q1, 3));
182+
}
183+
184+
[Test]
185+
public async Task UsingParameterInTwoWhereAsync()
186+
{
187+
var value3 = 1;
188+
var q1 = db.Products.Where(o => o.ProductId < value3).Where(o => o.ProductId < value3);
189+
await (AssertTotalParametersAsync(q1, 1));
190+
}
191+
192+
[Test]
193+
public async Task UsingObjectNestedPropertyParameterTwiceAsync()
194+
{
195+
var value = new Employee {Superior = new Employee {Superior = new Employee {FirstName = "test"}}};
196+
await (AssertTotalParametersAsync(
197+
db.Employees.Where(o => o.FirstName == value.Superior.Superior.FirstName && o.FirstName != value.Superior.Superior.FirstName),
198+
1));
199+
}
200+
201+
[Test]
202+
public async Task UsingDifferentObjectNestedPropertyParameterAsync()
203+
{
204+
var value = new Employee {Superior = new Employee {FirstName = "test", Superior = new Employee {FirstName = "test"}}};
205+
await (AssertTotalParametersAsync(
206+
db.Employees.Where(o => o.FirstName == value.Superior.FirstName && o.FirstName != value.Superior.Superior.FirstName),
207+
2));
208+
}
209+
210+
[Test]
211+
public async Task UsingMethodObjectPropertyParameterTwiceAsync()
212+
{
213+
var value = new Product {Name = "test"};
214+
await (AssertTotalParametersAsync(
215+
db.Products.Where(o => o.Name == value.Name.Trim() && o.Name != value.Name.Trim()),
216+
2));
217+
}
218+
219+
[Test]
220+
public async Task UsingStaticMethodObjectPropertyParameterTwiceAsync()
221+
{
222+
var value = new Product {Name = "test"};
223+
await (AssertTotalParametersAsync(
224+
db.Products.Where(o => o.Name == string.Copy(value.Name) && o.Name != string.Copy(value.Name)),
225+
2));
226+
}
227+
228+
[Test]
229+
public async Task UsingObjectPropertyParameterWithSecondLevelClosureAsync()
230+
{
231+
var value = new Product {Name = "test"};
232+
Expression<Func<Product, bool>> predicate = o => o.Name == value.Name && o.Name != value.Name;
233+
await (AssertTotalParametersAsync(
234+
db.Products.Where(predicate),
235+
1));
236+
}
237+
238+
[Test]
239+
public async Task UsingObjectPropertyParameterWithThirdLevelClosureAsync()
240+
{
241+
var value = new Product {Name = "test"};
242+
Expression<Func<OrderLine, bool>> orderLinePredicate = o => o.Order.ShippedTo == value.Name && o.Order.ShippedTo != value.Name;
243+
Expression<Func<Product, bool>> predicate = o => o.Name == value.Name && o.OrderLines.AsQueryable().Any(orderLinePredicate);
244+
await (AssertTotalParametersAsync(
245+
db.Products.Where(predicate),
246+
1));
247+
}
248+
249+
[Test]
250+
public async Task UsingParameterInDMLInsertIntoFourTimesAsync()
251+
{
252+
var value = "test";
253+
await (AssertTotalParametersAsync(
254+
QueryMode.Insert,
255+
db.Customers.Where(c => c.CustomerId == value),
256+
x => new Customer {CustomerId = value, ContactName = value, CompanyName = value},
257+
4));
258+
}
259+
260+
[Test]
261+
public async Task UsingFourParametersInDMLInsertIntoAsync()
262+
{
263+
var value = "test";
264+
var value2 = "test";
265+
var value3 = "test";
266+
var value4 = "test";
267+
await (AssertTotalParametersAsync(
268+
QueryMode.Insert,
269+
db.Customers.Where(c => c.CustomerId == value3),
270+
x => new Customer {CustomerId = value4, ContactName = value2, CompanyName = value},
271+
4));
272+
}
273+
274+
[Test]
275+
public async Task UsingParameterInDMLUpdateThreeTimesAsync()
276+
{
277+
var value = "test";
278+
await (AssertTotalParametersAsync(
279+
QueryMode.Update,
280+
db.Customers.Where(c => c.CustomerId == value),
281+
x => new Customer {ContactName = value, CompanyName = value},
282+
3));
283+
}
284+
285+
[Test]
286+
public async Task UsingThreeParametersInDMLUpdateAsync()
287+
{
288+
var value = "test";
289+
var value2 = "test";
290+
var value3 = "test";
291+
await (AssertTotalParametersAsync(
292+
QueryMode.Update,
293+
db.Customers.Where(c => c.CustomerId == value3),
294+
x => new Customer { ContactName = value2, CompanyName = value },
295+
3));
296+
}
297+
298+
[Test]
299+
public async Task UsingParameterInDMLDeleteTwiceAsync()
300+
{
301+
var value = "test";
302+
await (AssertTotalParametersAsync(
303+
QueryMode.Delete,
304+
db.Customers.Where(c => c.CustomerId == value && c.CompanyName == value),
305+
2));
306+
}
307+
308+
[Test]
309+
public async Task UsingTwoParametersInDMLDeleteAsync()
310+
{
311+
var value = "test";
312+
var value2 = "test";
313+
await (AssertTotalParametersAsync(
314+
QueryMode.Delete,
315+
db.Customers.Where(c => c.CustomerId == value && c.CompanyName == value2),
316+
2));
317+
}
318+
319+
private async Task AssertTotalParametersAsync<T>(IQueryable<T> query, int parameterNumber, int? linqParameterNumber = null, CancellationToken cancellationToken = default(CancellationToken))
320+
{
321+
using (var sqlSpy = new SqlLogSpy())
322+
{
323+
// In case of arrays linqParameterNumber and parameterNumber will be different
324+
Assert.That(
325+
GetLinqExpression(query).ParameterValuesByName.Count,
326+
Is.EqualTo(linqParameterNumber ?? parameterNumber),
327+
"Linq expression has different number of parameters");
328+
329+
var queryPlanCacheType = typeof(QueryPlanCache);
330+
var cache = (SoftLimitMRUCache)
331+
queryPlanCacheType
332+
.GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic)
333+
.GetValue(Sfi.QueryPlanCache);
334+
cache.Clear();
335+
336+
await (query.ToListAsync(cancellationToken));
337+
338+
// In case of arrays two query plans will be stored, one with an one without expended parameters
339+
Assert.That(cache, Has.Count.EqualTo(linqParameterNumber.HasValue ? 2 : 1), "Query should be cacheable");
340+
341+
AssertParameters(sqlSpy, parameterNumber);
342+
}
343+
}
344+
345+
private static Task AssertTotalParametersAsync<T>(QueryMode queryMode, IQueryable<T> query, int parameterNumber, CancellationToken cancellationToken = default(CancellationToken))
346+
{
347+
return AssertTotalParametersAsync(queryMode, query, null, parameterNumber, cancellationToken);
348+
}
349+
350+
private static async Task AssertTotalParametersAsync<T>(QueryMode queryMode, IQueryable<T> query, Expression<Func<T, T>> expression, int parameterNumber, CancellationToken cancellationToken = default(CancellationToken))
351+
{
352+
var provider = query.Provider as INhQueryProvider;
353+
Assert.That(provider, Is.Not.Null);
354+
355+
var dmlExpression = expression != null
356+
? DmlExpressionRewriter.PrepareExpression(query.Expression, expression)
357+
: query.Expression;
358+
359+
using (var sqlSpy = new SqlLogSpy())
360+
{
361+
Assert.That(await (provider.ExecuteDmlAsync<T>(queryMode, dmlExpression, cancellationToken)), Is.EqualTo(0), "The DML query updated the data"); // Avoid updating the data
362+
AssertParameters(sqlSpy, parameterNumber);
363+
}
364+
}
365+
366+
private static void AssertParameters(SqlLogSpy sqlSpy, int parameterNumber)
367+
{
368+
var sqlParameters = sqlSpy.GetWholeLog().Split(';')[1];
369+
var matches = Regex.Matches(sqlParameters, @"([\d\w]+)[\s]+\=", RegexOptions.IgnoreCase);
370+
371+
// Due to ODBC drivers not supporting parameter names, we have to do a distinct of parameter names.
372+
var distinctParameters = matches.OfType<Match>().Select(m => m.Groups[1].Value.Trim()).Distinct().ToList();
373+
Assert.That(distinctParameters, Has.Count.EqualTo(parameterNumber));
374+
}
375+
376+
private NhLinqExpression GetLinqExpression<T>(QueryMode queryMode, IQueryable<T> query, Expression<Func<T, T>> expression)
377+
{
378+
return GetLinqExpression(queryMode, DmlExpressionRewriter.PrepareExpression(query.Expression, expression));
379+
}
380+
381+
private NhLinqExpression GetLinqExpression<T>(QueryMode queryMode, IQueryable<T> query)
382+
{
383+
return GetLinqExpression(queryMode, query.Expression);
384+
}
385+
386+
private NhLinqExpression GetLinqExpression<T>(IQueryable<T> query)
387+
{
388+
return GetLinqExpression(QueryMode.Select, query.Expression);
389+
}
390+
391+
private NhLinqExpression GetLinqExpression(QueryMode queryMode, Expression expression)
392+
{
393+
return queryMode == QueryMode.Select
394+
? new NhLinqExpression(expression, Sfi)
395+
: new NhLinqDmlExpression<Customer>(queryMode, expression, Sfi);
396+
}
397+
}
398+
}

0 commit comments

Comments
 (0)