From 26472ed94b38adc6354e8c4803d45a31065373bf Mon Sep 17 00:00:00 2001 From: maca88 Date: Mon, 20 Jul 2020 16:46:53 +0200 Subject: [PATCH 1/3] Fix CustomType cast for Linq provider --- .../Async/NHSpecificTest/GH2437/Fixture.cs | 144 ++++++++++++++ .../NHSpecificTest/GH2437/Fixture.cs | 131 +++++++++++++ .../NHSpecificTest/GH2437/SqlBoolean.cs | 181 +++++++++++++++++ .../NHSpecificTest/GH2437/SqlNumberDate.cs | 58 ++++++ .../GH2437/SqlNumberDateTime.cs | 182 ++++++++++++++++++ .../NHSpecificTest/GH2437/User.cs | 9 + .../NHSpecificTest/GH2437/UserMapping.cs | 21 ++ .../NHSpecificTest/GH2437/UserSession.cs | 21 ++ .../GH2437/UserSessionMapping.cs | 38 ++++ .../Visitors/HqlGeneratorExpressionVisitor.cs | 8 + 10 files changed, 793 insertions(+) create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/GH2437/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2437/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2437/SqlBoolean.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2437/SqlNumberDate.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2437/SqlNumberDateTime.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2437/User.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2437/UserMapping.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2437/UserSession.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2437/UserSessionMapping.cs diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2437/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2437/Fixture.cs new file mode 100644 index 00000000000..b2df28d8386 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2437/Fixture.cs @@ -0,0 +1,144 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.AddMapping(); + mapper.AddMapping(); + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + User user = new User() { UserCode = "gokhanabatay", IsOpen = true, UserName = "Gökhan Abatay" }; + session.Save(user); + + for (int i = 0; i < 10; i++) + { + UserSession userSession = new UserSession() + { + Claims = "My Claims", + ExpireDateTime = DateTime.Now.AddDays(1), + MbrId = 1, + OpenDate = DateTime.Now, + LocalIpAddress = "192.168.1.1", + RemoteIpAddress = "127.0.0.1", + LocalPort = 80 + i.ToString(), + RemotePort = 80 + i.ToString(), + DeviceId = "None", + UserCode = "gokhanabatay", + IsOpen = true + }; + + session.Save(userSession); + } + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from UserSession").ExecuteUpdate(); + session.CreateQuery("delete from User").ExecuteUpdate(); + + transaction.Commit(); + } + } + + [Test] + public async Task Get_DateCustomType_NullableDateValueEqualsAsync() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + Assert.That((await (session.Query() + .Where(x => x.OpenDate.Value == DateTime.Now) + .ToListAsync())).Count == 10); + } + } + + [Test] + public async Task Get_DateTimeCustomType_NullableDateValueEqualsAsync() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + Assert.That((await (session.Query() + .Where(x => x.ExpireDateTime.Value > DateTime.Now) + .ToListAsync())).Count == 10); + } + } + + [Test] + public async Task Get_DateCustomType_DateEqualsAsync() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + Assert.That((await (session.Query() + .Where(x => x.OpenDate == DateTime.Now) + .ToListAsync())).Count == 10); + } + } + + [Test] + public async Task Get_DateTimeCustomType_DateEqualsAsync() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + Assert.That((await (session.Query() + .Where(x => x.ExpireDateTime > DateTime.Now) + .ToListAsync())).Count == 10); + } + } + + [Test] + public async Task Get_BooleanCustomTypeAsync() + { + + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + Assert.That( + (await (session.Query() + .Where(x => x.OpenDate == DateTime.Now) + .Select(x => new NullableBooleanResult() {IsOpen = x.User.IsOpen}) + .ToListAsync())).Count == 10); + } + } + + public class NullableBooleanResult + { + public bool? IsOpen { get; set; } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2437/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2437/Fixture.cs new file mode 100644 index 00000000000..1481b43f0fc --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2437/Fixture.cs @@ -0,0 +1,131 @@ +using System; +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + [TestFixture] + public class Fixture : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.AddMapping(); + mapper.AddMapping(); + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + User user = new User() { UserCode = "gokhanabatay", IsOpen = true, UserName = "Gökhan Abatay" }; + session.Save(user); + + for (int i = 0; i < 10; i++) + { + UserSession userSession = new UserSession() + { + Claims = "My Claims", + ExpireDateTime = DateTime.Now.AddDays(1), + MbrId = 1, + OpenDate = DateTime.Now, + LocalIpAddress = "192.168.1.1", + RemoteIpAddress = "127.0.0.1", + LocalPort = 80 + i.ToString(), + RemotePort = 80 + i.ToString(), + DeviceId = "None", + UserCode = "gokhanabatay", + IsOpen = true + }; + + session.Save(userSession); + } + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from UserSession").ExecuteUpdate(); + session.CreateQuery("delete from User").ExecuteUpdate(); + + transaction.Commit(); + } + } + + [Test] + public void Get_DateCustomType_NullableDateValueEquals() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + Assert.That(session.Query() + .Where(x => x.OpenDate.Value == DateTime.Now) + .ToList().Count == 10); + } + } + + [Test] + public void Get_DateTimeCustomType_NullableDateValueEquals() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + Assert.That(session.Query() + .Where(x => x.ExpireDateTime.Value > DateTime.Now) + .ToList().Count == 10); + } + } + + [Test] + public void Get_DateCustomType_DateEquals() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + Assert.That(session.Query() + .Where(x => x.OpenDate == DateTime.Now) + .ToList().Count == 10); + } + } + + [Test] + public void Get_DateTimeCustomType_DateEquals() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + Assert.That(session.Query() + .Where(x => x.ExpireDateTime > DateTime.Now) + .ToList().Count == 10); + } + } + + [Test] + public void Get_BooleanCustomType() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + Assert.That( + session.Query() + .Where(x => x.OpenDate == DateTime.Now) + .Select(x => new NullableBooleanResult() {IsOpen = x.User.IsOpen}) + .ToList().Count == 10); + } + } + + public class NullableBooleanResult + { + public bool? IsOpen { get; set; } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2437/SqlBoolean.cs b/src/NHibernate.Test/NHSpecificTest/GH2437/SqlBoolean.cs new file mode 100644 index 00000000000..ca2eb52f3ff --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2437/SqlBoolean.cs @@ -0,0 +1,181 @@ +using NHibernate.Engine; +using NHibernate.SqlTypes; +using NHibernate.UserTypes; +using System; +using System.Data; +using System.Data.Common; + +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + [Serializable] + public class SqlBoolean : IEnhancedUserType + { + /// + /// Compare two instances of the class mapped by this type for persistent "equality" + /// ie. equality of persistent state + /// + /// + /// + public new bool Equals(object x, object y) + { + if (x == y) + { + return true; + } + if (x == null || y == null) + { + return false; + } + + return x.Equals(y); + } + + /// + /// Get a hashcode for the instance, consistent with persistence "equality" + /// + public int GetHashCode(object x) + { + if (x == null) + { + return 0; + } + + return x.GetHashCode(); + } + + /// + /// Retrieve an instance of the mapped class from a JDBC resultset. + /// Implementors should handle possibility of null values. + /// + /// a IDataReadercolumn namesthe containing entity + /// session + /// + /// HibernateException + public object NullSafeGet(DbDataReader rs, string[] names, ISessionImplementor session, object owner) + { + object result = NHibernateUtil.Int16.NullSafeGet(rs, names[0], session, owner); + + if (result == null) + { + return default(bool?); + } + else + { + return ((short) result) == 1; + } + } + + /// + /// Write an instance of the mapped class to a prepared statement. + /// Implementors should handle possibility of null values. + /// A multi-column type should be written to parameters starting from index. + /// + /// a IDbCommandthe object to write + /// command parameter index + /// session + /// HibernateException + public void NullSafeSet(DbCommand cmd, object value, int index, ISessionImplementor session) + { + short? result = value != null ? Convert.ToInt16(value) : default(short?); + + NHibernateUtil.Int16.NullSafeSet(cmd, result, index, session); + } + + /// + /// Return a deep copy of the persistent state, stopping at entities and at collections. + /// + /// generally a collection element or entity field + /// + /// a copy + /// + public object DeepCopy(object value) + { + return value; + } + + /// + /// During merge, replace the existing () value in the entity + /// we are merging to with a new () value from the detached + /// entity we are merging. For immutable objects, or null values, it is safe to simply + /// return the first parameter. For mutable objects, it is safe to return a copy of the + /// first parameter. For objects with component values, it might make sense to + /// recursively replace component values. + /// + /// the value from the detached entity being mergedthe value in the managed entitythe managed entity + /// + /// the value to be merged + /// + public object Replace(object original, object target, object owner) + { + return DeepCopy(original); + } + + /// + /// Reconstruct an object from the cacheable representation. At the very least this + /// method should perform a deep copy if the type is mutable. (optional operation) + /// + /// the object to be cachedthe owner of the cached object + /// + /// a reconstructed object from the cacheable representation + /// + public object Assemble(object cached, object owner) + { + return DeepCopy(cached); + } + + /// + /// Transform the object into its cacheable representation. At the very least this + /// method should perform a deep copy if the type is mutable. That may not be enough + /// for some implementations, however; for example, associations must be cached as + /// identifier values. (optional operation) + /// + /// the object to be cached + /// + /// a cacheable representation of the object + /// + public object Disassemble(object value) + { + return DeepCopy(value); + } + + public object FromXMLString(string xml) + { + if (bool.TryParse(xml, out var value)) + { + return value; + } + + return null; + } + + public string ObjectToSQLString(object value) + { + if (value == null) + { + return "is null"; + } + + return ((bool) value) ? "1" : "0"; + } + + public string ToXMLString(object value) + { + return value?.ToString(); + } + + /// + /// The SQL types for the columns mapped by this type. + /// + public SqlType[] SqlTypes => new[] { new SqlType(DbType.Int16) }; + + /// + /// The type returned by NullSafeGet() + /// + public System.Type ReturnedType => typeof(bool?); + + /// + /// Are objects of this type mutable? + /// + public bool IsMutable => false; + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2437/SqlNumberDate.cs b/src/NHibernate.Test/NHSpecificTest/GH2437/SqlNumberDate.cs new file mode 100644 index 00000000000..feb28b4755b --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2437/SqlNumberDate.cs @@ -0,0 +1,58 @@ +using System; +using System.Data; +using System.Data.Common; +using NHibernate.Engine; +using NHibernate.SqlTypes; + +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + /// + /// Short Number Date support with format yyyyMMdd + /// + [Serializable] + public class SqlNumberDate : SqlNumberDateTime + { + protected override string Format { get; } = "yyyyMMdd"; + public static readonly DateTime DefaultDate = new DateTime(1900, 1, 1); + + /// + /// Write an instance of the mapped class to a prepared statement. + /// Implementors should handle possibility of null values. + /// A multi-column type should be written to parameters starting from index. + /// + /// a IDbCommandthe object to writecommand parameter indexHibernateException + public override void NullSafeSet(DbCommand cmd, object value, int index, ISessionImplementor session) + { + Int32? result; + if (value != null) + { + var dateTime = (DateTime?) value; + if (dateTime < DefaultDateTime) + { + result = default(Int32?); + } + else + { + result = Int32.Parse(dateTime?.ToString(Format)); + } + } + else + { + result = default(Int32?); + } + + NHibernateUtil.Int32.NullSafeSet(cmd, result, index, session); + } + + /// + /// The SQL types for the columns mapped by this type. + /// + public override SqlType[] SqlTypes + { + get + { + return new[] { new SqlType(DbType.Int32) }; + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2437/SqlNumberDateTime.cs b/src/NHibernate.Test/NHSpecificTest/GH2437/SqlNumberDateTime.cs new file mode 100644 index 00000000000..62dec517e92 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2437/SqlNumberDateTime.cs @@ -0,0 +1,182 @@ +using System; +using System.Data; +using System.Data.Common; +using NHibernate.Engine; +using NHibernate.SqlTypes; +using NHibernate.UserTypes; + +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + /// + /// Long Number Date support with format yyyyMMddHHmmssfff + /// + [Serializable] + public class SqlNumberDateTime : IUserType + { + protected virtual string Format { get; } = "yyyyMMddHHmmssfff"; + protected static readonly DateTime DefaultDateTime = new DateTime(1900, 1, 1, 0, 0, 0, 0); + + /// + /// Compare two instances of the class mapped by this type for persistent "equality" + /// ie. equality of persistent state + /// + /// + /// + public new bool Equals(object x, object y) + { + if (x == y) return true; + if (x == null && y != null && ((DateTime?) y) < DefaultDateTime) + { + return true; + } + if (x == null || y == null) return false; + DateTime? l = (DateTime?) x; + DateTime? r = (DateTime?) y; + + return l.Equals(r); + } + + /// + /// Get a hashcode for the instance, consistent with persistence "equality" + /// + public int GetHashCode(object x) + { + DateTime? value = (DateTime?) x; + + return value.GetHashCode(); + } + + /// + /// Retrieve an instance of the mapped class from a JDBC resultset. + /// Implementors should handle possibility of null values. + /// + /// a IDataReadercolumn namesthe containing entity + /// + /// HibernateException + public object NullSafeGet(DbDataReader rs, string[] names, ISessionImplementor session, object owner) + { + object result = NHibernateUtil.String.NullSafeGet(rs, names[0], session, owner); + + if (result == null) + return default(DateTime?); + else + { + DateTime.TryParseExact(result.ToString(), Format, null, System.Globalization.DateTimeStyles.None, out var date); + return date; + } + } + + /// + /// Write an instance of the mapped class to a prepared statement. + /// Implementors should handle possibility of null values. + /// A multi-column type should be written to parameters starting from index. + /// + /// a IDbCommandthe object to writecommand parameter indexHibernateException + public virtual void NullSafeSet(DbCommand cmd, object value, int index, ISessionImplementor session) + { + Int64? result; + if (value != null) + { + var dateTime = (DateTime?) value; + if (dateTime < DefaultDateTime) + { + result = default(Int64?); + } + else + { + result = Int64.Parse(dateTime?.ToString(Format)); + } + } + else + { + result = default(Int64?); + } + + NHibernateUtil.Int64.NullSafeSet(cmd, result, index, session); + } + + /// + /// Return a deep copy of the persistent state, stopping at entities and at collections. + /// + /// generally a collection element or entity field + /// + /// a copy + /// + public object DeepCopy(object value) + { + return value; + } + + /// + /// During merge, replace the existing () value in the entity + /// we are merging to with a new () value from the detached + /// entity we are merging. For immutable objects, or null values, it is safe to simply + /// return the first parameter. For mutable objects, it is safe to return a copy of the + /// first parameter. For objects with component values, it might make sense to + /// recursively replace component values. + /// + /// the value from the detached entity being mergedthe value in the managed entitythe managed entity + /// + /// the value to be merged + /// + public object Replace(object original, object target, object owner) + { + return DeepCopy(original); + } + + /// + /// Reconstruct an object from the cacheable representation. At the very least this + /// method should perform a deep copy if the type is mutable. (optional operation) + /// + /// the object to be cachedthe owner of the cached object + /// + /// a reconstructed object from the cachable representation + /// + public object Assemble(object cached, object owner) + { + return DeepCopy(cached); + } + + /// + /// Transform the object into its cacheable representation. At the very least this + /// method should perform a deep copy if the type is mutable. That may not be enough + /// for some implementations, however; for example, associations must be cached as + /// identifier values. (optional operation) + /// + /// the object to be cached + /// + /// a cacheable representation of the object + /// + public object Disassemble(object value) + { + return DeepCopy(value); + } + + /// + /// The SQL types for the columns mapped by this type. + /// + public virtual SqlType[] SqlTypes + { + get + { + return new[] { new SqlType(DbType.Int64) }; + } + } + + /// + /// The type returned by NullSafeGet() + /// + public System.Type ReturnedType + { + get { return typeof(DateTime?); } + } + + /// + /// Are objects of this type mutable? + /// + public bool IsMutable + { + get { return false; } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2437/User.cs b/src/NHibernate.Test/NHSpecificTest/GH2437/User.cs new file mode 100644 index 00000000000..00c01c07e2a --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2437/User.cs @@ -0,0 +1,9 @@ +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + public class User + { + public virtual string UserCode { get; set; } + public virtual bool IsOpen { get; set; } + public virtual string UserName { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2437/UserMapping.cs b/src/NHibernate.Test/NHSpecificTest/GH2437/UserMapping.cs new file mode 100644 index 00000000000..eefd2d6d602 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2437/UserMapping.cs @@ -0,0 +1,21 @@ +using NHibernate.Mapping.ByCode; +using NHibernate.Mapping.ByCode.Conformist; + +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + public class UserMapping : ClassMapping + { + public UserMapping() + { + Table("Users"); + Id(o => o.UserCode, m => m.Generator(Generators.Assigned)); + Property( + o => o.IsOpen, + m => + { + m.Type(); + m.Precision(8); + }); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2437/UserSession.cs b/src/NHibernate.Test/NHSpecificTest/GH2437/UserSession.cs new file mode 100644 index 00000000000..ac31f5b5e6f --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2437/UserSession.cs @@ -0,0 +1,21 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + public class UserSession + { + public virtual long Guid { get; set; } + public virtual short MbrId { get; set; } + public virtual string UserCode { get; set; } + public virtual DateTime? OpenDate { get; set; } + public virtual DateTime? ExpireDateTime { get; set; } + public virtual bool? IsOpen { get; set; } + public virtual string RemoteIpAddress { get; set; } + public virtual string RemotePort { get; set; } + public virtual string LocalIpAddress { get; set; } + public virtual string LocalPort { get; set; } + public virtual string DeviceId { get; set; } + public virtual string Claims { get; set; } + public virtual User User { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2437/UserSessionMapping.cs b/src/NHibernate.Test/NHSpecificTest/GH2437/UserSessionMapping.cs new file mode 100644 index 00000000000..1c164fe0c3c --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2437/UserSessionMapping.cs @@ -0,0 +1,38 @@ +using NHibernate.Mapping.ByCode; +using NHibernate.Mapping.ByCode.Conformist; + +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + public class UserSessionMapping : ClassMapping + { + public UserSessionMapping() + { + Table("UserSessions"); + Id(o => o.Guid, m => m.Generator(Generators.Native)); + Property(o => o.UserCode, m => m.NotNullable(true)); + Property(o => o.OpenDate, m => + { + m.Type(); + m.Precision(8); + }); + Property(o => o.ExpireDateTime, m => + { + m.Type(); + m.Precision(17); + }); + Property(o => o.IsOpen, m => + { + m.Type(); + m.Precision(8); + }); + ManyToOne( + o => o.User, + m => + { + m.Column("UserCode"); + m.Insert(false); + m.Update(false); + }); + } + } +} diff --git a/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionVisitor.cs b/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionVisitor.cs index a16968cccb2..86d17c141b9 100644 --- a/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionVisitor.cs +++ b/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionVisitor.cs @@ -639,6 +639,14 @@ private bool IsCastRequired(IType type, IType toType, out bool existType) return false; // Never cast an enum that is mapped as string, the type will provide a string for the parameter value } + // Custom types may use a different sql type for mapping a .NET type that NHibernate would use (e.g. DateTime stored as integer). + if (type is CustomType) + { + // TODO: Extend IType by adding a method that can control whether a cast is required + existType = false; + return false; + } + // Some dialects can map several sql types into one, cast only if the dialect types are different if (!_parameters.SessionFactory.Dialect.TryGetCastTypeName(sqlTypes[0], out var castTypeName) || !_parameters.SessionFactory.Dialect.TryGetCastTypeName(toSqlTypes[0], out var toCastTypeName)) From d55a2620419e433b43ae785225aef994778bf007 Mon Sep 17 00:00:00 2001 From: Alex Zaytsev Date: Tue, 21 Jul 2020 11:43:20 +1200 Subject: [PATCH 2/3] Improve readability --- .../Async/NHSpecificTest/GH2437/Fixture.cs | 52 +++++++++---------- .../NHSpecificTest/GH2437/Fixture.cs | 51 +++++++++--------- 2 files changed, 52 insertions(+), 51 deletions(-) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2437/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2437/Fixture.cs index b2df28d8386..2dea6eb573e 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH2437/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2437/Fixture.cs @@ -34,12 +34,12 @@ protected override void OnSetUp() using (var session = OpenSession()) using (var transaction = session.BeginTransaction()) { - User user = new User() { UserCode = "gokhanabatay", IsOpen = true, UserName = "Gökhan Abatay" }; + var user = new User { UserCode = "gokhanabatay", IsOpen = true, UserName = "Gökhan Abatay" }; session.Save(user); - for (int i = 0; i < 10; i++) + for (var i = 0; i < 10; i++) { - UserSession userSession = new UserSession() + var userSession = new UserSession { Claims = "My Claims", ExpireDateTime = DateTime.Now.AddDays(1), @@ -77,11 +77,11 @@ protected override void OnTearDown() public async Task Get_DateCustomType_NullableDateValueEqualsAsync() { using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) + using (session.BeginTransaction()) { - Assert.That((await (session.Query() - .Where(x => x.OpenDate.Value == DateTime.Now) - .ToListAsync())).Count == 10); + var sessions = await (session.Query().Where(x => x.OpenDate.Value == DateTime.Now).ToListAsync()); + + Assert.That(sessions, Has.Count.EqualTo(10)); } } @@ -89,11 +89,11 @@ public async Task Get_DateCustomType_NullableDateValueEqualsAsync() public async Task Get_DateTimeCustomType_NullableDateValueEqualsAsync() { using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) + using (session.BeginTransaction()) { - Assert.That((await (session.Query() - .Where(x => x.ExpireDateTime.Value > DateTime.Now) - .ToListAsync())).Count == 10); + var sessions = await (session.Query().Where(x => x.ExpireDateTime.Value > DateTime.Now).ToListAsync()); + + Assert.That(sessions, Has.Count.EqualTo(10)); } } @@ -101,11 +101,11 @@ public async Task Get_DateTimeCustomType_NullableDateValueEqualsAsync() public async Task Get_DateCustomType_DateEqualsAsync() { using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) + using (session.BeginTransaction()) { - Assert.That((await (session.Query() - .Where(x => x.OpenDate == DateTime.Now) - .ToListAsync())).Count == 10); + var sessions = await (session.Query().Where(x => x.OpenDate == DateTime.Now).ToListAsync()); + + Assert.That(sessions, Has.Count.EqualTo(10)); } } @@ -113,26 +113,26 @@ public async Task Get_DateCustomType_DateEqualsAsync() public async Task Get_DateTimeCustomType_DateEqualsAsync() { using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) + using (session.BeginTransaction()) { - Assert.That((await (session.Query() - .Where(x => x.ExpireDateTime > DateTime.Now) - .ToListAsync())).Count == 10); + var sessions = await (session.Query().Where(x => x.ExpireDateTime > DateTime.Now).ToListAsync()); + + Assert.That(sessions, Has.Count.EqualTo(10)); } } [Test] public async Task Get_BooleanCustomTypeAsync() { - using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) + using (session.BeginTransaction()) { - Assert.That( - (await (session.Query() - .Where(x => x.OpenDate == DateTime.Now) - .Select(x => new NullableBooleanResult() {IsOpen = x.User.IsOpen}) - .ToListAsync())).Count == 10); + var results = await (session.Query() + .Where(x => x.OpenDate == DateTime.Now) + .Select(x => new NullableBooleanResult() {IsOpen = x.User.IsOpen}) + .ToListAsync()); + + Assert.That(results, Has.Count.EqualTo(10)); } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH2437/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2437/Fixture.cs index 1481b43f0fc..05cc50f83f1 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2437/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2437/Fixture.cs @@ -22,12 +22,12 @@ protected override void OnSetUp() using (var session = OpenSession()) using (var transaction = session.BeginTransaction()) { - User user = new User() { UserCode = "gokhanabatay", IsOpen = true, UserName = "Gökhan Abatay" }; + var user = new User { UserCode = "gokhanabatay", IsOpen = true, UserName = "Gökhan Abatay" }; session.Save(user); - for (int i = 0; i < 10; i++) + for (var i = 0; i < 10; i++) { - UserSession userSession = new UserSession() + var userSession = new UserSession { Claims = "My Claims", ExpireDateTime = DateTime.Now.AddDays(1), @@ -65,11 +65,11 @@ protected override void OnTearDown() public void Get_DateCustomType_NullableDateValueEquals() { using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) + using (session.BeginTransaction()) { - Assert.That(session.Query() - .Where(x => x.OpenDate.Value == DateTime.Now) - .ToList().Count == 10); + var sessions = session.Query().Where(x => x.OpenDate.Value == DateTime.Now).ToList(); + + Assert.That(sessions, Has.Count.EqualTo(10)); } } @@ -77,11 +77,11 @@ public void Get_DateCustomType_NullableDateValueEquals() public void Get_DateTimeCustomType_NullableDateValueEquals() { using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) + using (session.BeginTransaction()) { - Assert.That(session.Query() - .Where(x => x.ExpireDateTime.Value > DateTime.Now) - .ToList().Count == 10); + var sessions = session.Query().Where(x => x.ExpireDateTime.Value > DateTime.Now).ToList(); + + Assert.That(sessions, Has.Count.EqualTo(10)); } } @@ -89,11 +89,11 @@ public void Get_DateTimeCustomType_NullableDateValueEquals() public void Get_DateCustomType_DateEquals() { using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) + using (session.BeginTransaction()) { - Assert.That(session.Query() - .Where(x => x.OpenDate == DateTime.Now) - .ToList().Count == 10); + var sessions = session.Query().Where(x => x.OpenDate == DateTime.Now).ToList(); + + Assert.That(sessions, Has.Count.EqualTo(10)); } } @@ -101,11 +101,11 @@ public void Get_DateCustomType_DateEquals() public void Get_DateTimeCustomType_DateEquals() { using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) + using (session.BeginTransaction()) { - Assert.That(session.Query() - .Where(x => x.ExpireDateTime > DateTime.Now) - .ToList().Count == 10); + var sessions = session.Query().Where(x => x.ExpireDateTime > DateTime.Now).ToList(); + + Assert.That(sessions, Has.Count.EqualTo(10)); } } @@ -113,13 +113,14 @@ public void Get_DateTimeCustomType_DateEquals() public void Get_BooleanCustomType() { using (var session = OpenSession()) - using (var transaction = session.BeginTransaction()) + using (session.BeginTransaction()) { - Assert.That( - session.Query() - .Where(x => x.OpenDate == DateTime.Now) - .Select(x => new NullableBooleanResult() {IsOpen = x.User.IsOpen}) - .ToList().Count == 10); + var results = session.Query() + .Where(x => x.OpenDate == DateTime.Now) + .Select(x => new NullableBooleanResult() {IsOpen = x.User.IsOpen}) + .ToList(); + + Assert.That(results, Has.Count.EqualTo(10)); } } From c8b9c23711f1e44ad17f899b22f686fb8578db28 Mon Sep 17 00:00:00 2001 From: Alex Zaytsev Date: Tue, 21 Jul 2020 11:44:28 +1200 Subject: [PATCH 3/3] Better fix --- .../Linq/Visitors/HqlGeneratorExpressionVisitor.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionVisitor.cs b/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionVisitor.cs index 86d17c141b9..34315c240d2 100644 --- a/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionVisitor.cs +++ b/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionVisitor.cs @@ -607,7 +607,8 @@ private bool IsCastRequired(Expression expression, System.Type toType, out bool { existType = false; return toType != typeof(object) && - IsCastRequired(ExpressionsHelper.GetType(_parameters, expression), TypeFactory.GetDefaultTypeFor(toType), out existType); + expression.Type.UnwrapIfNullable() != toType.UnwrapIfNullable() && + IsCastRequired(ExpressionsHelper.GetType(_parameters, expression), TypeFactory.GetDefaultTypeFor(toType), out existType); } private bool IsCastRequired(IType type, IType toType, out bool existType) @@ -639,14 +640,6 @@ private bool IsCastRequired(IType type, IType toType, out bool existType) return false; // Never cast an enum that is mapped as string, the type will provide a string for the parameter value } - // Custom types may use a different sql type for mapping a .NET type that NHibernate would use (e.g. DateTime stored as integer). - if (type is CustomType) - { - // TODO: Extend IType by adding a method that can control whether a cast is required - existType = false; - return false; - } - // Some dialects can map several sql types into one, cast only if the dialect types are different if (!_parameters.SessionFactory.Dialect.TryGetCastTypeName(sqlTypes[0], out var castTypeName) || !_parameters.SessionFactory.Dialect.TryGetCastTypeName(toSqlTypes[0], out var toCastTypeName))