Is it composable? - Closed

Dec 20, 2011 at 11:26 AM

I'm puzzled. I'm trying to compose a query (I think that's what it's called) and I'm getting a NullReferenceException "Object reference not set to an instance of an object." from the code.

I should say that I'm not at all experienced using LINQ.  I think, from other samples I seen using LINQ to Entities, that this sort of thing is possible with LINQ - I'm not sure if it's a fundamental thing to LINQ or varies by LINQ provider.  Should this be possible in LINQ to LDAP?

I'm trying to return an IQueryable<UserObject> from a service class and then narrow down the query in a client class.  I'm doing all of this in the same console program at the mo, but the intention is to use it in a Silverlight/LightSwitch app using WCF RIA Services.

I have a UserObject to represent my users.  I have a ClassMap called UserObjectMap.  I have a service class which returns objects, called UserObjectService.

Here is my UserObject class:

    public class UserObject
    {
        [Key]
        public Guid objectGuid { get; set; }

        public String commonName { get; set; }

        [Editable(false)]
        public String distinguishedName { get; set; }

        public String employeeID { get; set; }

        [Display(Name = "UserID", Description = "The Active Directory User logon name (pre-Windows 2000), also known as sAMAccountName.")]
        public String sAMAccountName { get; set; }
    }

and my ClassMap for it:

public class UserObjectMap : ClassMap<UserObject>
    {
        public UserObjectMap()
        {
            NamingContext("DC=my,DC=domain,DC=name");
            ObjectClass("User");

            DistinguishedName(x => x.distinguishedName);
            Map(x => x.objectGuid);
            Map(x => x.commonName).Named("cn");
            Map(x => x.employeeID);
            Map(x => x.sAMAccountName);
        }
    }

Here is my UserObjectService class:

public class UserObjectService
    {
        static UserObjectService()
        {
            var config = new LdapConfiguration()
                .AddMapping(new UserObjectMap());
            config.ConfigureFactory("my.domain.name");
        }

        public UserObject GetUserObject()
        {
            using (var context = new DirectoryContext())
            {
                var user = context.Query<UserObject>().Where(u => u.employeeID == "1234").FirstOrDefault();
                return user;
            }
        }

        public IQueryable<UserObject> GetUserObjects()
        {
            using (var context = new DirectoryContext())
            {
                var users = context.Query<UserObject>();
                return users;
            }
        }

        public IEnumerable<UserObject> GetUserObjectList()
        {
            using (var context = new DirectoryContext())
            {
                var users = context.Query<UserObject>().Where(u => u.employeeID == "1234");
                return users.ToList();
            }
        }

GetUserObjectList() returns a list of user accounts, as expected.  This method was just to demonstrate that LINQtoLDAP was finding objects.

GetUserObject() returns a single object, as expected.

When I compose GetUserObjects in the client class, with the same query as GetUserObject(), it throws the NullReferenceException:

    var service = new UserObjectService();
            
    var userObject = service.GetUserObjects().Where(u => u.employeeID == "1234").FirstOrDefault(); 

Should this work?

In case it helps, this is the stack trace:

   at LinqToLdap.Visitors.QueryTranslator.VisitMemberAccess(MemberExpression m) in C:\Users\ahatter\Documents\Visual Studio 2010\Projects\linqtoldap\Trunk\LinqToLdap\Visitors\QueryTranslator.cs:line 857
   at LinqToLdap.Visitors.ExpressionVisitor.Visit(Expression exp) in C:\Users\ahatter\Documents\Visual Studio 2010\Projects\linqtoldap\Trunk\LinqToLdap\Visitors\ExpressionVisitor.cs:line 69
   at LinqToLdap.Visitors.QueryTranslator.VisitBinary(BinaryExpression b) in C:\Users\ahatter\Documents\Visual Studio 2010\Projects\linqtoldap\Trunk\LinqToLdap\Visitors\QueryTranslator.cs:line 730
   at LinqToLdap.Visitors.ExpressionVisitor.Visit(Expression exp) in C:\Users\ahatter\Documents\Visual Studio 2010\Projects\linqtoldap\Trunk\LinqToLdap\Visitors\ExpressionVisitor.cs:line 59
   at LinqToLdap.Visitors.ExpressionVisitor.VisitLambda(LambdaExpression lambda) in C:\Users\ahatter\Documents\Visual Studio 2010\Projects\linqtoldap\Trunk\LinqToLdap\Visitors\ExpressionVisitor.cs:line 315
   at LinqToLdap.Visitors.ExpressionVisitor.Visit(Expression exp) in C:\Users\ahatter\Documents\Visual Studio 2010\Projects\linqtoldap\Trunk\LinqToLdap\Visitors\ExpressionVisitor.cs:line 73
   at LinqToLdap.Visitors.QueryTranslator.VisitUnary(UnaryExpression u) in C:\Users\ahatter\Documents\Visual Studio 2010\Projects\linqtoldap\Trunk\LinqToLdap\Visitors\QueryTranslator.cs:line 686
   at LinqToLdap.Visitors.ExpressionVisitor.Visit(Expression exp) in C:\Users\ahatter\Documents\Visual Studio 2010\Projects\linqtoldap\Trunk\LinqToLdap\Visitors\ExpressionVisitor.cs:line 35
   at LinqToLdap.Visitors.QueryTranslator.VisitMethodCall(MethodCallExpression m) in C:\Users\ahatter\Documents\Visual Studio 2010\Projects\linqtoldap\Trunk\LinqToLdap\Visitors\QueryTranslator.cs:line 154
   at LinqToLdap.Visitors.ExpressionVisitor.Visit(Expression exp) in C:\Users\ahatter\Documents\Visual Studio 2010\Projects\linqtoldap\Trunk\LinqToLdap\Visitors\ExpressionVisitor.cs:line 71
   at LinqToLdap.Visitors.QueryTranslator.VisitMethodCall(MethodCallExpression m) in C:\Users\ahatter\Documents\Visual Studio 2010\Projects\linqtoldap\Trunk\LinqToLdap\Visitors\QueryTranslator.cs:line 171
   at LinqToLdap.Visitors.ExpressionVisitor.Visit(Expression exp) in C:\Users\ahatter\Documents\Visual Studio 2010\Projects\linqtoldap\Trunk\LinqToLdap\Visitors\ExpressionVisitor.cs:line 71
   at LinqToLdap.Visitors.QueryTranslator.Translate(Expression expression) in C:\Users\ahatter\Documents\Visual Studio 2010\Projects\linqtoldap\Trunk\LinqToLdap\Visitors\QueryTranslator.cs:line 68
   at LinqToLdap.DirectoryQueryProvider.TranslateExpression(Expression expression) in C:\Users\ahatter\Documents\Visual Studio 2010\Projects\linqtoldap\Trunk\LinqToLdap\DirectoryQueryProvider.cs:line 44
   at LinqToLdap.DirectoryQueryProvider.Execute(Expression expression) in C:\Users\ahatter\Documents\Visual Studio 2010\Projects\linqtoldap\Trunk\LinqToLdap\DirectoryQueryProvider.cs:line 56
   at LinqToLdap.QueryProvider.Execute[TResult](Expression expression) in C:\Users\ahatter\Documents\Visual Studio 2010\Projects\linqtoldap\Trunk\LinqToLdap\QueryProvider.cs:line 72
   at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source)
   at LinqToLdap_Test1.Program.Main(String[] args) in C:\Users\Public\Visual Studio\Projects\Understanding\LinqToLdap\LinqToLdap_Test1\LinqToLdap_Test1\Program.cs:line 30
   at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

 

 

Coordinator
Dec 20, 2011 at 2:14 PM
Edited Dec 20, 2011 at 2:21 PM

The problem is the scope of your DirectoryContext.  Execution of a query occurs whenever IQueryable is enumerated or you call an immediate result method (First, FirstOrDefault, Single, SingleOrDefault, Any, Count, ToList, ToPage, InPagesOf).  The context is long disposed by the time FirstOrDefault is called in your example.  You could implement IDisposable on your service and scope your DirectoryContext to it's lifetime.  You could also create methods that take an expression for your service:

public IEnumerabkle<UserObject> GetUserObjects(Expression<Func<UserObject, bool>> expression)
{
	using (var context = new DirectoryContext())
	{
		var users = context.Query<UserObject>()
                     .Where(expression)
                     .ToList();
                return users;
        }
}

public UserObject GetUserObject(Expression<Func<UserObject, bool>> expression)
{
	using (var context = new DirectoryContext())
	{
		var user = context.Query<UserObject>()
                     .FirstOrDefault(expression);
                return user;
        }
}
var service = new UserObjectService();
            
var userObject = service.GetUserObject(u => u.employeeID == "1234");

Dec 20, 2011 at 4:55 PM

Thanks for the reply.  I'd come to the same realisation.  I pointed the project at the source code and re-ran it and traced it down to the Dispose.  And then looked at the using (var context = ...).  And then felt like an idiot.  In my defense, the offspring was in my ear all day, yesterday, whilst I was trying to get to grips with this.  But still...

I hate to be picky when someone has clearly worked so hard and also clearly knows a lot more than me but I think if you implement Dispose, you should track it and check for it in every method, if that method will access the disposed resource.  Something like:

    private bool disposed;

and then

    if (this.disposed)
    {
        throw new ObjectDisposedException("LDAPDomainService");
    } 

or

    this.GetType().FullName

if you want to avoid the class name string.  There seems to be some debate on whether disposed should be private (I think it should). 

My test RIA Service still isn't working but I feel I'm a step closer.

Coordinator
Dec 21, 2011 at 1:58 AM

You're absolutely correct.  ObjectDisposedException is much more descriptive than a generic NullReferenceException.  I'll be sure to include these checks in the next release.  

On a side note, I've done some thinking about the delegated admin model.  As it stands, you can't reuse a mappings across multiple naming contexts without using dynamic mapping.  I decided to add the naming context to all Query methods so you can optionally override whatever naming context has been mapped for the object.  I hope this gives a little more flexibility.

Dec 22, 2011 at 10:26 AM

Thanks for the ObjectDisposedException work and for the naming context option - it sounds like it'll be really useful to do targetted queries.