2

I have a search form with multiple inputs - first name, last name, company...

I would like to return entities based on user inputs, or if nothing is input, in the last name field for instance, return all last names.

I believe I should be using null-coalescing for this, just like "ISNULL" in t-sql...

contacts = contacts.Where(s => s.firstname.ToUpper().Contains(fNameSearch.ToUpper() ?? *)

The issue is that I don't know how to use a wildcard in this type of experession.

For instance, this returns everything that contains "test" in the firstname property if fNameSearch is null or white-space...

 contacts = contacts.Where(s => s.firstname.ToUpper().Contains(fNameSearch.ToUpper() ?? "test")

but I want to be able to return everything, not just "test".

tintyethan
  • 1,772
  • 3
  • 20
  • 44

2 Answers2

8

I believe I should be using null-coalescing for this:

fNameSearch.ToUpper() ?? *

Your belief is false. It only makes sense to use ?? when the left hand side can possibly be null. If fNameSearch is null then the call to ToUpper() throws; if it is not null then the call to ToUpper() produces a non-null string. So the ?? operator is not what you want to use.

The operator you're looking for is the lifted nullable member access operator:

fNameSearch.?ToUpper() ?? "test"

This means "if fNameSearch is null then produce null, give that to the ?? operator and get "test"; if it is not null then call ToUpper(), which will produce non-null string.

Unfortunately the .? operator does not exist in C#. It is a frequently requested feature, so perhaps a future version of the language will have it.

but I want to be able to return everything, not just "test".

Then you don't want either operator.

Take a step back. State what you want the predicate's behaviour to be.

  • if fNameSearch is null then match everything
  • otherwise, match firstName.ToUpper().Contains(fnameSearch.ToUpper)

OK, that's an easy predicate to write.

s => fNameSearch == null || s.firstname.ToUpper().Contains(fNameSearch.ToUpper())

So is this code correct?

No. ToUpper is not a good way to canonicalize a name. Remember, names are cultural artifacts and therefore must be searched using the correct rules for the culture associated with the name.

The correct way to do this is to obtain the CultureInfo object for the culture that the name is written in, and then call

culture.CompareInfo.IndexOf(firstname, fNameSearch, CompareOptions.IgnoreCase) 

and see if it comes back with a valid index or not.

Also, you probably should read this article before you write more code that tries to canonicalize names.

http://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Eric, you clearly have a strong grasp of C# and I appreciate your willingness to help. I do not appreciate your approach to addressing my question or the other responses on this thread though. I mean this in the most amicable way possible - you come off as demeaning and self-righteous due to the wording you choose. Most importantly, the question was answered thoroughly and had already been upvoted repeatedly before you decided to leave your answer. I don't know that you've aided the conversation in anyway. – tintyethan Jan 22 '14 at 17:03
  • 3
    @EthanPelton: Text-only formats make it easy to misread concision -- which is a good thing -- for brusqueness or rudeness; none is intended. My suggestion to you is first, to learn to read more charitably, and second, when you post a bunch of buggy, broken code and ask for help, to expect to get a concise list of your mistakes and suggestions for ways to improve it. Moreover: the question had not been answered *thoroughly* before my answer; my answer is the only answer which describes correctly how to do a case-insensitive cultural comparison. – Eric Lippert Jan 22 '14 at 17:33
  • @EthanPelton: Now, it may be the case that the entity framework already does culture-sensitive comparisons; that's not my area of expertise. In the more general case of LINQ-to-objects, however, where the comparison is being done on the client, the approach I describe is correct. – Eric Lippert Jan 22 '14 at 17:35
  • @EthanPelton: Moreover, my answer is the only one which suggests that you do further reading before you attempt to canonicalize names again; **this is a frequent source of business-impacting bugs**. – Eric Lippert Jan 22 '14 at 17:38
  • @EthanPelton: You might want to read my essay on bias in text-only communication. http://blogs.msdn.com/b/ericlippert/archive/2008/02/20/how-to-not-get-a-question-answered.aspx *an inherent and pervasive bias in pure-text communication which makes statements intended to be good-humoured sound sophomoric, makes statements which were intended to be friendly sound smarmy, makes statements which were intended to be enthusiastic sound brash, makes statements intended to be helpful sound condescending, makes statements which were intended to be precise and accurate sound brusque and pedantic...* – Eric Lippert Jan 22 '14 at 17:41
  • @EthanPelton: I assumed your question was about the composition of query filters (conditions), which is what I focussed on in my answer. However, Eric's answer contributed valuably to other aspects of your question that I failed to address (or addressed incorrectly); specifically, the semantics of the null-handling operator you were seeking, and the importance of culture-awareness in string comparisons. You should see the multiplicity of answers offered by SO as a benefit, since you can pick the one that best addresses your issue, whilst noting any relevant suggestions from the others. – Douglas Jan 22 '14 at 19:49
6

If you want to return everything, can't you place the condition before the query?

var contacts = /* construct your original query here */
if (fNameSearch != null)
    contacts = contacts.Where(s => s.firstname.ToUpper().Contains(fNameSearch.ToUpper());

Edit: Per Steve's comment below, case-sensitivity depends on the database's collation (and is case-insensitive by default), so you can just run:

var contacts = /* construct your original query here */
if (fNameSearch != null)
    contacts = contacts.Where(s => s.firstname.Contains(fNameSearch));

Edit2: If you have multiple inputs to check for, you can use the same approach compositionally:

var contacts = /* construct your original query here */
if (string.IsNullOrEmpty(fNameSearch) == false))
    contacts = contacts.Where(s => s.firstname.Contains(fNameSearch));
if (string.IsNullOrEmpty(fSurnameSearch) == false))
    contacts = contacts.Where(s => s.surname.Contains(fSurnameSearch));
if (string.IsNullOrEmpty(fCompanySearch) == false))
    contacts = contacts.Where(s => s.company.Contains(fCompanySearch));
Community
  • 1
  • 1
Douglas
  • 53,759
  • 13
  • 140
  • 188
  • In Entity Framework the string comparison is done on the SQL side, so the case-sensitivity is controlled by the collation settings in the database. – Steve Ruble Jan 22 '14 at 15:53
  • hmmm - I got that from asp.net (the website) samples. I wonder why they'd do that? http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/sorting-filtering-and-paging-with-the-entity-framework-in-an-asp-net-mvc-application – tintyethan Jan 22 '14 at 15:59
  • @Douglas your suggestion works with just one or two cases, but as I mentioned, there are several user inputs - first name, last name, company, etc. Checking for each of the different scenarios would become cumbersome I would think. – tintyethan Jan 22 '14 at 16:01
  • `if(x == false)` is a poor practice. I recommend `if(!x)` instead. – Eric Lippert Jan 22 '14 at 16:48
  • Names should not be compared using ordinal semantics; names are culture-sensitive. – Eric Lippert Jan 22 '14 at 16:49
  • @EricLippert: I agree with your second point (and I've removed the faulty snippet from my answer), but not your first, which is a matter of personal preference. I find `!` too easy to miss when skimming through code. – Douglas Jan 22 '14 at 18:20
  • @Douglas: As you point out, it is possible that the entity framework handles this situation correctly on the server side; I'm not an expert on that. If the comparison is being done on the client side then the correct culture object should be used to do the comparison. – Eric Lippert Jan 22 '14 at 18:45
  • @EricLippert: I'm not sure about Entity Framework either, but [this answer](http://stackoverflow.com/a/3843382/1149773) indicates that the string comparison depends on the collation that happens to be configured on the server. You're right about culture-awareness on the client-side. (I got used to ordinal comparisons for performance over ASCII strings.) – Douglas Jan 22 '14 at 18:55