Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid double param type guessing and better NULL parameter handling in LINQ #2723

Merged
merged 11 commits into from
Aug 26, 2022
121 changes: 121 additions & 0 deletions src/NHibernate.Test/Async/NHSpecificTest/NH3565/FixtureByCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by AsyncGenerator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------


using System.Data;
using System.Linq;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Mapping.ByCode;
using NHibernate.SqlTypes;
using NUnit.Framework;
using NHibernate.Linq;

namespace NHibernate.Test.NHSpecificTest.NH3565
{
using System.Threading.Tasks;
[TestFixture]
public class ByCodeFixtureAsync : TestCaseMappingByCode
{
protected override HbmMapping GetMappings()
{
var mapper = new ModelMapper();
mapper.Class<Entity>(rc =>
{
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
rc.Property(x => x.Name, m =>
{
m.Type(NHibernateUtil.AnsiString);
m.Length(10);
});
});

return mapper.CompileMappingForAllExplicitlyAddedEntities();
}

protected override bool AppliesTo(Dialect.Dialect dialect)
{
return base.AppliesTo(dialect)
//Dialects like SQL Server CE, Firebird don't distinguish AnsiString from String
&& Dialect.GetTypeName(new SqlType(DbType.AnsiString)) != Dialect.GetTypeName(new SqlType(DbType.String));
}

protected override void OnSetUp()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
var e1 = new Entity {Name = "Bob"};
session.Save(e1);

var e2 = new Entity {Name = "Sally"};
session.Save(e2);

transaction.Commit();
}
}

protected override void OnTearDown()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
session.CreateQuery("delete from System.Object").ExecuteUpdate();

transaction.Commit();
}
}

[Test]
public async Task ParameterTypeForLikeIsProperlyDetectedAsync()
{
using (var logSpy = new SqlLogSpy())
using (var session = OpenSession())
{
var result = from e in session.Query<Entity>()
where NHibernate.Linq.SqlMethods.Like(e.Name, "Bob")
select e;

Assert.That(await (result.ToListAsync()), Has.Count.EqualTo(1));
Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString"));
}
}

[KnownBug("Not fixed yet")]
[Test]
public async Task ParameterTypeForContainsIsProperlyDetectedAsync()
{
using (var logSpy = new SqlLogSpy())
using (var session = OpenSession())
{
var result = from e in session.Query<Entity>()
where e.Name.Contains("Bob")
select e;

Assert.That(await (result.ToListAsync()), Has.Count.EqualTo(1));
Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString"));
}
}

[KnownBug("Not fixed yet")]
[Test]
public async Task ParameterTypeForStartsWithIsProperlyDetectedAsync()
{
using (var logSpy = new SqlLogSpy())
using (var session = OpenSession())
{
var result = from e in session.Query<Entity>()
where e.Name.StartsWith("Bob")
select e;

Assert.That(await (result.ToListAsync()), Has.Count.EqualTo(1));
Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString"));
}
}
}
}
10 changes: 10 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/NH3565/Entity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace NHibernate.Test.NHSpecificTest.NH3565
{
class Entity
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
}
109 changes: 109 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/NH3565/FixtureByCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using System.Data;
using System.Linq;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Mapping.ByCode;
using NHibernate.SqlTypes;
using NUnit.Framework;

namespace NHibernate.Test.NHSpecificTest.NH3565
{
[TestFixture]
public class ByCodeFixture : TestCaseMappingByCode
{
protected override HbmMapping GetMappings()
{
var mapper = new ModelMapper();
mapper.Class<Entity>(rc =>
{
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
rc.Property(x => x.Name, m =>
{
m.Type(NHibernateUtil.AnsiString);
m.Length(10);
});
});

return mapper.CompileMappingForAllExplicitlyAddedEntities();
}

protected override bool AppliesTo(Dialect.Dialect dialect)
{
return base.AppliesTo(dialect)
//Dialects like SQL Server CE, Firebird don't distinguish AnsiString from String
&& Dialect.GetTypeName(new SqlType(DbType.AnsiString)) != Dialect.GetTypeName(new SqlType(DbType.String));
}

protected override void OnSetUp()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
var e1 = new Entity {Name = "Bob"};
session.Save(e1);

var e2 = new Entity {Name = "Sally"};
session.Save(e2);

transaction.Commit();
}
}

protected override void OnTearDown()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
session.CreateQuery("delete from System.Object").ExecuteUpdate();

transaction.Commit();
}
}

[Test]
public void ParameterTypeForLikeIsProperlyDetected()
{
using (var logSpy = new SqlLogSpy())
using (var session = OpenSession())
{
var result = from e in session.Query<Entity>()
where NHibernate.Linq.SqlMethods.Like(e.Name, "Bob")
select e;

Assert.That(result.ToList(), Has.Count.EqualTo(1));
Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString"));
}
}

[KnownBug("Not fixed yet")]
[Test]
public void ParameterTypeForContainsIsProperlyDetected()
{
using (var logSpy = new SqlLogSpy())
using (var session = OpenSession())
{
var result = from e in session.Query<Entity>()
where e.Name.Contains("Bob")
select e;

Assert.That(result.ToList(), Has.Count.EqualTo(1));
Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString"));
}
}

[KnownBug("Not fixed yet")]
[Test]
public void ParameterTypeForStartsWithIsProperlyDetected()
{
using (var logSpy = new SqlLogSpy())
using (var session = OpenSession())
{
var result = from e in session.Query<Entity>()
where e.Name.StartsWith("Bob")
select e;

Assert.That(result.ToList(), Has.Count.EqualTo(1));
Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString"));
}
}
}
}
1 change: 1 addition & 0 deletions src/NHibernate/Async/IQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace NHibernate
{
using System.Threading.Tasks;
using System.Threading;

public partial interface IQuery
{

Expand Down
2 changes: 1 addition & 1 deletion src/NHibernate/Async/Impl/AbstractQueryImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

namespace NHibernate.Impl
{
public abstract partial class AbstractQueryImpl : IQuery
public abstract partial class AbstractQueryImpl : IQuery, IQueryNextVer
{

#region Execution methods
Expand Down
13 changes: 13 additions & 0 deletions src/NHibernate/IQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@

namespace NHibernate
{
// 6.0 TODO add to IQuery
internal interface IQueryNextVer : IQuery
{
/// <summary>
/// Bind a value to a named query parameter
/// </summary>
/// <param name="name">The name of the parameter</param>
/// <param name="val">The possibly null parameter value</param>
/// <param name="type">The NHibernate <see cref="IType"/>.</param>
/// <param name="preferMetadataType">If true supplied type is used only if parameter metadata is missing</param>
IQuery SetParameter(string name, object val, IType type, bool preferMetadataType);
}

/// <summary>
/// An object-oriented representation of a NHibernate query.
/// </summary>
Expand Down
60 changes: 27 additions & 33 deletions src/NHibernate/Impl/AbstractQueryImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace NHibernate.Impl
/// <summary>
/// Abstract implementation of the IQuery interface.
/// </summary>
public abstract partial class AbstractQueryImpl : IQuery
public abstract partial class AbstractQueryImpl : IQuery, IQueryNextVer
{
private readonly string queryString;
protected readonly ISessionImplementor session;
Expand Down Expand Up @@ -242,17 +242,33 @@ public IQuery SetParameter(int position, object val, IType type)

public IQuery SetParameter(string name, object val, IType type)
{
if (!parameterMetadata.NamedParameterNames.Contains(name))
{
if (shouldIgnoredUnknownNamedParameters)//just ignore it
return this;
throw new ArgumentException("Parameter " + name + " does not exist as a named parameter in [" + QueryString + "]");
}
else
{
namedParameters[name] = new TypedValue(type, val, false);
return SetParameter(name, val, type, false);
}

//TODO 6.0: Add to IQuery interface
public IQuery SetParameter(string name, object val, IType type, bool preferMetadataType)
{
if (CheckParameterIgnored(name))
return this;

if (type == null || preferMetadataType)
{
type = parameterMetadata.GetNamedParameterExpectedType(name) ?? type ?? ParameterHelper.GuessType(val, session.Factory);
}

namedParameters[name] = new TypedValue(type, val, false);
return this;
}

private bool CheckParameterIgnored(string name)
{
if (parameterMetadata.NamedParameterNames.Contains(name))
return false;

if (shouldIgnoredUnknownNamedParameters) //just ignore it
return true;

throw new ArgumentException("Parameter " + name + " does not exist as a named parameter in [" + QueryString + "]");
}

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

public IQuery SetParameter(string name, object val)
{
if (!parameterMetadata.NamedParameterNames.Contains(name))
{
if (shouldIgnoredUnknownNamedParameters)//just ignore it
return this;
}

if (val == null)
{
IType type = parameterMetadata.GetNamedParameterExpectedType(name);
if (type == null)
{
throw new ArgumentNullException("val",
"A type specific Set(name, val) should be called because the Type can not be guessed from a null value.");
}

SetParameter(name, val, type);
}
else
{
SetParameter(name, val, DetermineType(name, val));
}

return this;
return SetParameter(name, val, null, true);
}

public IQuery SetParameter(int position, object val)
Expand Down
Loading