简体   繁体   中英

Group records stored in one-dimensional array using LINQ

I have a situation, where I get data from the database in such a way, that everything is stored in one-dimensional array.
For example:

User table: UserId, Name
Group table: GroupId, Name
UserGroup table: UserId, GroupId

As a result of joining these tables I obtain array of the following form:

result[0] = "1" // user id
result[1] = "John Doe" // user name
result[2] = "121" // group id
result[3] = "SomeGroup" // group name
result[4] = "1" // user id
result[5] = "John Doe" // user name
result[6] = "2135" // group id
result[7] = "SomeOtherGroup" // group name

I know it's not a good solution of keeping data, but these data are coming to me from some other piece of code which I am not allowed to change, so I have to deal with it.

My questions are:

  1. Is this possible to use LINQ in order to parse this array and place data in my own objects (User class with Groups collection in it).
  2. What is other best way to handle it if not by LINQ?

Pure linq Expression :

int i = 0;
var objects = result.GroupBy(x => Math.Floor(i++ / 4.0))
            .Select(g => new { id =g.ElementAt(0), name = g.ElementAt(1), gId= g.ElementAt(2), group = g.ElementAt(3)})
            .GroupBy(x=>new {x.id, x.name}, x=>new {x.gId, x.group})
            .Select(y=>new {y.Key, groups = y.ToList()});
  • In the first GroupBy I group results in 4 elements subsets using a floor and a temporary variable.
  • Then The next Select put the resulting arrays in an anonymous type for better usability in the next steps.
  • The next GroupBy is used to group the entries by Employee. The Key will be the employee and the values will be the corresponding Groups.
  • Finaly the lase Select is used to put the GroupBy result in a better shape. I choose to put the result in an other anonymous type but You could instantiate you custom objects here and put the values in the right fields using curly brace constructor.

If your logic depends on indexes LINQ is is rarely the right tool. It results in less readable, maintainable, efficient and robust code than with plain loops.

I would use something like following to create two dictionaries representing the many to many relation. Note the for -loop which increments by 4 on every iteration since that seems to be the user-group-"package":

var userIdGroups = new Dictionary<int, HashSet<Group>>();
var groupIdUsers = new Dictionary<int, HashSet<User>>();

for(int i = 0; i < result.Length; i += 4)
{
    int id;
    if(int.TryParse(result[i], out id))
    {
        string name = result.ElementAtOrDefault(i + 1);
        if(name == null)
            continue; // end, invalid data

        User user = new User{ UserId = id, Name = name };
        string groupID = result.ElementAtOrDefault(i + 2);
        if(!int.TryParse(groupID, out id))
            continue; // end, invalid data

        name = result.ElementAtOrDefault(i + 3);
        if(name == null)
            continue; // end, invalid data

        Group group = new Group{ GroupId = id, Name = name };
        HashSet<Group> userGroups;
        HashSet<User> groupUsers;
        if (userIdGroups.TryGetValue(user.UserId, out userGroups))
            userGroups.Add(group);
        else
            userIdGroups.Add(user.UserId, new HashSet<Group>{ group });

        if (groupIdUsers.TryGetValue(group.GroupId, out groupUsers))
            groupUsers.Add(user);
        else
            groupIdUsers.Add(group.GroupId, new HashSet<User> { user });
    }
}

The result is:

  • the user-dictionary contains one user with two groups
  • the group-dictionary contains two groups which map to the same user

You have to override Equals and GetHashCode to compare the ID's:

class User
{
    public int UserId { get; set; }
    public string Name { get; set; }

    public override bool Equals(object obj)
    {
        User u2 = obj as User;
        if (u2 == null) return false;
        return UserId == u2.UserId;
    }
    public override int GetHashCode()
    {
        return UserId;
    }
}
class Group
{
    public int GroupId { get; set; }
    public string Name { get; set; }

    public override bool Equals(object obj)
    {
        Group g2 = obj as Group;
        if (g2 == null) return false;
        return GroupId == g2.GroupId;
    }
    public override int GetHashCode()
    {
        return GroupId;
    }
}

You can do it with the basic structures like loops:

void Main()
{
    var result = new string[] {"1","John Doe","2","Joes Group","3","Jack Daniel","4","Jacks Group","5","Foo Bar","6","FooBar Group",};
    List<Person> personList = new List<Person>();
    List<Group> groupList = new List<Group>();

    for(int i = 0; i < result.Length; i+=2) 
    {
        i = i + 2;
        //check if group does not already exist
        groupList.Add(new Group() {Name = result[i+1]});
    }

    for(int i = 0; i < result.Length; i+=2)
    {
        //check if person exists.
        //if person only add group to person personList.Where(x => x.Name ==result[i+1])....
        personList.Add(new Person() { Id = int.Parse(result[i]), 
                                      Name = result[i+1],
                                      Groups = new List<Group>()
                                      {
                                        groupList.FirstOrDefault (l => l.Name == result[i+3])
                                      }
                                    });
        i = i+2;    
    }

    personList.Dump();
}

public class Person
{
    public Person()
    {
        Groups = new List<Group>();
    }
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Group> Groups { get; set; }

}

public class Group
{
    public string Name { get; set; }
}
// Define other methods and classes here

Output:

在此处输入图片说明

Please take advise: this code does not contain any validation logic, or duplicate checks. You'll have to imlpement this by yourself. But before you implement something like this, I'd rather change the way you get your data delivered. this way you would deal with the root of your peroblems not with the symptoms.

i think no need to linq

//some class

    public class Result
    {    
        public string UserId {get;set;}
        public string UserName {get;set;}
        public string GroupId {get;set;}
        public string GroupName {get;set;}
        public string UserGroupUserId {get;set;}
        public string UserGroupUserName {get;set;}
        public string UserGroupId {get;set;}
        public string UserGroupGroupId {get;set;}

    }

// your array

    private void Form1_Load(object sender, EventArgs e)
     {
        string[] result = new string[8];
        result[0] = "1";
        result[1] = "John Doe";
        result[2] = "121";
        result[3] = "SomeGroup";
        result[4] = "1";
        result[5] = "John Doe";
        result[6] = "2135";
        result[7] = "SomeOtherGroup";



        Result r = CastResult(result);


    }

// simple cast array to some type

    public Result CastResult(string[] array)
    {
        return new Result() { UserId=array[0], UserName=array[1], GroupId=array[2], GroupName=array[3], UserGroupUserId=array[4], UserGroupUserName=array[5] , UserGroupId=array[6], UserGroupGroupId=array[7] };
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM