views:

398

answers:

1

I'm trying to write Linq-To-SQL queries in LinqPad to help migrate users from old tables to new tables. Part of this migration is storing all addresses in a separate table. I'm using the following query to determine if a user's address exists in the new table (so I don't have duplicate entries):

var addresses = from a in Addresses where ((u.Street_address == null && a.Street1 == null) || (u.Street_address != null && a.Street1 != null && a.Street1.ToLower() == u.Street_address.ToLower())) 
        && ((a.City == null && u.City == null) || (a.City != null && u.City != null && a.City.ToLower() == u.City.ToLower())) 
        && ((a.State == null && u.State == null) || (a.State != null && u.State != null && a.State.ToLower() == u.State.ToLower())) 
        && ((a.Zip == null && u.Zipcode == null) || (a.Zip != null && u.Zipcode != null && a.Zip.ToLower() == u.Zipcode.ToLower()))
        select a;

Here, 'u' represents an old user. Some addresses in the old table contain null entries for the Street_address, City, State, and/or Zipcode. Also, some addresses are duplicates, except for casing (hence the ToLower()).

Despite checking for null parameters in the query, I still get a NullReferenceException if any of the user's address parameters are null.

Am I doing something wrong? Or is there a better way to accomplish what I need?

+1  A: 

UPDATE:

Hmmm, looks like this is more complex than I originally thought. Turns out String.Equals with a StringComparison overload is not supported by Linq-to-SQL. But, the fact that you get the error means that Linq-to-SQL is trying to take the entire expression and turn it into SQL. Which in turn means that all comparisons will happen according to the native collation of the database-- which by default is case-insensitive. So even though Linq-to-SQL won't support case-insensitive comparisons, you probably don't need case-insensitive comparisons since you can rely on SQL Server doing them by default.

So, provided you haven't changed the collation of your string columns in your table from case-insensitive (the default) to case-sensitive, the following code should work:

var addresses = from a in Addresses 
                where String.Equals (u.Street_address, a.Street1)
                      && String.Equals (u.City, a.City)
                      && String.Equals (u.State, a.State)
                      && String.Equals (u.ZipCode, a.Zip)
                select a;

It's possible this may work too:

var addresses = from a in Addresses 
                where u.Street_address == a.Street1
                      && u.City == a.City
                      && u.State == a.State
                      && u.ZipCode == a.Zip
                select a;

But, based on my reading of this MSDN article (excerpted below), I suspect that only using == (instead of String.Equals() may not work) :

Null semantics

LINQ to SQL does not impose null comparison semantics on SQL. Comparison operators are syntactically translated to their SQL equivalents. For this reason, the semantics reflect SQL semantics that are defined by server or connection settings. For example, two null values are considered unequal under default SQL Server settings, but you can change the settings to change the semantics. LINQ to SQL does not consider server settings when it translates queries.

A comparison with the literal null is translated to the appropriate SQL version (is null or is not null).

In other words, if I'm reading this MSDN text correctly, it sounds like Linq-to-SQL translates == into = in T-SQL, while (as your experiment showed) String.Equals is translated correctly as a check for IS NULL followed by a check using =. If you have a chance to test just ==, I'd be interested to see whether Linq-to-SQL emits the IS NULL checks or not.

Given the complexity here (Linq-to-SQL translating C# into SQL, and results back to C#) your best bet in cases like this is to try multiple variations (e.g. == vs. Equals()) and pick the one that works, since there are enough moving parts that it's hard to anticipate ahead of time which variation will work best.

OLD ANSWER (ignore this):

Consider using the static String.Equals method instead of == and ToLower(). You'll avoid the null-reference problems (and simplify your code) because nulls are OK to pass into that method and it supports a case-insensitive check.

var addresses = from a in Addresses 
                where String.Equals (u.Street_address, a.Street1, StringComparison.OrdinalIgnoreCase)
                      && String.Equals (u.City, a.City, StringComparison.OrdinalIgnoreCase)
                      && String.Equals (u.State, a.State, StringComparison.OrdinalIgnoreCase)
                      && String.Equals (u.ZipCode, a.Zip, StringComparison.OrdinalIgnoreCase)
                select a;

Although if your database is already case-insensitive, depending on how Linq-to-SQL splits the work between SQL and C# code, you may not need the insensitivity check at all-- although if it were me, I'd rather be safe and enure you always enforce the case check.

Justin Grant
String.Equals doesn't seem to work with Linq-to-SQL. You get the following exception: "NotSupportedException: Method 'Boolean Equals(System.String, System.String, System.StringComparison)' has no supported translation to SQL." I also tried using String.IsNullOrEmpty() for the null check, but I got the same error message
Jameel Al-Aziz
take a look at my revised answer-- the error message you got is a good sign, it means that the solution is probably even easier! :-)
Justin Grant
Thanks!!! That worked. What's really interesting is after looking at the generated SQL queries, String.Equals() generates queries similar to what I had originally without the ToLower() method calls. Basically, it checks for not null, then checks for equality. So just using u.Street_address == a.Street1 is not enough, you have to also check for null. I believe that's because SQL cannot compare NULL using =? What I don't understand is why == is not being evaluated as String.Equals()?
Jameel Al-Aziz
Did you try using just ==, without the ToLower() or null check? If so, did Linq-to-SQL correctly emit the "check if both null" check into SQL? If not, that sounds like a bug in Linq-to-SQL's translation of C# semantics to SQL and back again.
Justin Grant
Actually, reading MSDN (see link and excerpt in my just-revised answer above) it looks like the lack of IS NULL checking my be "by design" when using ==. It may be questionable design, though... :-)
Justin Grant
I did try using just == initially. Then I decided to try the IsLower() because I was getting duplicates and I thought it was a casing problem. The == operator translates to the = operator, but String.Equals() translates to "check if both null, then =". However, in normal C# == translates to String.Equals(). I can understand why you'd want the difference, but it's certainly confusing if you're used to the C# behavior. Finally, thank you for the MSDN article!
Jameel Al-Aziz