Description
We've implemented a custom method for doing an InsensitiveLike comparison on LINQ queries like this:
public override HqlTreeNode BuildHql(MethodInfo method,
Expression targetObject,
ReadOnlyCollection<Expression> arguments,
HqlTreeBuilder treeBuilder,
IHqlExpressionVisitor visitor)
{
StringMatchMode matchMode = (StringMatchMode)((ConstantExpression)arguments[2]).Value;
var toLowerGenerator = new ToLowerGenerator();
// We use a concat expression instead of doing in memory string concatenation
// so the pattern can also be an Nhibernate LINQ expression.
HqlExpression firstArgument = toLowerGenerator.BuildHql(method, arguments[0], null, treeBuilder, visitor).AsExpression();
HqlExpression likePatternLower = toLowerGenerator.BuildHql(method, arguments[1], null, treeBuilder, visitor).AsExpression();
switch (matchMode)
{
case StringMatchMode.Anywhere:
likePatternLower = treeBuilder.Concat(treeBuilder.Constant("%"), likePatternLower, treeBuilder.Constant("%"));
break;
case StringMatchMode.Start:
likePatternLower = treeBuilder.Concat(likePatternLower, treeBuilder.Constant("%"));
break;
case StringMatchMode.End:
likePatternLower = treeBuilder.Concat(treeBuilder.Constant("%"), likePatternLower);
break;
case StringMatchMode.Exact:
default:
break;
}
return treeBuilder.Like(firstArgument, likePatternLower);
}
which we use in tests as follows:
[Test]
[TestCase("Description Starts with")]
[TestCase("DEScRIpTION START")]
[TestCase("D")]
[TestCase("")]
public void InsensitiveLike_UsingMatchModeStart_Matches(string testPattern)
{
// Arrange
var pattern = testPattern;
var matchMode = StringMatchMode.Start;
var comparisionType = StringComparison.InvariantCultureIgnoreCase;
var descriptionNote = new Note
{
Description = "Description starts with 123456"
};
var session = InitializeSession();
session.Save(descriptionNote);
session.Flush();
// Act
var query = session.Query<Note>().Where(x => x.Description.InsensitiveLike(pattern, matchMode));
Console.WriteLine($"*****InsensitiveLike_UsingMatchModeStart_Matches '{testPattern}': {GetSqlStringFromQuery(query, session)}*******");
var results = query.ToList();
// Assert
Assert.AreEqual(1, results.Count, "Note counts matches expected.");
Assert.AreEqual(results.First(), descriptionNote, "Query returned corresponding note instance.");
Assert.IsTrue(results.First().Description.StartsWith(pattern, comparisionType), "Note description starts with pattern.");
//Assert.IsTrue(sqlString.Contains("lower("), "Query uses lowercase method.");
EndSession(session);
}
The problem is that if we run all 4 cases of the test, one query uses the result from the last one.
I read thro the issues and found 3 that seem similar to what I'm experiencing: #1330 (NH-3673), #1363 (NH-2500) and #866 (NH-2658). I can also see that #1544 seems to fix most of these issues and that it was merged to 5.1. However we're running the latest 4.X release (4.1.2.400 according to Nuget) and we're unable to update due to other dependencies at the moment.
Is there some user code workaround I could implement in order to prevent this issue? I solved it during tests by creating a new SessionFactory on each test contrary to creating only one for all tests and just starting a new Session, but that is not possible on production code for obvious reasons.
Additionally, the GetSqlStringFromQuery
method (which I copied as a test utils) shows the right SQL:
protected string GetSqlStringFromQuery<T>(IQueryable<T> queryable, ISession session)
{
var translators = BuildQueryTranslators(queryable, session);
return translators[0].SQLString;
}
private IQueryTranslator[] BuildQueryTranslators<T>(IQueryable<T> queryable, ISession session)
{
var sessionImp = (ISessionImplementor)session;
var nhLinqExpression = new NhLinqExpression(queryable.Expression, sessionImp.Factory);
var translatorFactory = new ASTQueryTranslatorFactory();
var translators = translatorFactory.CreateQueryTranslators(
nhLinqExpression,
null,
false,
sessionImp.EnabledFilters,
sessionImp.Factory);
return translators;
}