4

Is there anyway to implement an repository that uses active directory (or any other data source than relational db) as the data source and can get entity objects that has the ability to lazy load their associations?

An example:

Two classes will be frequently used in various applications we are going to develop

class User
{
   public string DisplayName {get; set;}
   public string UserID {get; set;}
   public string Email {get; set;}
   public string WorkPhone {get; set;}
   // etc.
   public string IList<Group> Groups {get; private set;}
   public User Manager {get; set;}
}

class Group
{
   public string DisplayName {get; set;}
   public string Email {get; set;}
   // etc.
   public IList<User> Members {get; private set;}
   public User Owner {get; private set;}
}

I want to be able to reuse these two classes in all our future applications. For some application, the data source for User and Group would now be active directory, but in the future, we may wanna use some database or a mixture of database and active directory. In order for code reuse, I would employ the repository pattern to design two repositories MySqlDBRepository and ActiveDirectoryRepository for me to retrieve and store Users and Groups.

Suppose we already have a library for active directory, which can be used to get user information represented in texts, i.e. you can get user's name, email, manager id all in string format. I need to implement a repository that can be used in much the same way as Entity framework or NHibernate, i.e.

 var user = _activeDirectoryRepository.GetUserByUserID("bryan");

 foreach(var group in user.Groups) 

     Console.WriteLine(group.Manager.DisplayName);

Most importantly, user.groups and group.Manager both should be lazy-loading automatically. How am I supposed to implement _activeDirectoryRepository.GetUserByUserID("bryan")?

Shuo
  • 4,749
  • 9
  • 45
  • 63
  • 1
    Not sure how to implement the repository above active directory... But I would say that's definitely the way to go. – rsenna Nov 02 '10 at 00:57

3 Answers3

1

Yes, that is a good way of approaching your problem.

Define an interface to represent a repository's actions, for example:

public interface IAccountRepository 
{ 
   User GetUserByAccountName(string accountName);

   List<User> GetUsers(Group group); 
   List<Group> GetGroups(User user); 

   // ... etc...
} 

Then create a business logic layer to map repository onto objects:

public class Accounts
{ 
   IAccountRepository _repository; 

   public Accounts(IAccountRepository repository) 
   { 
      _repository = repository; 
   } 

   public List<User> GetUsers(Group group) 
   { 
      return _repository.GetUsers(group); 
   } 

   public List<Group> GetGroups(User user) 
   { 
      return _repository.GetGroups(user); 
   } 

   public User GetUserByAccountName(string accountName)
   {
       return _repository.GetUserByAccountName(accountName); 
   }



   // etc...
} 

Then to use:

static void Main(string[] args) 
{ 
   // Load from the MSSQL repository. 
   Accounts accounts = new Accounts(new MSSQLAccountRepository());

   User user = accounts.GetUserByAccountName("someuser"); 

   //...

}

Also see: Repository pattern tutorial in C#

Community
  • 1
  • 1
Mitch Wheat
  • 295,962
  • 43
  • 465
  • 541
  • Will I be able to use user.Groups in that case? – Shuo Nov 02 '10 at 03:21
  • 2
    Doesn't seem like there's any value to the Accounts class here, since it does nothing but exactly wrap the IAccountRepositoy. In fact, it could implement IAccountRepository itself. So why not just have a reference to IAccountRepository and be done with it? – quentin-starin Nov 02 '10 at 14:39
  • Agree with @qstarin - nothing wrong with the repo/business layer combo, but if you're going down that path, have your repo with a single `IQueryable Find()` method (deferred exec), and your business layer execute the queries off this. No point having 10+ find methods in your repo and wrapping them all in another layer (IMO) – RPM1984 Nov 02 '10 at 20:52
  • Re: the Accounts class, I have simplified to the point where you are correct; it is just a wrapper. The idea would be to have more complicated business methods in it. – Mitch Wheat Nov 02 '10 at 23:33
1

I want to be able to reuse these two classes in all our applications.

Ok, I'm going to suggest re-evaluating this whole idea.

Reuse is [often] a fallacy.

What exactly is the gain you get from moving these classes into a reusable library linked to several applications?

What exactly is the pain you'll experience moving these classes into a reusable library linked to several applications?

I'm pretty confident that if you go this route you will experience considerably more pain than gain.

I don't want to copy the User, Group class definitions (source code) from one app to another.

Why? What does this really gain you?

How much time do you honestly expect to save by clicking Add Reference versus copy & pasting a directory of code files and clicking Add Existing Item?

How much time do you think you'll lose the first time you change this library and create a bug in some application other than the one you made the change for? How much time will you spend managing multiple versions of a shared library linked to several different applications? How much time do you think you'll waste over-generalizing the concepts for this library, versus just doing what you need for the application at hand? How much extra time will it take to extend the User & Group when you use it in a domain that requires more of the Entities than what you put in the library?

If you go forward with this I'd be willing to lay wager that you'll lose more than you gain.

quentin-starin
  • 26,121
  • 7
  • 68
  • 86
  • +1 Presumptions aside, a lot of important considerations in here. – Dan J Nov 02 '10 at 22:18
  • I don't wanna copy and paste the same code not because I wanna save the time doing copy and paste. It's because I don't wanna have modify the same code several places when a change request comes in. – Shuo Nov 03 '10 at 00:05
  • Let's just assume we don't need change existing applications. I'll have five new apps that will use basically the same User class whose data source is now active directory and may later be switched to a database. – Shuo Nov 03 '10 at 00:08
  • It's the change request that only pertains to *some* of the applications depending on this common component that will have the potential to bite you, and if it does you'll likely lose more than any benefit you've received. It is usually easier to deal with less dependencies than more, and that consideration often outweighs duplicating fixes to multiple copies of similar code. – quentin-starin Nov 03 '10 at 14:33
1

After some thought, I found this solution might be good.

Along with ActiveDirectoryUserRepository, I can create a internal class ProxyUser that inherits from User, which takes a reference to the ActiveDirectoryUserRepository. The ProxyUser should return the Groups property the first time it is invoked.


// User.cs
public class User
{
    public virtual string Name { get; set; }
    public virtual string DisplayName {get; set;}
    public virtual string Email { get; set; }

    public virtual IList<Group> Groups { get; set; }
}

// Group.cs
public class Group
{
    public virtual string Name { get; set; }
    public virtual string Email { get; set; }

    public virtual IList<User> Members { get; set; }
}

//  ProxyUser.cs. It should be in the same assembly as ActiveDirectoryUserRepository 

internal class ProxyUser : User
{

   private ActiveDirectoryUserRepository _repository ;
   internal ProxyUser(ActiveDirectoryUserRepository repository)
   {
       _repository = repository;
   }

   internal string IList<string> GroupNames { get; set; }

   private IList<Group> _groups;
   public override IList<Group> Groups 
   { 
      get
      {
         if (_groups == null)
         {
            if (GroupNames != null && GroupNames.Count > 0)
            {
                _groups = new List<Group>();
                foreach(string groupName in GroupNames)
                   _groups.Add(_repository.FindGroupByName(groupName);
            }
         }
         return _groups;
       }

       set
       {
           _groups = value;
       }
    }
}

// ProxyGroup.cs

internal class ProxyGroup : Group
{
    // This class should be similar to ProxyUser
}

// ActiveDirectoryUserRepository.cs

public class ActiveDirectoryUserRepository
{
    public User FindUserByName(string name)
    {
         DirectorySearcher searcher = new DirectorySearcher();

         // ...
         // set up the filter and properties of the searcher
         // ...

         Result result = searcher.FindOne();
         User user = new ProxyUser(this)
         {
             Name = result.Properties["displayName"][0],
             Email = result.Properties["email"][0],
             GroupNames = new List<string>()
         };

         foreach(string groupName in result.Properties["memberOf"])
         {
             user.GroupNames.Add(groupName);
         }

         return user;
    }

    public Group FindGroupByName(string name)
    {
        // Find the Group, which should be similar to FindUserByName
    }
}
Shuo
  • 4,749
  • 9
  • 45
  • 63