Sunday, August 14, 2011

LINQ To LDAP: CR(U)D

So now that I've covered Adding entries, let's talk about updating. Updating your entries is a little different than in a RDBMS.

Using S.DS.P directly you can make modifications to an entry like so:
var firstNameMod = new DirectoryAttributeModification
                        {
                            Name = "givenname",
                            Operation = DirectoryAttributeOperation.Replace,

                        };
firstNameMod.Add("Jack");

var commentMod = new DirectoryAttributeModification
                        {
                            Name = "comment",
                            Operation = DirectoryAttributeOperation.Add,

                        };
commentMod.Add("add a property to this entry");

var dateOfBirthMod = new DirectoryAttributeModification
                        {
                            Name = "dateofbirth",
                            Operation = DirectoryAttributeOperation.Delete,

                        };

ModifyRequest request = new ModifyRequest("CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", new [] {firstNameMod, commentMod, dateOfBirthMod });

ldapConnection.SendRequest(request);

So what you see here is a request to modify John Doe by changing his given name to Jack, adding a brand new attribute called comment, and deleting his date of birth. Those last two modification will actually make a change to the structure of the object. So how do you do this with LINQ To LDAP? Replace (updating) operations are pretty easy, but I separate adding or removing attributes into different methods because it's difficult to identify the intention (i.e. are you trying to delete an attribute because you set it to null).

I'll continue with my previous User class example, but with a few modifications for change tracking. The primary change is sub typing DirectoryObjectBase (built into LINQ to LDAP) and changing your setters to look like this:
set
{
     _firstName = value;
     AttributeChanged("FirstName");
}

Similar to INotifyPropertyChanged, just call the AttributeChanged when the property changes. However, I don't use the interface because I didn't want to worry about events subscriptions. But don't feel like you have to use DirectoryObjectBase. LINQ To LDAP will work just as well without it.

Change tracking on entries is enabled when you use either a full projection ( Select(u => u) ), no projection or GetByDN. Since I support projections of mapped entries, I had to disable it so you wouldn't accidentally update attributes to an empty value. If your object sub types DirectoryObjectBase and you try to update it without change tracking being enabled, then I actually throw an exception. For dynamic queries, change tracking is already built in so you don't have to do anything special.

So let's update!
var factory = new LdapConnectionFactory("localhost");

//mapped
using (var context = new DirectoryContext(factory.GetConnection(), true))
{
    var user = context.GetByDN<User>("CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local");
    user.FirstName = "Jack";
    context.Update(user);
}

//dynamic
using (var context = new DirectoryContext(factory.GetConnection(), true))
{
    dynamic user = context.GetByDN("CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", "givenname");
    user.givenname = "Jack";
    context.Update(user.DistinguishedName, user);
}

//update structure
using (var context = new DirectoryContext(factory.GetConnection(), true))
{
    context.AddAttribute(
        "CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", "comment", 
        "add a property to this entry");

    context.DeleteAttribute(
        "CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", 
        "dateofbirth");
}

There's one more operation for updating. In order to change an entry's distinguished name or move it to a different location you have to issue a ModifyDN request. This kind of request looks like this:
//Move entry using raw S.DS.P
var dnRequest = new ModifyDNRequest
{
	DistinguishedName = "CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local"
	NewParentDistinguishedName = "CN=Deactivated Users,CN=Employees,DC=Northwind,DC=local",
	NewName = "CN=John Doe"
};

connection.SendRequest(dnRequest);

//Move entry using LINQ To LDAP
string newDn = directoryContext.MoveEntry(
	"CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", 
	"CN=Deactivated Users,CN=Employees,DC=Northwind,DC=local");


//Rename entry using raw S.DS.P
var dnRequest = new ModifyDNRequest
{
	DistinguishedName = "CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local"
	NewParentDistinguishedName = "CN=Users,CN=Employees,DC=Northwind,DC=local",
	NewName = "CN=Jack Doe"
};

connection.SendRequest(dnRequest);

//Rename entry using LINQ To LDAP
string newDn = directoryContext.RenameEntry(
	"CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", 
	"Jack Doe");

And that's all there is to it. One thing that you must know is there's no concept of transactions in LDAP (at least not yet) so if you want to update multiple entries, be aware that if it fails half-way through, there's no rollback.

LINQ To LDAP 2.0 Beta is out over at CodePlex!