LINQ: запретить создание нового объекта (добавление к атрибуту списка, если объект существует)

#c# #linq #linq-to-objects

#c# #linq #linq-to-objects

Вопрос:

(Я новичок в C #) Я хочу создать список классов Person, используя 2 списка, список пользователей и список сообщений вот как выглядит структура классов. Проблема: при создании объекта с использованием LINQ я хочу, чтобы тот же объект person не создавался (т. е. с тем же именем, идентификатором пользователя, идентификатором). Возможно ли это вообще с помощью LINQ? (я использую 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 )
    }
}
  

Я попытался написать логику, поискать ее в Google, просмотреть другой вопрос StackOverflow, но не смог найти / понять, как подойти к этой проблеме.
Я новичок в C #, менее (20 дней опыта).

Ответ №1:

Вы можете использовать group оператор

 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 (группа по идентификатору пользователя) содержит список записей для этого пользователя, вы можете запросить его как IEnumerable
Подробнее об этом в MSDN

Или вы можете внести следующее изменение в Person конструктор и передать grp в качестве 4-го аргумента:

  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();
 }
  

Ответ №2:

Ваш запрос Linq можно упростить. Из вашего описания я понял, что вы хотите загрузить все User , выбрать их, Person а затем добавить к ним все Post объекты.

Рассмотрим следующее:

 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();
  

Во-первых, я предполагаю, что вам не нужен p.UserId в вашем Person Ctor, потому что он такой же, как u.Id .

Второе, что делает мое утверждение, — это перебирать всех пользователей и создавать из них людей. Во время этого я загружаю все Post объекты, которые имеют соответствие UserId , и назначаю их списку сообщений.

Ответ №3:

Итак, у вас есть последовательность Users и последовательность Posts . Каждый User имеет нуль или больше Posts , каждый Post — это сообщение ровно одного User , а именно пользователя, на которого UserId ссылается внешний ключ. Прямое отношение «один ко многим».

Действительно, можно использовать LINQ для получения «Пользователей с их сообщениями». Вы можете преобразовать каждого пользователя в Person и поместить проверку всех сообщений пользователя в свойство ListOfPosts .

Есть возможности для улучшения

Сначала несколько советов:

Кроме того: у вашего человека есть Id и UserId . В чем разница? Является ли идентификатор не идентификатором пользователя, из которого создан этот человек?

Если вы создаете классы, которые будут заполняться операторами LINQ, обычно проще создать (только) конструктор по умолчанию для этого класса.

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

Такой класс обычно называется POCO (обычный старый объект c #): класс, который имеет только установщики и получатели и не имеет дополнительной функциональности.

Если вы действительно не можете изменить, убедите своего руководителя проекта в том, что лучше иметь конструктор по умолчанию, вы можете смириться с этим, но убедитесь, что ваш конструктор по умолчанию, по крайней мере, создает правильный объект: зачем указывать имя, но не список записей?

Кстати, я взял на себя смелость изменить ваш ListOfPosts. Прежде всего, это список строк, а не список сообщений, более того, он не содержит сообщений, он содержит только тексты сообщений. Совет: по возможности используйте имена свойств, которые правильно описывают, что означает свойство. Таким образом, пользователям не нужно будет искать, что находится в строках, они знают, что это не сообщения, потому что сообщение не является строкой.

Кроме того: можете ли вы определить значение Post[4] ? Вы ожидаете, что пользователи будут запрашивать Pos [4]? Совет: не предоставляйте функции вызывающим вашего класса, если вы ожидаете, что пользователям эти функции не понадобятся. Это только усложнит изменение вашей внутренней структуры.

Итак, если вы не думаете, что можете объяснить функциональность списка, но вам все равно нужно все из списка, кроме индексации: Добавление / удаление / подсчет / перечисление, подумайте о создании ICollection<...> , это дает вам свободу иметь внутри другие вещи, кроме списков, например, массив, HashSet, дажеСловарь.

Вернемся к вашему вопросу

Итак, учитывая последовательности пользователей и сообщений, вы хотите создать последовательность лиц.

Всякий раз, когда у вас есть отношение «один ко многим», и вам нужны «элементы с нулевыми или более подпунктами», например «Клиенты с их заказами», «Школы с их учениками» и «Пользователи с их сообщениями», рассмотрите возможность использования одной из перегрузки Enumerable .Групповое соединение

Если вы хотите указать результат, отличный от «исходных элементов с их исходными подпунктами», используйте перегрузку с параметром 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(),
    })
  

Итак, даже если свойство PostTexts содержит список, вы можете изменить это в будущем, без необходимости изменять использование вашего оператора.