开发者

Hierarchical layering in Linq

开发者 https://www.devze.com 2023-01-04 16:53 出处:网络
If I have a set of employee data similar to: var users = new[] { new {SupervisorId = \"CEO\", UserId = \"CEO\", UserName = \"Joe\"},

If I have a set of employee data similar to:

var users = new[] 
{
   new {SupervisorId = "CEO", UserId = "CEO", UserName = "Joe"},
   new {SupervisorId = "CEO", UserId = "CIO", UserName = "Mary"},
   new {SupervisorId = "CIO", UserId = "XDIR", UserName = "Ed"},
   new {SupervisorId = "CIO", UserId = "YDIR", UserName = "Lisa"},
   new {SupervisorId = "XDIR", UserId = "AMNGR", UserName = "Steve"},
   new {SupervisorId = "AMNGR", UserId = "ASUP", UserName = "Lesley"}
};

Would it be possible to use Linq to add hierarchical layers, in the sense that:

  • CEO = 1 (top)
  • CIO = 2 (2nd level)
  • XDIR and YDIR = 3 (3rd level)
  • AMNGR = 4 (etc)
  • ASUP = 5 (etc)

I've been able to group the employees according to SupervisorId, but not sure how to make the "level" happen.

var userGroups = from user in users
  group user by user.SupervisorId into userGroup
  select new
  {
    SupervisorId = userGroup.Key,
    Level = ??????
    Users = userGroup.ToList()
  };

  foreach (var group in userGroups)
  {
    Console.WriteLine("{0} - {1} - {2}", group.SupervisorId, group.Level, group.Users.开发者_C百科Count);
  }

Many thanks.


I would add a ranking to your linq "user object"

public class User{

  public string SupervisorId  {get;set;}
  public string UserId {get;set;}
  public string UserName   {get;set;}
  public int Level {get {  return GetRank(SupervisorId  ) ; } } 

  private int GetRank(string userId){
     if(string.IsNullOrEmpty(userId)){
         //Bad case, probably want to use a very large number
         return -1;
     }
     int level = 0;
     switch(userId){
        case "CEO":
           level  = 0;
           break;

         //insert others here

     }

  }
}

Then your Linq you would add a join.

     var userGroups = from user in users
                     join super in users on user.SupervisorId  equals super.UserId
                     group user by  user.SupervisorId into userGroup 
                     select new
    {
        SupervisorId = userGroup.Key,
        Level =  super.Level, 
        Users = userGroup.ToList()
    };


ILookup<string, User> subordLookup = users
  .ToLookup(u => u.SupervisorId);

foreach(User user in users)
{
  user.Subordinates = subordLookup[user.UserId].ToList();
}

User userHierarchy = user.Single(u => u.UserId == "CEO");

Disclaimers:

  • Does not handle multiple CEOs.
  • Preserves circular relationships.
  • Leaves orphans behind.


Is this what you are looking for?

         var levels = new[]
        {
            new { Level = 1, LevelName = "CEO" },
            new { Level = 2, LevelName = "CIO" },
            new { Level = 3, LevelName = "XDIR" },
            new { Level = 3, LevelName = "YDIR" },
            new { Level = 4, LevelName = "AMNGR" },
            new { Level = 5, LevelName = "ASUP" }
        };                        

        var userGroups = from user in users
                         join level in levels on
                         user.UserId equals level.LevelName                             
                         group new{ User = user, Level = level.Level } by new { SuperId = user.SupervisorId, Level = level.Level } into userGroup
                         select new
                            {
                                SupervisorId = userGroup.Key.SuperId,
                                Level = userGroup.Key.Level,
                                Users = userGroup.ToList()
                            };


Update

Heres one way to create a lookup table for each level. Its fairly and I dont know how it will scale. Obviously, you'll need to adapt it to pull the rows from your database.

Define a class to hold our lookup table

public class user{
    public string SupervisorId;
    public string UserId;   
    public int Level;
}

Then we get a unique list of UserId/SupervisorId combinations and loop through the list calculating the level for each combination by 'walking' up the tree.

    var uniqueusers = (new user[] 
        {
            new user {SupervisorId = "CEO", UserId = "CEO"},
            new user {SupervisorId = "CEO", UserId = "CIO"},
            new user {SupervisorId = "CIO", UserId = "XDIR"},
            new user {SupervisorId = "CIO", UserId = "YDIR"},
            new user {SupervisorId = "XDIR", UserId = "AMNGR"},
            new user {SupervisorId = "AMNGR", UserId = "ASUP"}
        }).Distinct();


        foreach (var item in uniqueusers)
        {           
            int level = 0;
            user CurrentUser = item;
            while (CurrentUser.UserId != CurrentUser.SupervisorId){
                CurrentUser = uniqueusers.Where(c => c.UserId == CurrentUser.SupervisorId).FirstOrDefault();
                level++;
            } 
            item.Level = level;             
        }

Now you can use the uniqueusers as a lookup table to determine the level for your query. eg

private int GetLevel(string userId){         
     return uniqueusers.Where(c => c.UserId == userId).FirstOrDefault().Level;    
  }

You could probably even combine this into a single step with a little effort.

0

精彩评论

暂无评论...
验证码 换一张
取 消