NullReferenceException in QueryTranslator.VisitConstant(ConstantExpression c) - Closed

Dec 22, 2011 at 11:19 AM
Edited Dec 22, 2011 at 11:32 AM

Sorry to be a pain but I'm getting another NullReferenceException.

In the app, I've open up a search page and it's returned a set of UserObjects, then I click on one and it's supposed to fetch the details.

The exception was occurring in DirectoryQueryProvider.Execute(Expression expression) - I had to comment out the try-catch block to see the values in play.

I've tracked it down to QueryTranslator.VisitConstant(ConstantExpression c) here:

 var str = _dnFilter 
    ? (c.Value as string).CleanFilterValue() 
    : _currentProperty.FormatValueToFilter(c.Value);

The c is a Boolean and c.Value is true, so (c.Value as string) is null, and then we get the NullReferenceException.

I've added an implementation of ILinqToLdapLogger which uses Debug.WriteLine to output the trace.  I believe this is the trace causing the problem.

Trace:: Search Request >
Filter: (&(objectCategory=person)(&TRUE(objectGuid=\7e\d8\76\3e\f2\24\48\42\b8\a1\b1\9e\b4\b3\30\1e)))
Attributes: distinguishedname, cn, employeeID, spam_csIMAApplicantNumber, objectGuid, sAMAccountName, seeAlso
Naming Context: DC=spam,DC=eggs,DC=chipsand,DC=spam
Scope: Subtree
Types Only: False
Controls:
Page Size: 500
Page Cookie Length: 0
Page Control Is Critical: True
Page Control OID: 1.2.840.113556.1.4.319

To get this, I had to change the above section of code to look like this:

    if (c.Value.GetType() == typeof(bool) &&
        ((bool)c.Value) == true)
    {
        _sb.Append("TRUE");
    }
    else
    {
        var str = _dnFilter
            ? (c.Value as string).CleanFilterValue()
            : _currentProperty.FormatValueToFilter(c.Value);

        if (!_dnFilter && str.IsNullOrEmpty())
        {
            throw new FilterException("Cannot use white spaces for a value in a filter.");
        }
    
        _sb.Append(str);
    }

which allowed us to see the query being submitted, after a fashion.  The query is auto-generated by LightSwitch so I don't know exactly what it sends.

I haven't a clue what to do about this.  I'd largely ignored LINQ (apart from the odd Where on a collection) until now.  I've just bought a LINQ book and I'm on chapter 3 but I don't think we get to expression trees til chapter 16 and then there's no guarantee it'll make sense to this brain.  :-)

Dec 22, 2011 at 11:32 AM
Edited Dec 22, 2011 at 7:31 PM

EDIT: I've re-run the traces to capture better data, and replaced the content of this post.

I switched on LightSwitch tracing (which required me to change the above to something which would work (i.e. not throw an exception) so that I could access the Trace.axd handler.  I changed it to  _sb.Append(String.Empty) - okay, I could've just removed it but something odd is a better reminder of what I did than white space.

In the /Trace.axd, for the initial page load (of 45 'rows' - a LightSwitch default), we see:

[Microsoft.LightSwitch.DataService][Application:Information][LightSwitchApplication.LinqToLdapServiceData:UserObjects_All] Executing 'UserObjects_All'

[Microsoft.LightSwitch.DataService][Application:Information][LightSwitchApplication.LinqToLdapServiceData:UserObjects_All:PreProcessQuery] Query Arguments: 

[Microsoft.LightSwitch.DataService][Application:Information][LightSwitchApplication.LinqToLdapServiceData:UserObjects_All:DataProviderQuery] source.OrderBy(e => e.objectGuid).Skip(0).Take(45)

[Microsoft.LightSwitch.DataService][Application:Information][LightSwitchApplication.LinqToLdapServiceData:UserObjects_All:QueryExecuted] The query returned 45 results.

[Microsoft.LightSwitch.DataService][Application:Information][LightSwitchApplication.LinqToLdapServiceData:UserObjects_All] Finished executing 'UserObjects_All'

I'm only showing the trace statements relating to the DataService.  There are loads of UserCode statements but I'm not doing anything in user code so I've excluded them.  The essential part of this seems to be

source.OrderBy(e => e.objectGuid).Skip(0).Take(45)

For this, the logger shows:

Trace:: Search Request >
Filter: (objectCategory=person)
Attributes: distinguishedname, cn, employeeID, camelot_csIMAApplicantNumber, objectGuid, sAMAccountName, seeAlso
Naming Context: DC=large,DC=wooden,DC=badger
Scope: Subtree
Types Only: False
Controls: 
Page Size: 45
Page Cookie Length: 0
Page Control Is Critical: True
Page Control OID: 1.2.840.113556.1.4.319

For the load of the single object, we see:

[Microsoft.LightSwitch.DataService][Application:Information][LightSwitchApplication.LinqToLdapServiceData:UserObjects_SingleOrDefault] Executing 'UserObjects_SingleOrDefault'

[Microsoft.LightSwitch.DataService][Application:Information][LightSwitchApplication.LinqToLdapServiceData:UserObjects_All:PreProcessQuery] Query Arguments: 

[Microsoft.LightSwitch.DataService][Application:Information][LightSwitchApplication.LinqToLdapServiceData:UserObjects_SingleOrDefault:PreProcessQuery] Query Arguments: QueryOperationParameter:: Name = 'objectGuid', ID = 'LightSwitchApplication:LinqToLdapServiceData/Members[UserObjects_SingleOrDefault]/Parameters[objectGuid]': 50aa3a5f-d916-485e-893f-7190c6c0d3d1

[Microsoft.LightSwitch.DataService][Application:Information][LightSwitchApplication.LinqToLdapServiceData:UserObjects_SingleOrDefault:DataProviderQuery] source.Where(u => (value(LightSwitchApplication.Implementation.LinqToLdapServiceDataServiceImplementation+<>c__DisplayClass2).objectGuid.HasValue AndAlso (Convert(u.objectGuid) == value(LightSwitchApplication.Implementation.LinqToLdapServiceDataServiceImplementation+<>c__DisplayClass2).objectGuid)))

[Microsoft.LightSwitch.DataService][Application:Information][LightSwitchApplication.LinqToLdapServiceData:UserObjects_SingleOrDefault:QueryExecuted] The query returned 1 results.

[Microsoft.LightSwitch.DataService][Application:Information][LightSwitchApplication.LinqToLdapServiceData:UserObjects_SingleOrDefault] Finished executing 'UserObjects_SingleOrDefault'

The essential part of this seems to be the rather more complicated than before:

source.Where(u => (value(LightSwitchApplication.Implementation.LinqToLdapServiceDataServiceImplementation+<>c__DisplayClass2).objectGuid.HasValue AndAlso (Convert(u.objectGuid) == value(LightSwitchApplication.Implementation.LinqToLdapServiceDataServiceImplementation+<>c__DisplayClass2).objectGuid)))
And for this, the logger shows (with my modification in action):

Trace:: Search Request >
Filter: (&(objectCategory=person)(&(objectGuid=\5f\3a\aa\50\16\d9\5e\48\89\3f\71\90\c6\c0\d3\d1)))
Attributes: distinguishedname, cn, employeeID, camelot_csIMAApplicantNumber, objectGuid, sAMAccountName, seeAlso
Naming Context: DC=large,DC=wooden,DC=badger
Scope: Subtree
Types Only: False
Controls: 
Page Size: 500
Page Cookie Length: 0
Page Control Is Critical: True
Page Control OID: 1.2.840.113556.1.4.319

Granted, this is for a different user from before but I'm probably connecting to a different DC, now.  Hopefully, this is better information for you to diagnose the problem.

Coordinator
Dec 23, 2011 at 1:12 AM

I'm having trouble reproducing the NullReferenceException for a mapped boolean.  The _dnFilter field is only set to true when you use the Filter class for building queries, otherwise it relies on the property mapping to know the appropriate filter format.  The implementation for bool is pretty simple: true.Equals(value) ? "TRUE" : "FALSE".  I've checked in a change that will log the expression.  Also, can you use the SimpleTextLogger in LinqToLdap.Logging?  It will log the full exception using an ObjectDumper.

context.Logger = new SimpleTextLogger(Console.Out);

Dec 23, 2011 at 7:24 AM
Edited Jan 28, 2012 at 6:32 AM

Here's the output from SimpleTextLogger:

 

Initializing Connection Pool.
Scavenge Timer Started.
Creating Connection For Use.
Building Connection
Connection Built
In Use Connection Count: 1
Available Connection Count: 0
Expression: value(LinqToLdap.DirectoryQuery`1[LightSwitchToLdapService.Entities.UserObject]).OrderBy(e => e.objectGuid).Skip(0).Take(45)
Search Request >
Filter: (objectCategory=person)
Attributes: distinguishedname, cn, employeeID, camelot_csIMAApplicantNumber, objectGuid, sAMAccountName, seeAlso
Naming Context: DC=arthur,DC=lancelot,DC=bedevere
Scope: Subtree
Types Only: False
Controls: 
Page Size: 45
Page Cookie Length: 0
Page Control Is Critical: True
Page Control OID: 1.2.840.113556.1.4.319


Expression: value(LinqToLdap.DirectoryQuery`1[LightSwitchToLdapService.Entities.UserObject]).Where(u => (value(LightSwitchApplication.Implementation.LinqToLdapServiceDataServiceImplementation+<>c__DisplayClass2).objectGuid.HasValue AndAlso (Convert(u.objectGuid) == value(LightSwitchApplication.Implementation.LinqToLdapServiceDataServiceImplementation+<>c__DisplayClass2).objectGuid)))
Message=Object reference not set to an instance of an object.   Data=...        InnerException={ }      TargetSite={ }  StackTrace=   at LinqToLdap.Visitors.QueryTranslator.VisitConstant(ConstantExpression c)
   at LinqToLdap.Visitors.ExpressionVisitor.Visit(Expression exp)
   at LinqToLdap.Visitors.QueryTranslator.VisitBinary(BinaryExpression b)
   at LinqToLdap.Visitors.ExpressionVisitor.Visit(Expression exp)
   at LinqToLdap.Visitors.ExpressionVisitor.VisitLambda(LambdaExpression lambda)
   at LinqToLdap.Visitors.ExpressionVisitor.Visit(Expression exp)
   at LinqToLdap.Visitors.QueryTranslator.VisitUnary(UnaryExpression u)
   at LinqToLdap.Visitors.ExpressionVisitor.Visit(Expression exp)
   at LinqToLdap.Visitors.QueryTranslator.VisitMethodCall(MethodCallExpression m)
   at LinqToLdap.Visitors.ExpressionVisitor.Visit(Expression exp)
   at LinqToLdap.Visitors.QueryTranslator.Translate(Expression expression)
   at LinqToLdap.DirectoryQueryProvider.TranslateExpression(Expression expression)
   at LinqToLdap.DirectoryQueryProvider.Execute(Expression expression)        HelpLink=null   Source=LinqToLdap

I posted a question about why the query was so ugly and the answer was:

"The underlying type of the query parameter is a nullable type therefore in order to avoid causing the custom RIA data service from throwing a NullReferenceException when the parameter is null, LightSwitch injects the appropriate null checks so it is easier to write the custom Ria service.
 
"Assumption:  objectGuid is a Nullable<Guid>
 
"Therefore the query is essentially the following:  source.Where(u=> objectGuid.HasValue && u.objectGuid == objectGuid.Value)"

UserObject is defined as in this post, so it's not Nullable<Guid>.  I'm following that up in the other forum.  Does that help at all?

EDIT

madhatter22 has created an issue for this.

 

Coordinator
Dec 23, 2011 at 3:43 PM
Edited Dec 23, 2011 at 6:41 PM

Actually, that does help quite a bit.  The NullReferenceException is coming from _currentProperty since "objectGuid.HasValue" cannot be translated to a mapped property.  I'm currently working on a solution so I'll keep you posted.  

EDIT

The solution I have feels a bit like a hack since I haven't addressed the HasValue check to prevent a nullable from accessing Value.

//WORKS
//prepare
DateTime? now = DateTime.Now;
_expression = e => now.HasValue && e.Property4 == now.Value;

//act
_translator.Translate(_expression);

//assert
_factory.Filter.Should().Be.EqualTo("(&(a=" + now.Value.ToFileTime() + "))");


//THROWS EXCEPTION
//prepare
DateTime? now = null;
_expression = e => now.HasValue && e.Property4 == now.Value;

//act
_translator.Translate(_expression);

//assert
_factory.Filter.Should().Be.EqualTo("(&(a=*))");