Skip to content

Issues with constant values in LINQ Query on NH 4.X #2466

Closed
@ramarivera

Description

@ramarivera

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;
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions