How to define your directory object classes and ClassMaps

This article will show how to create a directory object class and associated ClassMap.

For the purposes of this article, I’m going to use the term ‘LDAP object’ to refer to an instance of user or organizationalUnit in the directory and ‘LDAP object class’ to refer to their definitions.  I’ll use ‘directory object class’ to refer to the class that represents the LDAP object in our program.

In my earlier article OrganinizationalUnit CRUD, I showed a simple POCO (which was our directory object class) and a ClassMap but pointed out that whilst they would work, they weren’t really the right way to do things.

This is what we had as the directory object class:

using System;
using LinqToLdap.Mapping;

namespace BlogOuTest1
{
    public class OrganizationalUnitObject
    {
        public Guid objectGuid { get; set; }
        public String distinguishedName { get; set; }
        public String organizationalUnitName { get; set; }
        public String description { get; set; }
        public DateTime whenCreated { get; set; }
        public DateTime whenChanged { get; set; }
    }
}

and this is what we had as the ClassMap:

using System;
using LinqToLdap.Mapping;

namespace BlogOuTest1
{
    public class OrganizationalUnitObjectMap : ClassMap
    {
        public OrganizationalUnitObjectMap()
        {
            NamingContext("DC=big,DC=wooden,DC=badger");    // Update this
            ObjectClass("organizationalUnit");
            ObjectCategory("organizationalUnit");
            DistinguishedName(x => x.distinguishedName);

            Map(x => x.organizationalUnitName).Named("ou").StoreGenerated();
            Map(x => x.description);
            Map(x => x.objectGuid).StoreGenerated();
            Map(x => x.whenChanged).StoreGenerated();
            Map(x => x.whenCreated).StoreGenerated();
        }
    }
}

So, what’s wrong with them?  And why did they work?  Actually, there’s nothing wrong with the OrganizationalUnitObjectMap, at the moment, but we need it to highlight what’s wrong with the OrganizationalUnitObject class.

First, some definitions:

NamingContext – this tells LinqToLdap what to use as the starting point for queries (searches).  It can be a domainContext as shown here or an OU if you want to restrict the searches.

ObjectClass – the directory objectClass of the object being represented by this class.  Yes, I know, too many ‘objects’ and ‘classes’ in that definition but that’s the problem with using an object-oriented language to write programs against a directory which uses OO terms and an OO inheritance model.

ObjectCategory – the directory objectCategory of the object being represented by this class.  If both objectClass and objectCategory are defined, objectCategory is used by LinqToLdap.

DistinguishedName – sets the POCO property holding the distinguishedName of the directory class.

Map – defines a property mapping and returns an IPropertyMapper, which is a fluent interface.  If the name of the property on the POCO is the same as the attribute name on the directory class, only the property name is required, for example:

Map(x => x.description);

If they don’t match, the Named() method allows you to specify the name in the directory, for example:

Map(x => x.organizationalUnitName).Named("ou");

The IPropertyMapper interface also contains .StoreGenerated() which tells LinqToLdap not to include that property in the set of modifications to be written back to the directory.  Since we can’t update the ou attribute directly, the previous line becomes:

Map(x => x.organizationalUnitName).Named("ou").StoreGenerated();

All properties not marked with StoreGenerated that aren’t the DistinguishedName are candidates to be updated.  In fact, in a class defined like ours all properties not marked with StoreGenerated that aren’t the DistinguishedName are updated. There is a way to change this behaviour, more of which later.  Meanwhile, back at the farm…

The IPropertyMapper interface contains other methods but we’ll leave them for a while because we’ve reached the point at which the problems with the above class definitions can be illuminated.  If you look at the OrganizationalUnitObjectMap above, you’ll see that there’s only one property that isn’t marked StoreGenerated and that is description.  So, the only property we’ll want to update is description so it doesn’t matter than every time we run Update() all non-StoreGenerated, non-DN properties are updated because we’ve only got one and it’s the one we want to update.  But if we add another property, say displayName, then it starts to go awry.  Every time we update displayName, we’ll write description back to the directory and vice-versa.  The odd unnecessary update to the directory isn’t a calamity but multiply it by lots of properties on lots of objects and it could be.  There are also circumstances where it won’t work, for instance, unicodePwd over a non-SSL connection.  So, how do we avoid it?

DirectoryObjectBase provides a mechanism to track changes to directory object class instances:

    public abstract class DirectoryObjectBase
    {
        public bool TrackChanges { get; internal set; }
        protected void AttributeChanged(string attribute);
        protected void AttributeChanged<T, TProperty>(Expression<Func<T, TProperty>> attribute);
        protected void ClearAllChanged();
        public IEnumerable<string> GetChangedAttributes()
        protected void RemoveChanged(string attribute);
        protected void RemoveChanged<T, TProperty>(Expression<Func<T, TProperty>> attribute);
    }

So, we derive our directory object class from DirectoryObjectBase (you can see why I used the term ‘directory object class’ now) and use AttributeChanged in our property setter.

I’ve created a new project, called BlogOuTest2 for this, so you can compare the differences (note the new Namespace).  To start quickly, I copied the OrganizationalUnitObject.cs and OrganizationalUnitObjectMap.cs from the BlogOuTest1 project folder to BlogOuTest2 project folder.  I did Add > Existing to include the files in the new project.  I opened both files and updated the namespace statements to reflect the new namespace.  I added a reference to LinqToLdap.  Then I copied the static void Main(string[] args) method from Program.cs in BlogOuTest1 and to replace the static void Main(string[] args) in Program.cs in BlogOuTest2.  To make sure all was well, I ran it before making any changes.

Then, I modified OrganizationalUnitObject as follows:

using System;
using LinqToLdap.Mapping;

namespace BlogOuTest2
{
    public class OrganizationalUnitObject : DirectoryObjectBase
    {
        public Guid objectGuid { get; set; }
        public String distinguishedName { get; set; }
        public String organizationalUnitName { get; set; }

        private string _description;
        public String description
        {
            get { return this._description; }
            set
            {
                if (value != this._description)
                {
                    this._description = value;
                    this.AttributeChanged<OrganizationalUnitObject, String>(a => a.description);
                }
            }
        }

        public DateTime whenCreated { get; set; }
        public DateTime whenChanged { get; set; }
    }
}

Now, when we submit our Update(), LinqToLdap will only update attributes that have been marked using one of the overloads of AttributeChanged.

So, now we know how to set up directory objects, we can look at how to map the different directory syntaxes, in the next article.