Skip to content

Commit e6b8dbc

Browse files
authored
Avoid double param type guessing and better NULL parameter handling in LINQ (#2723)
1 parent ca2aa47 commit e6b8dbc

File tree

7 files changed

+298
-33
lines changed

7 files changed

+298
-33
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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.Data;
12+
using System.Linq;
13+
using NHibernate.Cfg.MappingSchema;
14+
using NHibernate.Mapping.ByCode;
15+
using NHibernate.SqlTypes;
16+
using NUnit.Framework;
17+
using NHibernate.Linq;
18+
19+
namespace NHibernate.Test.NHSpecificTest.NH3565
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+
mapper.Class<Entity>(rc =>
29+
{
30+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
31+
rc.Property(x => x.Name, m =>
32+
{
33+
m.Type(NHibernateUtil.AnsiString);
34+
m.Length(10);
35+
});
36+
});
37+
38+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
39+
}
40+
41+
protected override bool AppliesTo(Dialect.Dialect dialect)
42+
{
43+
return base.AppliesTo(dialect)
44+
//Dialects like SQL Server CE, Firebird don't distinguish AnsiString from String
45+
&& Dialect.GetTypeName(new SqlType(DbType.AnsiString)) != Dialect.GetTypeName(new SqlType(DbType.String));
46+
}
47+
48+
protected override void OnSetUp()
49+
{
50+
using (var session = OpenSession())
51+
using (var transaction = session.BeginTransaction())
52+
{
53+
var e1 = new Entity {Name = "Bob"};
54+
session.Save(e1);
55+
56+
var e2 = new Entity {Name = "Sally"};
57+
session.Save(e2);
58+
59+
transaction.Commit();
60+
}
61+
}
62+
63+
protected override void OnTearDown()
64+
{
65+
using (var session = OpenSession())
66+
using (var transaction = session.BeginTransaction())
67+
{
68+
session.CreateQuery("delete from System.Object").ExecuteUpdate();
69+
70+
transaction.Commit();
71+
}
72+
}
73+
74+
[Test]
75+
public async Task ParameterTypeForLikeIsProperlyDetectedAsync()
76+
{
77+
using (var logSpy = new SqlLogSpy())
78+
using (var session = OpenSession())
79+
{
80+
var result = from e in session.Query<Entity>()
81+
where NHibernate.Linq.SqlMethods.Like(e.Name, "Bob")
82+
select e;
83+
84+
Assert.That(await (result.ToListAsync()), Has.Count.EqualTo(1));
85+
Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString"));
86+
}
87+
}
88+
89+
[KnownBug("Not fixed yet")]
90+
[Test]
91+
public async Task ParameterTypeForContainsIsProperlyDetectedAsync()
92+
{
93+
using (var logSpy = new SqlLogSpy())
94+
using (var session = OpenSession())
95+
{
96+
var result = from e in session.Query<Entity>()
97+
where e.Name.Contains("Bob")
98+
select e;
99+
100+
Assert.That(await (result.ToListAsync()), Has.Count.EqualTo(1));
101+
Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString"));
102+
}
103+
}
104+
105+
[KnownBug("Not fixed yet")]
106+
[Test]
107+
public async Task ParameterTypeForStartsWithIsProperlyDetectedAsync()
108+
{
109+
using (var logSpy = new SqlLogSpy())
110+
using (var session = OpenSession())
111+
{
112+
var result = from e in session.Query<Entity>()
113+
where e.Name.StartsWith("Bob")
114+
select e;
115+
116+
Assert.That(await (result.ToListAsync()), Has.Count.EqualTo(1));
117+
Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString"));
118+
}
119+
}
120+
}
121+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
3+
namespace NHibernate.Test.NHSpecificTest.NH3565
4+
{
5+
class Entity
6+
{
7+
public virtual Guid Id { get; set; }
8+
public virtual string Name { get; set; }
9+
}
10+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using System.Data;
2+
using System.Linq;
3+
using NHibernate.Cfg.MappingSchema;
4+
using NHibernate.Mapping.ByCode;
5+
using NHibernate.SqlTypes;
6+
using NUnit.Framework;
7+
8+
namespace NHibernate.Test.NHSpecificTest.NH3565
9+
{
10+
[TestFixture]
11+
public class ByCodeFixture : TestCaseMappingByCode
12+
{
13+
protected override HbmMapping GetMappings()
14+
{
15+
var mapper = new ModelMapper();
16+
mapper.Class<Entity>(rc =>
17+
{
18+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
19+
rc.Property(x => x.Name, m =>
20+
{
21+
m.Type(NHibernateUtil.AnsiString);
22+
m.Length(10);
23+
});
24+
});
25+
26+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
27+
}
28+
29+
protected override bool AppliesTo(Dialect.Dialect dialect)
30+
{
31+
return base.AppliesTo(dialect)
32+
//Dialects like SQL Server CE, Firebird don't distinguish AnsiString from String
33+
&& Dialect.GetTypeName(new SqlType(DbType.AnsiString)) != Dialect.GetTypeName(new SqlType(DbType.String));
34+
}
35+
36+
protected override void OnSetUp()
37+
{
38+
using (var session = OpenSession())
39+
using (var transaction = session.BeginTransaction())
40+
{
41+
var e1 = new Entity {Name = "Bob"};
42+
session.Save(e1);
43+
44+
var e2 = new Entity {Name = "Sally"};
45+
session.Save(e2);
46+
47+
transaction.Commit();
48+
}
49+
}
50+
51+
protected override void OnTearDown()
52+
{
53+
using (var session = OpenSession())
54+
using (var transaction = session.BeginTransaction())
55+
{
56+
session.CreateQuery("delete from System.Object").ExecuteUpdate();
57+
58+
transaction.Commit();
59+
}
60+
}
61+
62+
[Test]
63+
public void ParameterTypeForLikeIsProperlyDetected()
64+
{
65+
using (var logSpy = new SqlLogSpy())
66+
using (var session = OpenSession())
67+
{
68+
var result = from e in session.Query<Entity>()
69+
where NHibernate.Linq.SqlMethods.Like(e.Name, "Bob")
70+
select e;
71+
72+
Assert.That(result.ToList(), Has.Count.EqualTo(1));
73+
Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString"));
74+
}
75+
}
76+
77+
[KnownBug("Not fixed yet")]
78+
[Test]
79+
public void ParameterTypeForContainsIsProperlyDetected()
80+
{
81+
using (var logSpy = new SqlLogSpy())
82+
using (var session = OpenSession())
83+
{
84+
var result = from e in session.Query<Entity>()
85+
where e.Name.Contains("Bob")
86+
select e;
87+
88+
Assert.That(result.ToList(), Has.Count.EqualTo(1));
89+
Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString"));
90+
}
91+
}
92+
93+
[KnownBug("Not fixed yet")]
94+
[Test]
95+
public void ParameterTypeForStartsWithIsProperlyDetected()
96+
{
97+
using (var logSpy = new SqlLogSpy())
98+
using (var session = OpenSession())
99+
{
100+
var result = from e in session.Query<Entity>()
101+
where e.Name.StartsWith("Bob")
102+
select e;
103+
104+
Assert.That(result.ToList(), Has.Count.EqualTo(1));
105+
Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString"));
106+
}
107+
}
108+
}
109+
}

src/NHibernate/Async/IQuery.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313
using NHibernate.Transform;
1414
using NHibernate.Type;
1515
using System.Collections.Generic;
16+
using NHibernate.Impl;
1617

1718
namespace NHibernate
1819
{
1920
using System.Threading.Tasks;
2021
using System.Threading;
22+
2123
public partial interface IQuery
2224
{
2325

src/NHibernate/IQuery.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,38 @@
33
using NHibernate.Transform;
44
using NHibernate.Type;
55
using System.Collections.Generic;
6+
using NHibernate.Impl;
67

78
namespace NHibernate
89
{
10+
// 6.0 TODO remove
11+
internal static class QueryExtensions
12+
{
13+
/// <summary>
14+
/// Bind a value to a named query parameter
15+
/// </summary>
16+
/// <param name="query">The query</param>
17+
/// <param name="name">The name of the parameter</param>
18+
/// <param name="val">The possibly null parameter value</param>
19+
/// <param name="type">The NHibernate <see cref="IType"/>.</param>
20+
/// <param name="preferMetadataType">If true supplied type is used only if parameter metadata is missing</param>
21+
public static void SetParameter(this IQuery query, string name, object val, IType type, bool preferMetadataType)
22+
{
23+
if (query is AbstractQueryImpl impl)
24+
{
25+
impl.SetParameter(name, val, type, preferMetadataType);
26+
}
27+
else
28+
{
29+
//Let HQL try to process guessed types (hql doesn't support type guessing for NULL)
30+
if (type != null && (preferMetadataType == false || val == null))
31+
query.SetParameter(name, val, type);
32+
else
33+
query.SetParameter(name, val);
34+
}
35+
}
36+
}
37+
938
/// <summary>
1039
/// An object-oriented representation of a NHibernate query.
1140
/// </summary>

src/NHibernate/Impl/AbstractQueryImpl.cs

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -242,17 +242,33 @@ public IQuery SetParameter(int position, object val, IType type)
242242

243243
public IQuery SetParameter(string name, object val, IType type)
244244
{
245-
if (!parameterMetadata.NamedParameterNames.Contains(name))
246-
{
247-
if (shouldIgnoredUnknownNamedParameters)//just ignore it
248-
return this;
249-
throw new ArgumentException("Parameter " + name + " does not exist as a named parameter in [" + QueryString + "]");
250-
}
251-
else
252-
{
253-
namedParameters[name] = new TypedValue(type, val, false);
245+
return SetParameter(name, val, type, false);
246+
}
247+
248+
//TODO 6.0: Add to IQuery interface
249+
public IQuery SetParameter(string name, object val, IType type, bool preferMetadataType)
250+
{
251+
if (CheckParameterIgnored(name))
254252
return this;
253+
254+
if (type == null || preferMetadataType)
255+
{
256+
type = parameterMetadata.GetNamedParameterExpectedType(name) ?? type ?? ParameterHelper.GuessType(val, session.Factory);
255257
}
258+
259+
namedParameters[name] = new TypedValue(type, val, false);
260+
return this;
261+
}
262+
263+
private bool CheckParameterIgnored(string name)
264+
{
265+
if (parameterMetadata.NamedParameterNames.Contains(name))
266+
return false;
267+
268+
if (shouldIgnoredUnknownNamedParameters) //just ignore it
269+
return true;
270+
271+
throw new ArgumentException("Parameter " + name + " does not exist as a named parameter in [" + QueryString + "]");
256272
}
257273

258274
public IQuery SetParameter<T>(int position, T val)
@@ -289,29 +305,7 @@ public IQuery SetParameter<T>(string name, T val)
289305

290306
public IQuery SetParameter(string name, object val)
291307
{
292-
if (!parameterMetadata.NamedParameterNames.Contains(name))
293-
{
294-
if (shouldIgnoredUnknownNamedParameters)//just ignore it
295-
return this;
296-
}
297-
298-
if (val == null)
299-
{
300-
IType type = parameterMetadata.GetNamedParameterExpectedType(name);
301-
if (type == null)
302-
{
303-
throw new ArgumentNullException("val",
304-
"A type specific Set(name, val) should be called because the Type can not be guessed from a null value.");
305-
}
306-
307-
SetParameter(name, val, type);
308-
}
309-
else
310-
{
311-
SetParameter(name, val, DetermineType(name, val));
312-
}
313-
314-
return this;
308+
return SetParameter(name, val, null, true);
315309
}
316310

317311
public IQuery SetParameter(int position, object val)

src/NHibernate/Linq/DefaultQueryProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ private static void SetParameters(IQuery query, IDictionary<string, NamedParamet
265265
}
266266
else
267267
{
268-
query.SetParameter(parameter.Name, parameter.Value);
268+
query.SetParameter(parameter.Name, parameter.Value, parameter.Type, parameter.IsGuessedType);
269269
}
270270
}
271271
}

0 commit comments

Comments
 (0)