diff --git a/doc/reference/modules/query_linq.xml b/doc/reference/modules/query_linq.xml
index 32e0e041aa1..fceba0e0f39 100644
--- a/doc/reference/modules/query_linq.xml
+++ b/doc/reference/modules/query_linq.xml
@@ -8,13 +8,13 @@
The Linq provider works as an extension of the ISession. It is defined in the
NHibernate.Linq namespace, so this namespace has to be imported for using the
- Linq provider. Of course, the LINQ namespace is still needed too.
+ Linq provider. Of course, the Linq namespace is still needed too.
Note: NHibernate has another querying API which uses lambda, QueryOver.
- It should not be confused with a LINQ provider.
+ It should not be confused with a Linq provider.
@@ -545,9 +545,10 @@ IList cats =
}
}]]>
- It is required that at least one of the parameters of the method call has its value originating
- from an entity. Otherwise, the Linq provider will try to evaluate the method call with .Net
- runtime.
+ The method call will always be translated to SQL if at least one of the parameters of the
+ method call has its value originating from an entity. Otherwise, the Linq provider will try to
+ evaluate the method call with .Net runtime instead. Since NHibernate 5.0, if this runtime
+ evaluation fails (throws an exception), then the method call will be translated to SQL too.
@@ -565,7 +566,7 @@ IList cats =
As an example, here is how to add support for an AsNullable method which
- would allow to call aggregates which lay yield null without to explicitly
+ would allow to call aggregates which may yield null without to explicitly
cast to the nullable type of the aggregate.
cats =
}
}]]>
- Adding support in Linq to NHibernate for this custom method requires a generator. For this
+ Adding support in Linq to NHibernate for a custom method requires a generator. For this
AsNullable method, we need a method generator, declaring statically its
supported method.
@@ -612,8 +613,8 @@ IList cats =
For adding AsNullableGenerator in Linq to NHibernate provider, a new
- generators registry should be used. Derive from the default one and merge it. (Static
- declaration of method support case.)
+ generators registry should be used. Derive from the default one and merge it. (Here we
+ have a static declaration of method support case.)
()
+ .OrderBy(e => SqlServerFunction.NewID());
+
+ Assert.DoesNotThrow(() => { result.ToList(); });
+ Assert.That(sqlInterceptor.Sql.ToString(), Does.Contain(nameof(SqlServerFunction.NewID)).IgnoreCase);
+ }
+ }
+ }
+
+ public static class SqlServerFunction
+ {
+ [LinqExtensionMethod]
+ public static Guid NewID()
+ {
+ throw new InvalidOperationException("To be translated to SQL only");
+ }
+ }
+
+ public class SqlInterceptor: EmptyInterceptor
+ {
+ public SqlString Sql { get; private set; }
+
+ public override SqlString OnPrepareStatement(SqlString sql)
+ {
+ Sql = sql;
+ return base.OnPrepareStatement(sql);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NHibernate.Test/NHSpecificTest/NH3386/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/NH3386/Mappings.hbm.xml
new file mode 100644
index 00000000000..0a13a0df6ff
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/NH3386/Mappings.hbm.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/NHibernate.Test/NHSpecificTest/NH3961/DateParametersComparedTo.cs b/src/NHibernate.Test/NHSpecificTest/NH3961/DateParametersComparedTo.cs
new file mode 100644
index 00000000000..678e90c8502
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/NH3961/DateParametersComparedTo.cs
@@ -0,0 +1,282 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using NHibernate.Linq;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.NH3961
+{
+ [TestFixture]
+ public class DateParametersComparedTo : BugTestCase
+ {
+ private DateTime _testDate;
+ private CultureInfo _backupCulture;
+ private CultureInfo _backupUICulture;
+ private readonly CultureInfo _testCulture = CultureInfo.GetCultureInfo("fr-FR");
+
+ protected override void OnSetUp()
+ {
+ using (ISession session = OpenSession())
+ using (ITransaction transaction = session.BeginTransaction())
+ {
+ // day > 12 for ensuring a mdy/dmy mix-up would cause a failure.
+ _testDate = new DateTime(2017, 03, 15);
+ var e1 = new Entity { Name = "Bob", NullableDateTime = _testDate, NonNullableDateTime = _testDate };
+ session.Save(e1);
+
+ var e2 = new Entity { Name = "Sally", NullableDateTime = _testDate.AddDays(1), NonNullableDateTime = _testDate.AddDays(1) };
+ session.Save(e2);
+
+ session.Flush();
+ transaction.Commit();
+ }
+
+ _backupCulture = CultureInfo.CurrentCulture;
+ _backupUICulture = CultureInfo.CurrentUICulture;
+ // "CultureInfo.CurrentCulture =": Fx 4.6 only; affect trough Thread.CurrentThread instead if in need of supporting a previous Fx.
+ // This test needs a culture using a dmy date format. If the test system does not support fr-FR, try find another one...
+ // This test assumes the SQL user language is set as English, otherwise it may not showcase the failure.
+ CultureInfo.CurrentCulture = _testCulture;
+ CultureInfo.CurrentUICulture = _testCulture;
+ }
+
+ protected override void OnTearDown()
+ {
+ using (ISession session = OpenSession())
+ using (ITransaction transaction = session.BeginTransaction())
+ {
+ session.Delete("from System.Object");
+
+ session.Flush();
+ transaction.Commit();
+ }
+ CultureInfo.CurrentCulture = _backupCulture;
+ CultureInfo.CurrentUICulture = _backupUICulture;
+ }
+
+ // Non-reg test case
+ [Test]
+ public void NonNullableMappedAsDateShouldBeCultureAgnostic()
+ {
+ using (ISession session = OpenSession())
+ using (session.BeginTransaction())
+ {
+ var result = session.Query()
+ .Where(e => e.NonNullableDateTime == _testDate.MappedAs(NHibernateUtil.Date))
+ .ToList();
+
+ Assert.AreEqual(1, result.Count);
+ Assert.AreEqual("Bob", result[0].Name);
+ }
+ }
+
+ // Non-reg test case
+ [Test]
+ public void NonNullableMappedAsDateShouldIgnoreTime()
+ {
+ using (ISession session = OpenSession())
+ {
+ var result = session.Query()
+ .Where(e => e.NonNullableDateTime == _testDate.AddMinutes(10).MappedAs(NHibernateUtil.Date))
+ .ToList();
+
+ Assert.AreEqual(1, result.Count);
+ Assert.AreEqual("Bob", result[0].Name);
+ }
+ }
+
+ // Non-reg test case
+ [Test]
+ public void NonNullableMappedAsDateTimeShouldBeCultureAgnostic()
+ {
+ using (ISession session = OpenSession())
+ using (session.BeginTransaction())
+ {
+ var result = session.Query()
+ .Where(e => e.NonNullableDateTime == _testDate.MappedAs(NHibernateUtil.DateTime))
+ .ToList();
+
+ Assert.AreEqual(1, result.Count);
+ Assert.AreEqual("Bob", result[0].Name);
+ }
+ }
+
+ // Non-reg test case
+ [Test]
+ public void NonNullableMappedAsTimestampShouldBeCultureAgnostic()
+ {
+ using (ISession session = OpenSession())
+ using (session.BeginTransaction())
+ {
+ var result = session.Query()
+ .Where(e => e.NonNullableDateTime == _testDate.MappedAs(NHibernateUtil.Timestamp))
+ .ToList();
+
+ Assert.AreEqual(1, result.Count);
+ Assert.AreEqual("Bob", result[0].Name);
+ }
+ }
+
+ // Non-reg test case
+ [Test]
+ public void NonNullableParameterValueShouldNotBeCachedWithMappedAsAnd()
+ {
+ // Dodges the query parameter formatting bug for showcasing the parameter value bug
+ CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
+ CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture;
+ try
+ {
+ using (ISession session = OpenSession())
+ {
+ var result = session.Query()
+ .Where(e => e.NonNullableDateTime == _testDate.MappedAs(NHibernateUtil.DateTime))
+ .ToList();
+
+ Assert.AreEqual(1, result.Count);
+ Assert.AreEqual("Bob", result[0].Name);
+
+ var testDate = _testDate.AddMinutes(10);
+ result = session.Query()
+ .Where(e => e.NonNullableDateTime == testDate.MappedAs(NHibernateUtil.DateTime))
+ .ToList();
+
+ CollectionAssert.IsEmpty(result);
+ }
+ }
+ finally
+ {
+ CultureInfo.CurrentCulture = _testCulture;
+ CultureInfo.CurrentUICulture = _testCulture;
+ }
+ }
+
+ // Non-reg test case
+ [Test]
+ public void NonNullableShouldBeCultureAgnostic()
+ {
+ using (ISession session = OpenSession())
+ using (session.BeginTransaction())
+ {
+ var result = session.Query()
+ .Where(e => e.NonNullableDateTime == _testDate)
+ .ToList();
+
+ Assert.AreEqual(1, result.Count);
+ Assert.AreEqual("Bob", result[0].Name);
+ }
+ }
+
+ // Failing test case till NH-3961 is fixed
+ [Test]
+ public void NullableMappedAsDateShouldBeCultureAgnostic()
+ {
+ using (ISession session = OpenSession())
+ using (session.BeginTransaction())
+ {
+ var result = session.Query()
+ .Where(e => e.NullableDateTime == _testDate.MappedAs(NHibernateUtil.Date))
+ .ToList();
+
+ Assert.AreEqual(1, result.Count);
+ Assert.AreEqual("Bob", result[0].Name);
+ }
+ }
+
+ // Failing test case till NH-3961 is fixed
+ [Test]
+ public void NullableMappedAsDateShouldIgnoreTime()
+ {
+ var testDate = _testDate.AddMinutes(10);
+ using (ISession session = OpenSession())
+ {
+ var result = session.Query()
+ .Where(e => e.NullableDateTime == testDate.MappedAs(NHibernateUtil.Date))
+ .ToList();
+
+ Assert.AreEqual(1, result.Count);
+ Assert.AreEqual("Bob", result[0].Name);
+ }
+ }
+
+ // Failing test case till NH-3961 is fixed
+ [Test]
+ public void NullableMappedAsDateTimeShouldBeCultureAgnostic()
+ {
+ using (ISession session = OpenSession())
+ using (session.BeginTransaction())
+ {
+ var result = session.Query()
+ .Where(e => e.NullableDateTime == _testDate.MappedAs(NHibernateUtil.DateTime))
+ .ToList();
+
+ Assert.AreEqual(1, result.Count);
+ Assert.AreEqual("Bob", result[0].Name);
+ }
+ }
+
+ // Failing test case till NH-3961 is fixed
+ [Test]
+ public void NullableMappedAsTimestampShouldBeCultureAgnostic()
+ {
+ using (ISession session = OpenSession())
+ using (session.BeginTransaction())
+ {
+ var result = session.Query()
+ .Where(e => e.NullableDateTime == _testDate.MappedAs(NHibernateUtil.Timestamp))
+ .ToList();
+
+ Assert.AreEqual(1, result.Count);
+ Assert.AreEqual("Bob", result[0].Name);
+ }
+ }
+
+ // Failing test case till NH-3961 is fixed
+ [Test]
+ public void NullableParameterValueShouldNotBeCachedWithMappedAs()
+ {
+ // Dodges the query parameter formatting bug for showcasing the parameter value bug
+ CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
+ CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture;
+ try
+ {
+ using (ISession session = OpenSession())
+ {
+ var result = session.Query()
+ .Where(e => e.NullableDateTime == _testDate.MappedAs(NHibernateUtil.DateTime))
+ .ToList();
+
+ Assert.AreEqual(1, result.Count);
+ Assert.AreEqual("Bob", result[0].Name);
+
+ var testDate = _testDate.AddMinutes(10);
+ result = session.Query()
+ .Where(e => e.NullableDateTime == testDate.MappedAs(NHibernateUtil.DateTime))
+ .ToList();
+
+ CollectionAssert.IsEmpty(result);
+ }
+ }
+ finally
+ {
+ CultureInfo.CurrentCulture = _testCulture;
+ CultureInfo.CurrentUICulture = _testCulture;
+ }
+ }
+
+ // Non-reg test case
+ [Test]
+ public void NullableShouldBeCultureAgnostic()
+ {
+ using (ISession session = OpenSession())
+ using (session.BeginTransaction())
+ {
+ var result = session.Query()
+ .Where(e => e.NullableDateTime == _testDate)
+ .ToList();
+
+ Assert.AreEqual(1, result.Count);
+ Assert.AreEqual("Bob", result[0].Name);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NHibernate.Test/NHSpecificTest/NH3961/Entity.cs b/src/NHibernate.Test/NHSpecificTest/NH3961/Entity.cs
new file mode 100644
index 00000000000..c5fbfca1b15
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/NH3961/Entity.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace NHibernate.Test.NHSpecificTest.NH3961
+{
+ public class Entity
+ {
+ public virtual Guid Id { get; set; }
+ public virtual string Name { get; set; }
+ public virtual DateTime NonNullableDateTime { get; set; }
+ public virtual DateTime? NullableDateTime { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/NHibernate.Test/NHSpecificTest/NH3961/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/NH3961/Mappings.hbm.xml
new file mode 100644
index 00000000000..3795a14547c
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/NH3961/Mappings.hbm.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj
index 8fae8736b23..e6b4cd82bf4 100644
--- a/src/NHibernate.Test/NHibernate.Test.csproj
+++ b/src/NHibernate.Test/NHibernate.Test.csproj
@@ -737,6 +737,10 @@
+
+
+
+
@@ -3208,6 +3212,8 @@
+
+
diff --git a/src/NHibernate/Linq/NhRelinqQueryParser.cs b/src/NHibernate/Linq/NhRelinqQueryParser.cs
index 86c1410099e..4e3b04f04e1 100644
--- a/src/NHibernate/Linq/NhRelinqQueryParser.cs
+++ b/src/NHibernate/Linq/NhRelinqQueryParser.cs
@@ -12,6 +12,7 @@
using Remotion.Linq.Parsing.Structure;
using Remotion.Linq.Parsing.Structure.IntermediateModel;
using Remotion.Linq.Parsing.Structure.NodeTypeProviders;
+using Remotion.Linq.Parsing.Structure.ExpressionTreeProcessors;
namespace NHibernate.Linq
{
@@ -26,9 +27,11 @@ static NhRelinqQueryParser()
transformerRegistry.Register(new RemoveRedundantCast());
transformerRegistry.Register(new SimplifyCompareTransformer());
- var processor = ExpressionTreeParser.CreateDefaultProcessor(transformerRegistry);
- // Add custom processors here:
- // processor.InnerProcessors.Add (new MyExpressionTreeProcessor());
+ // If needing a compound processor for adding other processing, do not use
+ // ExpressionTreeParser.CreateDefaultProcessor(transformerRegistry), it would
+ // cause NH-3961 again by including a PartialEvaluatingExpressionTreeProcessor.
+ // Directly instanciate a CompoundExpressionTreeProcessor instead.
+ var processor = new TransformingExpressionTreeProcessor(transformerRegistry);
var nodeTypeProvider = new NHibernateNodeTypeProvider();