views:

532

answers:

2

The code we're using is straight-forward in this part of the search query:

myCriteria.Add(
    Expression.InsensitiveLike("Code", itemCode, MatchMode.Anywhere));

and this works fine in a production environment.

The issue is that one of our clients has item codes that contain % symbols which this query needs to match. The resulting SQL output from this code is similar to:

SELECT ... FROM ItemCodes WHERE ... AND Code LIKE '%ItemWith%Symbol%'

which clearly explains why they're getting some odd results in item searches.

Is there a way to enable escaping using the programmatic Criteria methods?


Addendum:

We're using a slightly old version of NHibernate, 2.1.0.4000 (current as of writing is 2.1.2.4853), but I checked the release notes, and there was no mention of a fix for this. I didn't find any open issue in their bugtracker either.

We're using SQL Server, so I can escape the special characters (%, _, [, and ^) in code really easily, but the point of us using NHibernate was to make our application database-engine-independent as much as possible.

Neither Restrictions.InsensitiveLike() nor HqlQueryUtil.GetLikeExpr() escape their inputs, and removing the MatchMode parameter makes no difference as far as escaping goes.


Update: I found someone else wanting to do this same thing (three years ago), and the resolution was to add the escapeChar overloads to the methods I've mentioned above (this was "fixed" in version 2.0.0.3347). I added a comment to that issue asking for further resolution.

A: 

You can create an instance of LikeExpression to accomplish this. In this example I am escaping % with a backslash (which itself has to be escaped):

var itemCode = "ItemWith%Symbol";
itemCode = searchCode.Replace("%", "\\%");
var exp = new LikeExpression("Code", itemCode, MatchMode.Anywhere, '\\', true);
myCriteria.Add(exp);

I didn't see a static method to return a LikeExpression using this overload.

By the way, if you're using SQL Server it is case insensitive by default.

Jamie Ide
Thanks for the reply. We are using SQL Server, but as I said, the point of using NHibernate was to make our back end database-agnostic. I'd prefer a solution that will work based on the selected NHibernate dialect. In SQL Server the `[` character is also an escape character. I suppose escaping those in other dialects would work... but it's not really clean, if you know what I mean. And since we've got these calls to `Like` and `InsensitiveLike` sprinkled through our data layer, it would be nice to use an internal function/option to do it, instead of rolling our own.
Jon Seigel
Jon, I don't understand your comment. My answer does a case-insensitive LIKE -- the true final parameter is supplied for ignoreCase: `public LikeExpression(string propertyName, string value, char? escapeChar, bool ignoreCase)`. I was just pointing out that it has no effect on SQL Server. There is no overloaded constructor on InsensitiveLikeExpression that accepts an escape character.
Jamie Ide
@Jamie: I understand that. What I'm saying is that SQL Server has an additional escape character (`[`) while other engines do not. I guess to get at least one escape character in there, all the existing code has to change, but for engines other than SQL Server, I'd rather not be escaping `[` if I don't have to.
Jon Seigel
OK, I understand. I assumed that only the wildcard characters would present a problem with LIKE but that's not the case. I'll add a new answer if I come up with a good one.
Jamie Ide
+2  A: 

The only way I can find to achieve database independence is to escape every character in the search string and call the appropriate constructor on LikeExpression as in my previous answer. You could do this manually or extend LikeExpression:

    public class LikeExpressionEscaped : LikeExpression
    {
        private static string EscapeValue(string value)
        {
            var chars = value.ToCharArray();
            var strs = chars.Select(x => x.ToString()).ToArray();
            return "\\" + string.Join("\\", strs);
        }

        public LikeExpressionEscaped(string propertyName, string value, MatchMode matchMode, bool ignoreCase)
            : base(propertyName, EscapeValue(value), matchMode, '\\', ignoreCase)
        {}
    }

There is no doubt a more efficient way to create the escaped string (see the answers and comments to this question). Usage is:

var exp = new LikeExpressionEscaped("Code", itemCode, MatchMode.Anywhere, true);
myCriteria.Add(exp);
Jamie Ide
Jamie, thanks for your help. It appears what I'm looking for doesn't exist (see my update in the question body). The approach you gave here is the one I'm going to take. I'll mark this answer as accepted for now, and if the authors of NHibernate decide to do anything, I will revisit this issue at that time.
Jon Seigel