簡體   English   中英

C# LINQ 展平父列表和兩個嵌套的子列表並將其填充到 datagridview

[英]C# LINQ to flatten parent list and two nested children lists and populate it to datagridview

我有這個代碼,我想填充到 windows 中的數據網格視圖中,形成 c#

   public class Person
   {
       public string Nom { get; set; }
       public string Prenom { get; set; }
   }

    public class Phone
    {
        public int PhoneId { get; set; }
        public string PhoneNumber { get; set; }
    }

    public class Email
    {
        public int EmailId { get; set; }
        public string EmailAddress { get; set; }
    }

    public class Contacts
    {
        public int ContactId { get; set; }
        public Person Contact { get; set; }
        public List<Phone> Telephone { get; set; } 
        public List<Email> Emails { get; set; }

    }

我有這段代碼可以正常工作,但我也無法讓它工作以顯示電子郵件

    var result = c.SelectMany(x => x.Telephone, (x, y) => 
new { x.ContactId, x.Contact.Nom, x.Contact.Prenom, y.PhoneNumber }).ToList();

dataGridView1.DataSource = result;

這是我所擁有的圖像

使用List<myClass>作為網格的DataSource的問題是網格不會顯示myClass的 collections 屬性。 就像TelephoneEmails列表一樣。 它將顯示 Class,但是,它將默認並使用類ToString方法並將其顯示到“單個”列中。

In your example, using the Contacts Class as a List<Contacts> as a DataSource to the grid, I am sure you are aware that neither the Person object is displayed properly since it is a Class nor is the Phone and Email objects displayed because they是 collections 的對象。

在此示例中, Person object 可能在網格中顯示為一列,但是,它將顯示類似...“SolutionName.Person”的內容。 如果Person有一個重寫的ToString方法,那么它將顯示該方法返回的內容。 但是,即使使用重寫的ToString方法,它也不會將Person object “拆分”為兩列……一列用於Nom ,另一列用於Prenom ……您的代碼需要這樣做。

在電話號碼和電子郵件的情況下……不會為這些屬性生成列,因為它們是“集合”屬性,並且網格不知道如何將“多個”項目放入單個單元格中。 這就是他們被忽略的原因。

此外,正如我所評論的,“重復”x 行的NomPrenom數據似乎是一個糟糕的 UI 想法。 IMO,每個Person一行,如果該人有多個電話號碼和電子郵件,則添加所有電話號碼和電子郵件,以便它們位於一個單元格中。 這將刪除冗余數據重復。

那么,鑒於此……我們如何“展平”數據,以便我們可以讓兩個Person屬性顯示在兩個不同的列中,並且在一個單元格中同時擁有所有電話號碼和在一個單元格中的所有 email 地址?

下面是一種可能的解決方案。

首先,我建議您簡單地將這些屬性“添加”到Contacts class。 Contact class 中,我們將向 class 添加屬性,這些屬性僅返回PersonNomPrenom屬性。

這將解決在網格中將NomPrenom屬性顯示為兩個不同列的問題。 但是,我們仍然存在PhoneNumbersEmailAddresses.

在這種情況下,一種解決方案是向Contacts class 添加一個string屬性,並讓它返回一個包含所有電話號碼的字符串和另一個將所有Emails作為單個字符串返回的string屬性string. 完成此操作后,網格將顯示這些屬性,因為它是單個字符串而不是字符串的“集合”。

因此,需要對Contacts class 進行一些更改。 以下是上述內容的示例。 請注意,我將 class 名稱更改為Contact而不是Contacts ,並且我將Person屬性名稱更改為ThePerson而不是Contact. 因此,您需要相應地調整代碼。

這個更新的Contact class 可能看起來像……

public class Contact {
  public int ContactId { get; set; }
  public Person ThePerson { get; set; }
  public List<Phone> Telephone { get; set; }
  public List<Email> Emails { get; set; }

  public string PhoneNumbers {
    get {
      if (Telephone == null) {
        return "";
      }
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < Telephone.Count; i++) {
        sb.Append(Telephone[i].PhoneNumber.ToString());
        if (i < Telephone.Count - 1) {
          sb.AppendLine();
        }
      }
      return sb.ToString();
    }
  }

  public string EmailAddresses {
    get {
      if (Emails == null) {
        return "";
      }
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < Emails.Count; i++) {
        sb.Append(Emails[i].EmailAddress.ToString());
        if (i < Emails.Count - 1) {
          sb.AppendLine();
        }
      }
      return sb.ToString();
    }
  }

  public string Nom {
    get {
      if (ThePerson == null) {
        return "";
      }
      return ThePerson.Nom;
    }
  }

  public string Prenom {
    get {
      if (ThePerson == null) {
        return "";
      }
      return ThePerson.Prenom;
    }
  }
}

如前所述,我們添加了兩個名為NomPrenom的屬性,它們只是從ThePerson object 中返回這些屬性。 這將為這兩個屬性創建一個列。

接下來,我們添加了PhoneNumbersEmailAddresses屬性。 在這種情況下,我們需要遍歷所有電話號碼並將每個電話號碼添加到單個字符串中。 在這種情況下,每個電話號碼將位於一行,因此我們需要調整網格行高以適應有多少行。 否則,某些電話號碼可能會在單元格顯示中被截斷。 同樣的想法也用於 Email 地址。

接下來,我們需要手動將列添加到網格中。 然后告訴網格不要自動生成列,因為我們顯然已經添加了它們。 每列中的關鍵屬性將是它的DataPropertyName 此屬性告訴網格在給定DataSource的列中顯示“哪個”屬性。 在這種情況下,我們要將第一列DataPropertyName設置為ContactID ,第二列設置為Nom ,第三列設置為Prenom ,第四列設置為PhoneNumbers ,最后將最后一列設置為EmaillAddresses 為了幫助創建一個方法來返回具有給定屬性的列......類似於......

private DataGridViewColumn GetDGVCol(string colName, string dataPropertyName, string headerText) {
  DataGridViewTextBoxColumn col = new DataGridViewTextBoxColumn();
  col.Name = colName;
  col.DataPropertyName = dataPropertyName;
  col.HeaderText = headerText;
  return col;
}

為了在這個例子中使用上面的幫助器,我創建了一個額外的方法,按照描述將列添加到網格中。

private void AddDGV_Columns() {
  dataGridView1.Columns.Add(GetDGVCol("ContactID", "ContactID", "ContactID"));
  dataGridView1.Columns.Add(GetDGVCol("Nom", "Nom", "Nom"));
  dataGridView1.Columns.Add(GetDGVCol("Prenom", "Prenom", "Prenom"));
  dataGridView1.Columns.Add(GetDGVCol("PhoneNumbers", "PhoneNumbers", "PhoneNumbers"));
  dataGridView1.Columns.Add(GetDGVCol("EmailAddresses", "EmailAddresses", "EmailAddresses"));
  dataGridView1.DefaultCellStyle.WrapMode = DataGridViewTriState.True;
  dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
}

上面我們將所有列與Contacts class 中的一個屬性配對,然后在PhoneNumersEmailAddresses屬性中設置單元格環繞模式以容納多行。 除了自動調整列寬以顯示所有數據。

為了做一個完整的例子,下面的代碼應該演示上面描述的內容。 注意,我已經更改了一些 class 名稱和屬性名稱,您需要調整您的代碼以適合我的或更改我的代碼以適合您的。

在此處輸入圖像描述

List<Contact> Contacts;

public Form1() {
  InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e) {
  Contacts = GetContacts();
  AddDGV_Columns();
  dataGridView1.AutoGenerateColumns = false;
  dataGridView1.DataSource = Contacts;
}

private void AddDGV_Columns() {
  dataGridView1.Columns.Add(GetDGVCol("ContactID", "ContactID", "ContactID"));
  dataGridView1.Columns.Add(GetDGVCol("Nom", "Nom", "Nom"));
  dataGridView1.Columns.Add(GetDGVCol("Prenom", "Prenom", "Prenom"));
  dataGridView1.Columns.Add(GetDGVCol("PhoneNumbers", "PhoneNumbers", "PhoneNumbers"));
  dataGridView1.Columns.Add(GetDGVCol("EmailAddresses", "EmailAddresses", "EmailAddresses"));
  dataGridView1.DefaultCellStyle.WrapMode = DataGridViewTriState.True;
  dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells;
  dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
}

private DataGridViewColumn GetDGVCol(string colName, string dataPropertyName, string headerText) {
  DataGridViewTextBoxColumn col = new DataGridViewTextBoxColumn();
  col.Name = colName;
  col.DataPropertyName = dataPropertyName;
  col.HeaderText = headerText;
  return col;
}



private List<Contact> GetContacts() {
  List<Contact> Contacts = new List<Contact>();
  List<Phone> PhoneNumbers = new List<Phone>();
  Phone pn = new Phone { PhoneId = 1, PhoneNumber = "111-11-11" };
  PhoneNumbers.Add(pn);
  pn = new Phone { PhoneId = 2, PhoneNumber = "222-22-22" };
  PhoneNumbers.Add(pn);
  pn = new Phone { PhoneId = 3, PhoneNumber = "222-22-22" };
  PhoneNumbers.Add(pn);
  Person = new Person { Nom = "Jeff", Prenom = "Prosper" };
  List<Email> emails = new List<Email>();
  Email em = new Email { EmailId = 1, EmailAddress = "email1@someISP.com" };
  emails.Add(em);
  em = new Email { EmailId = 2, EmailAddress = "email2@someISP.com" };
  emails.Add(em);
  Contact newC = new Contact { ContactId = 1, Telephone = PhoneNumbers, Emails = emails, ThePerson = person };
  Contacts.Add(newC);
  person = new Person { Nom = "Jimmy", Prenom = "Gean" };
  PhoneNumbers = new List<Phone>();
  pn = new Phone { PhoneId = 1, PhoneNumber = "333-33-33" };
  PhoneNumbers.Add(pn);
  emails = new List<Email>();
  em = new Email { EmailId = 1, EmailAddress = "emailXX1@someISP.com" };
  emails.Add(em);
  em = new Email { EmailId = 2, EmailAddress = "emailXX2@someISP.com" };
  emails.Add(em);
  newC = new Contact { ContactId = 2, Telephone = PhoneNumbers, Emails = emails, ThePerson = person };
  Contacts.Add(newC);
  return Contacts;
}

根據 Enigmativity 的評論進行編輯。

正如@Enigmativity 所指出的,可以使用如下更簡潔的方法。 您將在下面使用這些屬性,而不是上面的屬性。 謝謝,謎題

public string PhoneNumbers => Telephone == null ? "" : String.Join(Environment.NewLine, Telephone.Select(x => x.PhoneNumber));

public string EmailAddresses => Emails == null ? "" : String.Join(Environment.NewLine, Emails.Select(x => x.EmailAddress));

public string Nom => ThePerson == null ? "" : ThePerson.Nom;

public string Prenom => ThePerson == null ? "" : ThePerson.Prenom;

我希望這是有道理的並有所幫助。

從你的意思來看,它可以像下面這樣實現:

var result = c.SelectMany(
    // Here we merge phone and email together, give null if no value for either.
    x => Enumerable.Range(0, new[] { x.Emails.Count, x.Telephone.Count }.Max())
        .Select(n => new { Phone = x.Telephone.ElementAtOrDefault(n), Email = x.Emails.ElementAtOrDefault(n) }), 
    // Select phone and email at the same row.
    (x, y) => new { x.ContactId, x.Contact.Nom, x.Contact.Prenom, y.Phone?.PhoneNumber, y.Email?.EmailAddress })
.ToList();

dataGridView1.DataSource = result;

一種方法是通過查詢語法Email枚舉上執行類似的SelectMany ,然后加入適用的標准(例如,相同的聯系人標識符)

var phoneNumbers = c.SelectMany(c => c.Telephone, (contact, phone) => new { contact.ContactId, contact.Contact.Nom, contact.Contact.Prenom, phone.PhoneNumber });
var result = from phone in phoneNumbers
             // return back join condition (e.g, ContactId) and email address
             from email in c.SelectMany(c => c.Emails, (contact, email) => new { contact.ContactId, email.EmailAddress } )
             where email.ContactId == phone.ContactId
             select new { phone.ContactId, phone.Nom, phone.Prenom, email.EmailAddress, phone.PhoneNumber };

emailGrid.DataSource = result.ToList();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM