4

I'm writing a UI that allows a someone to lookup users by their first and/or last name. For example, if you typed in "Mike" for the first name and "Jo" for the last name, it would return "Mike Jones", "Mike Johnson" and "Mike Jobs". I use the following LINQ statement for this search:

var users = (from u in context.TPM_USER
             where u.LASTNAME.ToLower().Contains(LastName.ToLower())
             && u.FIRSTNAME.ToLower().Contains(FirstName.ToLower())
             select u);

(There may or may not be a better way to do a case-insensitive like clause, but this seems to work)

The problem is if the user types in a first or last name, but then leaves the other field empty. If I type in "Mike" for the first name and leave the Last Name field blank, I want to return all Mikes regardless of their last name. The above query returns no results unless both fields are filled in with at least something.

I tried:

var users = (from u in context.TPM_USER
             where (LastName == "" || u.LASTNAME.ToLower().Contains(LastName.ToLower()))
             && (FirstName == "" || u.FIRSTNAME.ToLower().Contains(FirstName.ToLower()))
             select u);

However, I still get no results unless both fields are filled out. I've verified under the debugger that LastName == "" is indeed true.

UPDATE:

I did some more debugging and this is actually an Oracle issue. The query being generated is:

--Replaced the field list with * for brevity
SELECT * FROM TPMDBO.TPM_USER "Extent1"
     WHERE (('jones' = '') OR ((INSTR(LOWER("Extent1".LASTNAME), LOWER('jones'))) > 0)) AND (('' = '') OR ((INSTR(LOWER("Extent1".FIRSTNAME), LOWER(''))) > 0))

Which at first glance appears to be correct. However, Oracle does not seem to correctly short-circuit the phrase ('' = ''). In fact, if I do:

select * from TPM_USER where '' = ''

I get zero rows. I'm not enough of an Oracle expert to know how this query should be written, but either way it's an Entity Framework dialect bug.

Mike Christensen
  • 88,082
  • 50
  • 208
  • 326

5 Answers5

10

Just add the predicates conditionally:

var users = from u in context.TPM_USER select u;
if (!string.IsNullOrWhiteSpace(FirstName))
    users = users.Where(u => u.FIRSTNAME.ToLower().Contains(FirstName.ToLower()));
if (!string.IsNullOrWhiteSpace(LastName))
    users = users.Where(u => u.LASTNAME.ToLower().Contains(LastName.ToLower()));

Or only the LASTNAME predicate as conditional one.

Later addition:

An expression like Where(u => u.FIRSTNAME.ToLower()... is better to be avoided. They cause any indexes on FIRSTNAME to be ignored, because the field value is converted first and then compared (see here for more details).

There's a big chance you don't need these lower-case conversions. Check the database collation of the field. If it's case-insensitive (CI), which it probably is, you don't need these conversions.

Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
  • Thanks! That works. I'm somewhat curious as to why you have to jump through these hoops though. I'll accept this answer if no one else comes up with a cleaner way involving a single LINQ statement. – Mike Christensen Jun 07 '12 at 16:56
  • Even though it's an Oracle issue, I'm marking this as the answer since it provides the best workaround. However, Steve deserves a few points for confirming this *should and does* work in SQL Server. – Mike Christensen Jun 07 '12 at 17:34
  • For what it's worth, imo this *is* a cleaner way: it results in a minimized LINQ statement, and it's more readable. In addition, `IsNullOrWhiteSpace` is (I would assume) a more robust check than `== ''`. – JKomusin Jun 07 '12 at 17:36
  • @JKomusin - Agreed, it results in a better SQL statement as well. – Mike Christensen Jun 07 '12 at 20:45
  • @MikeChristensen - That's what I was going to ask about actually. This looks like it would return all users initially, and then filter them after that. – Steve Wortham Jun 07 '12 at 20:47
  • @SteveWortham - Since a LINQ query simply builds an expression tree, it doesn't actually *run* until you try to iterate through it. So GertArnold's answer is perfectly fine. – Mike Christensen Jun 07 '12 at 20:51
  • @MikeChristensen - Ah, OK. Got it. – Steve Wortham Jun 07 '12 at 20:52
2

Are you sure that FirstName and LastName aren't null?

You might try writing it like this instead...

string LowerFirstName = (FirstName + "").ToLower();
string LowerLastName = (LastName + "").ToLower();

var users = (from u in context.TPM_USER
             where (LowerLastName == "" || u.LASTNAME.ToLower().Contains(LowerLastName))
             && (LowerFirstName == "" || u.FIRSTNAME.ToLower().Contains(LowerFirstName))
             select u);
Steve Wortham
  • 21,740
  • 5
  • 68
  • 90
  • Yes. These values come from an ASP.NET TextBox control, and as I mentioned I verified they were indeed `""` under the debugger. – Mike Christensen Jun 07 '12 at 16:57
  • @MikeChristensen - Strange. It should work then. I've written code like this before in both Linq and in SQL. It's efficient and to the point. – Steve Wortham Jun 07 '12 at 17:00
  • I wonder if it's a bug with the Oracle Entity Framework driver I'm using. I wish I could see what SQL is being generated, but I haven't a clue how to do that. – Mike Christensen Jun 07 '12 at 17:02
  • @MikeChristensen - You could use the SQL Profiler. Or, you can retrieve the generated SQL with some code. Check this out... http://msdn.microsoft.com/en-us/library/bb386961.aspx or maybe this... http://stackoverflow.com/questions/1412863/how-do-i-view-the-sql-generated-by-the-entity-framework – Steve Wortham Jun 07 '12 at 17:05
  • Yup, I got it working. This appears to be lameness with Oracle. I'll update and re-tag my question. – Mike Christensen Jun 07 '12 at 17:13
  • Great. Well you mentioned Oracle and the first thing I suggested was SQL Server Profiler. That was dumb of me. I guess one of the code examples worked though. ;) – Steve Wortham Jun 07 '12 at 17:23
  • Naw, Oracle has a thing called the SGA Manager that lets you log queries, though it's nowhere near as nice as SQL Server Profiler. Why they decided to use .NET and Oracle for this project confuses me to no end. I run into like ten of these silly little *issues* every week. These technologies just don't play well together. – Mike Christensen Jun 07 '12 at 17:29
1

FYI, if anyone runs into this issue with Oracle, here's a workaround:

var users = (from u in context.TPM_USER
             where (LastName == null|| u.LASTNAME.ToLower().Contains(LastName.ToLower()))
             && (FirstName == null || u.FIRSTNAME.ToLower().Contains(FirstName.ToLower()))
             select u);

This will get converted to:

'' is null

In SQL, which Oracle interprets as true.

Mike Christensen
  • 88,082
  • 50
  • 208
  • 326
  • 1
    If you need to search with Contains/EndsWith/StartWith always in case-insensitive, you can set the CaseInsensitiveLike option to "true" and use the following code: u.LASTNAME.Contains(LastName) The CaseInsensitiveLike option can be turned on in the following way: var config = Devart.Data.Oracle.Entity.Configuration.OracleEntityProviderConfig.Instance; config.QueryOptions.CaseInsensitiveLike = true; – Devart Jun 08 '12 at 15:19
  • @Mike Either FirstName or LastName contains value it returns expected result. If both First and Last Name contains value it returns null. Could you please help me on this? I have one int and one string column. – Rajesh Shetty Oct 12 '17 at 06:28
0

You could simply create a conditional statement around your query:

if (String.IsNullOrWhiteSpace(LastName) && !String.IsNullOrWhiteSpace(FirstName))
{
   var users = (from u in context.TPM_USER
   where (u.FIRSTNAME.ToLower().Contains(FirstName.ToLower()))
   select u);
}
else if (String.IsNullOrWhiteSpace(FirstName) && !String.IsNullOrWhiteSpace(LastName))
{
   var users = (from u in context.TPM_USER
   where (u.LASTNAME.ToLower().Contains(LastName.ToLower()))
   select u);
}
Eoin Campbell
  • 43,500
  • 17
  • 101
  • 157
lkaradashkov
  • 8,609
  • 1
  • 15
  • 12
0

May be you can try checking the length of the search terms to see if it is working in Oracle PL/SQL.

var users = (from u in context.TPM_USER
         where ((LastName ?? "").Trim().Length == 0 || u.LASTNAME.ToLower().Contains(LastName.ToLower()))
         && ((FirstName ?? "").Trim().Length == 0 || u.FIRSTNAME.ToLower().Contains(FirstName.ToLower()))
         select u);
Anil Vangari
  • 570
  • 7
  • 12