0

( I am a beginner with C# ) I wants to create a list of Person class using 2 lists, List of Users and List of Post here is how the class structure looks. Problem : while Creating Object using LINQ, i want that same person object is not created ( ie. with same name, userId, Id ). Is it even Possible using LINQ ? ( i am using System.linq )

class User
{
    public string Id                { get; set; }
    public string Name              { get; set; }
}

class Post
{
    public string Id                { get; set; }
    public string UserId            { get; set; }
    public string Text              { get; set; }
}
class Person
{
    public string Name              { get; set; }
    public string Id                { get; set; }
    public string UserId            { get; set; }
    public List<string> listOfPosts { get; set; }
    public Person(string Id , string Name , string UserId)
    {
        this.Name = Name;
        this.Id = Id;
        this.UserId = Id;
    }
}

class Test
{
    static void Main()
    {
        
        List<User> userList = UserRepository.GetUsers(); // sample data
        List<Post> postList = PostRepository.GetPosts(); // sample data
      /*
        * i want to create List of Person where person will contain UserId, ID,
        * name , listOfPost(list<string> - containg all the post of user)
        * this is the code is wrote for creating new person, but how will i populate 
        *    person's listOfPost ? 
        *    what changes do i need to make in person class ? 
        *    what changes should i do so that same person object is not created (same userId, Id, name) and contins List of post?
        *
        * one approach what i thought is - to remove duplicates by merging objects using loops.
        *    can i do this using LINQ ? 
        *    Or Is it even possible ? 
        *    
        */
        List<Person> personList = (from u in userList
                                    from p in postList
                                    where u.Id == p.UserId
                                    select (new Person(u.Id, u.Name, p.UserId))
                                    ).ToList<Person>();   
              // if i go by this logic then this creates duplicate objects ( same name, id, UserId , but with diff text ), 
             // ( after modifying constructor and passing p.Text )
    }
}

I tried writing logic, googling it up, looking over other stackOverflow question, but was not able to find/understand how to approach this problem. I am a beginner with C#, less than (20 days of exp).

3 Answers 3

1

You can use a group statement

List<Person> personList = (from u in userList
                           from grp in from p in postList
                                       group p by p.UserId
                           where u.Id == grp.Key
                           select new Person(u.Id, u.Name, grp.Key)
                           {
                               listOfPosts = grp.Select(x => x.Text).ToList()
                           }).ToList<Person>();

grp (a group by UserId) contains a list of post for that user, you can query it as IEnumerable More about it in MSDN

Or you can do the following change to Person constructor and pass grp as 4th argument:

 public Person(string Id , string Name , string UserId, IEnumerable<Post> posts)
 {
     this.Name = Name;
     this.Id = Id;
     this.UserId = Id;
     this.listOfPosts = posts.Select(p => p.Text).ToList();
 }
Sign up to request clarification or add additional context in comments.

Comments

1

Your Linq Query can be simplified. What I get from your description is, that you want to load all User, select them into Person and then add all the Post objects to them.

Consider the following:

var persons = users.Select(u => 
{
   var person = new Person(u.Name, u.Id);
   person.Posts = posts.Where(p => p.UserId == u.Id).ToList();
   return person;
}).ToList();

First I'm assuming you don't need the p.UserId in your Person Ctor because it is the same as the u.Id.

Second what my statement does is to go through all the users and make persons out of them. During that I load all the Post objects that have a matching UserIdand assign them to the Posts List.

Comments

1

So you have a sequence of Users and a sequence of Posts. Every User has zero or more Posts, every Post is the Post of exactly one User, namely the User that the foreign key UserId refers to. A straightforward one-to-many relation.

Indeed, it is possible to use LINQ to get "Users with their Posts". You can convert each User in a Person, and put the test of all Posts of the User into property ListOfPosts.

There is room for improvement

First some advices:

Furthermore: your Person has an Id and a UserId. What is the difference? Is the Id not the Id of the User that this Person is created from?

If you create classes to be filled by LINQ statements, it is usually easier to make (only) a default constructor for this class.

class Person
{
    public string Id { get; set; }
    public string Name{ get; set; }
    public ICollection<string> PostTexts { get; set; }
}

Such a class is usually called a POCO (plain old c# object): a class that has only setters and getters, and no added functionality.

If you really can't change convince your project leader that it is better to have a default constructor, you can live with it, but make sure that your non-default constructor at least constructs a proper object: why oblige a name, but not a list of Posts?

By the way, I took the freedom to change your ListOfPosts. First of all, it is a list of strings, not a list of posts, furthermore, it doesn't contain the posts, it only contains the texts of the posts. Advice: whenever possible, use property names that properly describes what the property means. This way uses won't have to look up what is in the strings, they know it isn't Posts, because a Post is not a string.

Furthermore: can you define the value of Post[4]? Do you expect that users will ask for Pos[4]? Advice: don't expose functions to callers of your class if you expect that users won't need those functions. It will only make it harder to change your internal structure.

So if you don't think you can explain list functionality, but you still want everything from a List, except the indexing: Add / Remove / Count / Enumerate, consider to create an ICollection<...>, this give you the freedom to internally have other things than Lists, for instance an array, HashSet, even a Dictionary.

Back to your question

So, given sequences of Users and Posts, you want to create a sequence of Persons.

Whenever you have a one-to-many relation, and you want "items with their zero or more sub-items", like "Customers with their Orders", "Schools with their Students" and "Users with their Posts", consider to use one of the overload of Enumerable.GroupJoin

If you want to specify a different result than "original items with their original sub-items", use the overload with a parameter resultSelector:

IEnumerable<User> users = ...
IEnumerable<Post> posts = ...
var persons = users.GroupJoin(posts  // GroupJoin users and posts

    user => user.Id,                 // from every user take the Id
    post => post.UserId,             // from every Post take the foreign key UserId

    // parameter resultSelector:
    // From every User, with his zero or more Posts make one Person
    (user, postsOfThisUser) => new Person
    {
        Id = user.Id,
        Name = user.Name,

        PostTexts = postsOfThisUser.Select(post => post.Text).ToList(),
    })

So, even though property PostTexts contains a List, you are free to change this in future, without having to change usage of your statement.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.