how can i query 2 Active Directory at the same time

Nov 11, 2013 at 9:10 PM
Edited Nov 11, 2013 at 9:20 PM
hi!

i need a way to attach the context to 2 active directory. All users are unique but they are separated in 2 AD.

What i would like is when a search for something, it trie the first one and then the second one. Do you know a way i can do this?

Presently, i was trying to make another Context that would contain 2 AD instance but i'm not sure if it's the right thing to do. I get an error that the Concat is not supported.
public class MtoContext
{
    private MtoInterneContext interneContext = new MtoInterneContext();
    private MtoExterneContext externeContext = new MtoExterneContext();

    private IEntrySet<User> _users1;
    private IEntrySet<User> _users2;

    public IEntrySet<User> Users
    {
        get
        {
            if (_users1 == null)
            {
                _users1 = interneContext.Users;
                _users2 = externeContext.Users;
            }

            return _users1.Concat(_users2) as IEntrySet<User>;
        }
    }
}    
thank you for your help!

alex
Coordinator
Nov 11, 2013 at 9:31 PM
Edited Nov 11, 2013 at 9:42 PM
Hi @skoub,

Yeah, there is no way to handle querying from multiple LDAP sources from within one DirectoryContext. But, I do think you were on the write track with the AggregateContext idea. Maybe something like the below would work for you?
    public class AggregateDirectoryContext
    {
        private readonly DirectoryContext _context1;
        private readonly DirectoryContext _context2;

        public AggregateDirectoryContext(DirectoryContext context1, DirectoryContext context2)
        {
            _context1 = context1;
            _context2 = context2;
        }

        public IEnumerable<T> ExecuteQuery<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            var result = _context1.CreateEntrySet<T>().Where(predicate).ToList();
            result.AddRange(_context2.CreateEntrySet<T>().Where(predicate));
            return result;
        }
    }

    // usage
    public class SomeotherPortionOfMyApp
    {
        public void Method()
        {
            var aggregateContext = new AggregateDirectoryContext(new DirectoryContext(), new DirectoryContext());
            var users = aggregateContext.ExecuteQuery<User>(u => u.UserName != "JimmyPop");
        }
    }
Thanks for using the framework,
Stephen
Coordinator
Nov 11, 2013 at 10:13 PM
Edited Nov 11, 2013 at 11:26 PM
There are a few other ways I can think of to handle this as well...if the above doesn't work for you.

I'm not able to test this until I have access to an Active directory. But, I can't think of a reason off the top of my head why this wouldn't work.
    public interface IAggregateQueryable<T> : IEnumerable<T>
    {
    }

    public class AggregateQueryable<T> : IAggregateQueryable<T> where T : class
    {
        private readonly IQueryable<T> _original;
        private readonly IEntrySet<T>[] _includes;

        public AggregateQueryable(IQueryable<T> original, params IEntrySet<T>[] includes)
        {
            _original = original;
            _includes = includes;
        }

        public IEnumerator<T> GetEnumerator()
        {
            // Get the aggregate of both queries
            var list = _original.ToList();

            foreach (var entrySet in _includes)
            {
                list.AddRange(entrySet.Provider.CreateQuery<T>(_original.Expression));
            }

            return list.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

    public static class IAggregateQueryableExtensions
    {
        public static IAggregateQueryable<T> Include<T>(this IQueryable<T> source, params IEntrySet<T>[] sources) where T : class
        {
            return new AggregateQueryable<T>(source, sources);
        }
    }

    public class SomeotherPortionOfMyApp
    {
        private MtoInternelContext _context1;
        private MtoExternelContext _context2;

        public void Method()
        {
            _context1 = new MtoInternelContext();
            _context2 = new MtoExternelContext();

            // Aggregate the results.
            // You would need to build your expression completely
            // before you would add the include(s)
            var users1 = _context1.Users.Where(u => u.UserName == "sbaker").Include(_context2.Users);


            // Multi-sources...
            var context3 = new MyOtherContext();
            var users2 = _context1.Users.Where(u => u.UserName == "sbaker").Include(_context2.Users, context3.Users);
        }
    }
Let me know if this helps,
Stephen
Nov 12, 2013 at 3:44 PM
thank you for the code. I have tried the first one and it seems to work good. I will try to put it async to see if i can get it faster (im totally new to async/await).

But one thing that i miss with this method is that can't build my query Dynamically. What i mean is that i can't do this (or maybe i'm wrong):
public IEnumerable<User> GetUsers()
{
    return GetUsers(null);
}

public IEnumerable<User> GetUsers(string email)
{
    var query = this.context.Users;

    if (!string.IsNullOrEmpty(email))
    {
        query = query.Where(x => x.Email == email);
    }

    return query.ToList();
}
do you think it would be hard to edit the source code to support multiple AD? i don't say that you have to do it, i can do it myself but knowing if it's doable would help me :)
Nov 12, 2013 at 6:16 PM
i think i've found a way that would be very great but we must add something in your framework. If we could implement the Concat function in your framework, we could do something like this:
public IQueryable<User> Users()
{
    var result = interneContext.CreateEntrySet<User>();
    var result2 = externeContext.CreateEntrySet<User>();

    return result.Concat(result2);
}

public void Method()
{
    var count = this.Users().Count();
}
That way, we still use the power of your framework.
Coordinator
Nov 12, 2013 at 11:41 PM
Edited Nov 12, 2013 at 11:45 PM
Hi @skoub,

A few things to mention here. Using async/await when executing your query won't make it faster. it will only execute the query on a different thread while other processing can take place. I don't think it is a good idea to allow for querying from multiple LDAP sources from within one directory context. It is a little more complex then just implementing the "Concat" method. While discussing this, I decided to create a new project http://aggregator.codeplex.com/ which will allow you to build a query and mix and match from LinqToAD, EntityFramework, LinqToSql, or any other IQueryable source. It will keep track of all the sources you specify (example below) but the query must be supported by all the sources and then once enumerated, send the created expression to all the sources and then aggregating the results.
var ef = new DbContext(); // EntityFramework
var ad = new DirectoryContext(); // LinqToAD
var ltos = new DataContext(); // LinqToSql
var objects = new List<User>() { new User { FirstName = "Stephen" }, new User { FirstName = "Tom" } }.AsQueryable(); // LinqToObjects 

// As long as the base "User" class is shared between contexts, it will allow the adding of the source.
// I will try to figure out a way to support querying from multiple sources with out that dependency in the future.
var query = ef.Users.Include(ad.Users, ltos.Users, objects).Where(u => u.FirstName == "Stephen").ToList();
So, once it is complete you can add a reference to that and your code will look like:
public IQueryable<User> Users()
{
    var result = interneContext.CreateEntrySet<User>();
    var result2 = externeContext.CreateEntrySet<User>();

    return result.Include(result2);
}

public void SomeMethod()
{
    Users().Where(u => u.UserName != null).Select(u => u.UserName);
}
Hopefully, I can have it done by tonight.

Thanks,
Stephen
Nov 13, 2013 at 12:17 AM
wow... i think it will be pretty cool if you can achieve this. Never thought you would go that far to help me out. Thank you very much for that! I really appreciate it!

about the async/await, i thought that if you send a query to multi providers, each providers could do his job side by side instead of queuing. Or maybe they already do their job side by side?

alex
Coordinator
Nov 13, 2013 at 12:35 AM
Edited Nov 13, 2013 at 12:44 AM
There are no limitations as far as how LINQ is implemented. So, I don't see how it couldn't work. I bought it up with a couple of my co-workers and they agreed that it was an interesting / cool concept. Plus, if someone using code that I've written is having problems working with the framework, even if it isn't a flaw in the architecture, I'll do what I can to help. I just wish I'd get more feed back and ratings instead of people using the code and not giving any credit back to the guys who developed the code. But, it is what it is.

Thanks,
Stephen
Coordinator
Nov 13, 2013 at 12:43 AM
Oh, and to answer your question on async/await. Yes, if each query is run asynchronously, getting all the results will take as long as the longest running query/process. So, it will take about half as long in the case where each Ad filter is executed asynchronously.

Thanks,
Stephen
Nov 13, 2013 at 1:24 AM
i'm very happy to see that my needs had the effect to pop new ideas on your side with your co-worker and build a framework that can help many people. If i can do something to help, just let me know.

When you said that you don't get much feedback and ratings, i didnt really understand since that i thought i was on github! Now that i realized that we are on codeplex, i will write something for your framework :)

alex
Coordinator
Nov 13, 2013 at 4:01 AM
I didn't mean you, I'm sorry if you thought I meant it that way. It isn't a bad thing. I just want the framework to get more use and lots of reviews and ratings. If there are good ratings, it'll appear higher in search results. But regardless, thanks for using the framework.

Stephen
Nov 13, 2013 at 2:45 PM
don't worry about that. I havent taken this personaly at all. Nevertheless, i'm this kind of person who take open source code or idea from them without leaving feedback to the owner. So i will try to change that on my side :)
Nov 20, 2013 at 5:44 PM
hi Stephen!

how is it going your the aggregate project? is there something i can do to help?

alex