3

I have the following LINQ code used in a unified search function.

var searchObjects =
    from objectA in this.context.DB.objectAs
    join objectB in this.context.DB.objectBs on objectA equals objectB.objectA into objectAB
    from AB in objectAB.Where(o => o.Type == "BasicGroup").DefaultIfEmpty()
    select new { objectA, objectB = AB};

foreach (var searchWord in searchWords)
{
    var searchObjects =
        searchObjects.Where(p => p.objectA.Name.Contains(searchWord) ||
            (p.objectB != null &&
                (p.objectB.Name.Contains(searchWord) ||
                p.objectB.ID.contains(searchWord))));
}

The goal is to look for the search words in objectA's Name field, or in the associated objectB's Name or ID fields. The problem is that when I try to enumerate searchObjects, it just returns a NullReferenceException, with no further details.

The first part of the query returns a proper list of the combination objects (without any filters), so I don't think the problem is with the left join?

I can't figure out what is causing the exception, so any help would be appreciated.

I am also using Telerik's OpenAccess ORM, but I don't think that should be causing any problems here?

EDIT:

Turns out this was an issue with th Telerik OpenAccess ORM, which under certain conditions would just give up on producing sensible SQL, draw everything into memory and treat it as L2Objects (which should fail on nulls, as pointed out by @Dead.Rabit). The condition that seemed at least part of the problem was the .Where(o => o.Type == "BasicGroup") in front of the .DefaultIfEmpty(). Updating to the latest version of OpenAccess (2013 Q1 SPI I think it was) allows me to rewrite that condition as part of the equals statement

on new { objectA.ID, Type = "BasicGroup" } equals new { ID = objectB.AID, Type = object.Type }

This wasn't possible before the SP1. With this new query, I am able to compose the searchword Where clauses into the query and still have it produce SQL rather than drawing it into memory.

Miika L.
  • 3,333
  • 1
  • 24
  • 35
  • You should split the various parts up and inspect the results of each in the debugger or via a dump method to see which bit is returning null. (I'm suspicious of the `DefaultIfEmpty()` which can of course return null) – Matthew Watson May 13 '13 at 10:11
  • @MatthewWatson But how can I split it further? The first part works, it returns a list of combination objects. The second part has OR conditions, so they have to be part of the same where query, no? And I know it is the part within the foreach that breaks the query, even with a single iteration. – Miika L. May 13 '13 at 10:18

2 Answers2

2

AB can be null since your creating it with DefaultIfEmpty() function, but also if ObjectA.Name can be null; Contains() in your second statement will throw an error. Similarly if ObjectB.ID or ObjectB.Name can be null when ObjectB is not null then your ObjectB != null guard clause won't have the desired effect.

Try a simple enumaration of your objects to ensure the issue is with your first query

foreach( var item in searchObjects )
    Console.WriteLine( item.Type );

Your redefining the searchObjects variable every itteration which I assume is something lost in the code-dump to SO, but worth a mention JIC.

Finally I'm not sure about the OpenAccess ORM since I've never used it, but this query would fail on the Dynamics CRM implementation of LINQ which doesn't support certain types of conditions in where clauses (I won't go into details because it's irrelevant, but you've broken all of them in this query :p). It's definitely worth browsing the documentation for gotcha's in a 3rd party LINQ implementation.

Dead.Rabit
  • 1,965
  • 1
  • 28
  • 46
  • Hmm, it appears that you have hit the nail on the head. The exception was a result of objectB Name or ID being null. However, my earlier version of this code used a .Any subquery rather than LINQ join, and it did not have this issue. Is it possible that (the ORM perhaps) is drawing this data into objects and then running it as Linq2Objects thus resulting in null exceptions rather than this all running in SQL? This would also explain how this query has also gone from case insensitive to case sensitive. – Miika L. May 13 '13 at 10:55
  • It's possible, I can't really comment, but you're right that if the query was performed against Linq2Objects it should fail. If your interested you should get a transaction log from SQL to see where it's splitting the query. Personally I'd just solve the problem I see in-front of me though by implementing a case-insensitive, null-safe string comparison function. – Dead.Rabit May 13 '13 at 12:21
  • Actual solution edited into question text, but this pointed me in the right direction. – Miika L. May 15 '13 at 05:41
1

I'm not 100% sure but i think you need to use AB instead of objectB in your anonymous type.

select new { objectA, AB }

because thats the result of the left join.

Viper
  • 2,216
  • 1
  • 21
  • 41
  • ah, thats how I had it in my code, just wrote it wrong in the question. Thanks for pointing it out, but it doesn't solve the problem. – Miika L. May 13 '13 at 10:13