Linq - Birçok Karışıklık Seçin


81

SelectMany belgelerinden anladığım kadarıyla, 1-çok ilişkisinin (düzleştirilmiş) bir dizisini üretmek için onu kullanabiliriz.

Aşağıdaki derslerim var

  public class Customer
  {
    public int Id { get; set; }
    public string Name { get; set; }
  }

  class Order
  {
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public string Description { get; set; }
  }

Daha sonra bunları sorgu ifadesi sözdizimini kullanarak kullanmaya çalışıyorum.

  var customers = new Customer[]
  {
    new Customer() { Id=1, Name ="A"},
    new Customer() { Id=2, Name ="B"},
    new Customer() { Id=3, Name ="C"}
  };

  var orders = new Order[]
  {
    new Order { Id=1, CustomerId=1, Description="Order 1"},
    new Order { Id=2, CustomerId=1, Description="Order 2"},
    new Order { Id=3, CustomerId=1, Description="Order 3"},
    new Order { Id=4, CustomerId=1, Description="Order 4"},
    new Order { Id=5, CustomerId=2, Description="Order 5"},
    new Order { Id=6, CustomerId=2, Description="Order 6"},
    new Order { Id=7, CustomerId=3, Description="Order 7"},
    new Order { Id=8, CustomerId=3, Description="Order 8"},
    new Order { Id=9, CustomerId=3, Description="Order 9"}
  };

  var customerOrders = from c in customers
                       from o in orders
                       where o.CustomerId == c.Id
                       select new 
                              { 
                                 CustomerId = c.Id
                                 , OrderDescription = o.Description 
                              };

  foreach (var item in customerOrders)
    Console.WriteLine(item.CustomerId + ": " + item.OrderDescription);

Bu ihtiyacım olanı veriyor.

1: Order 1
1: Order 2
1: Order 3
1: Order 4
2: Order 5
2: Order 6
3: Order 7
3: Order 8
3: Order 9

Sorgu ifade sözdizimini kullanmadığınızda bunun SelectMany yöntemini kullanmaya dönüştüğünü varsayıyorum.

Her iki durumda da, SelectMany kullanarak başımı sarmaya çalışıyorum. Öyleyse, yukarıdaki sorgum, iki sınıf ve sahte veriler göz önüne alındığında SelectMany'ye çevrilmese bile, birisi bana SelectMany kullanan bir linq sorgusu sağlayabilir mi?


3
Jon Skeet'in Edulinq serisinin 41. bölümüne bakın . Sorgu ifadesi çeviri sürecini açıklar.
R. Martinho Fernandes

2
Bir düşünün, ayrıca bkz.Bölüm 9: SelectMany :)
R. Martinho Fernandes

3
John Skeet'in Edulinq serisi artık burada .
Dan Jagnow

Yanıtlar:


101

İşte kullandığınız sorgunuz SelectMany, tam olarak örneğinizden sonra modellenmiştir. Aynı çıktı!

        var customerOrders2 = customers.SelectMany(
            c => orders.Where(o => o.CustomerId == c.Id),
            (c, o) => new { CustomerId = c.Id, OrderDescription = o.Description });

İlk argüman her müşteriyi bir sipariş koleksiyonuyla eşleştirir (zaten sahip olduğunuz 'nerede' cümlesine tamamen benzer).

İkinci bağımsız değişken, eşleşen her bir çifti {(c1, o1), (c1, o2) .. (c3, o9)} yeni bir türe dönüştürür, sizin örneğinizle aynı şeyi yaptım.

Yani:

  • arg1, temel koleksiyondaki her öğeyi başka bir koleksiyonla eşler.
  • arg2 (isteğe bağlı) her çifti yeni bir türe dönüştürür

Ortaya çıkan koleksiyon, orijinal örneğinizde beklediğiniz gibi düzdür.

İkinci argümanı atlarsanız, bir müşteriyle eşleşen tüm siparişlerin bir koleksiyonuyla sonuçlanırsınız. Sadece bu, düz bir Ordernesne koleksiyonu olurdu .

Kullanmaya alışmak çok zaman alıyor, yine de bazen kafamı dolanmakta zorlanıyorum. :(


2
Cevabınız ve açıklamanız için teşekkürler. Tam da ihtiyacım olan şey buydu. Ayrıca sorum bağlamında eksiksiz bir cevap verdiğiniz için teşekkür ederim, anlaşılmasını çok daha kolay hale getiriyor.
Jackie Kirby

1
Pete aşkına, .Where () öğesini SelectMany () içine koymak neden bu kadar uzun süre benden kaçtı? Bunu işaret ettiğiniz için teşekkürler ...
Tobias J

Sadece kayıt GroupByiçin, bu özel senaryo için daha iyi bir seçenek olabilir.
Ekevoo

27

SelectMany (), Select gibi çalışır, ancak bu fazladan seçilen bir koleksiyonu düzleştirme özelliğiyle birlikte. Alt koleksiyon öğelerinin projeksiyonunu istediğinizde ve alt koleksiyonun içerdiği öğeyi umursamadığınızda kullanılmalıdır.

Örneğin, etki alanınızın şöyle göründüğünü varsayalım:

public class Customer
  {
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Order> Orders { get; set; }
  }

  class Order
  {
    public int Id { get; set; }
    public Customer Customer { get; set; }
    public string Description { get; set; }
  }

İstediğiniz listeyi almak için Linq'iniz şuna benzer:

var customerOrders = Customers
                        .SelectMany(c=>c.Orders)
                        .Select(o=> new { CustomerId = o.Customer.Id, 
                                           OrderDescription = o.Description });

... Siparişlerin düz bir şekilde toplanmasına gerek kalmadan aynı sonucu verecektir. SelectMany, her Müşterinin Sipariş koleksiyonunu alır ve bunu yineleyerek IEnumerable<Order>bir IEnumerable<Customer>.


3
"(...) ve alt koleksiyonun içerdiği öğeyi umursamayın." Düzleştirmeyi istiyorsanız ve içerdiği öğeyi önemsiyorsanız, bunun için bir SelectMany aşırı yüklemesi var :)
R. Martinho Fernandes

Cevabınız için @ Keith teşekkürler. Düz bir sipariş koleksiyonuyla nasıl kullanırım?
Jackie Kirby

Etki alanınız biraz şüpheli görünüyor. Bir Sipariş, sırayla birçok Sipariş içeren bir Müşteri mi içeriyor?
Buh Buh

@Buh Buh, hiçbir Sipariş Müşteri değil Müşteri Kimliği içermiyor.
Jackie Kirby

1
@Buh Buh - Bunu defalarca gördüm ve yaptım; sadece yukarıdan aşağıya değil, herhangi bir yönde de ilerletilebilen bir nesne grafiğiyle sonuçlanır. Grafiğinizde birden fazla "giriş noktası" varsa çok kullanışlıdır. NHibernate gibi bir ORM kullanıyorsanız, geri referansı eklemek önemsizdir çünkü alt tabloda zaten var. Sadece basamakların yukarı değil aşağı gittiğini belirterek dairesel referansı kırmanız gerekir.
KeithS

5

Bu eski bir soru olmasına rağmen, mükemmel cevapları biraz geliştireceğimi düşündüm:

SelectMany, kontrol listesinin her bir öğesi için bir liste (boş olabilir) döndürür. Bu sonuç listelerindeki her öğe, ifadelerin çıktı sırasına göre numaralandırılır ve böylece sonuçta birleştirilir. Dolayısıyla, bir 'liste -> b' listesi [] -> birleştirme -> b 'listesi.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
using System.Diagnostics;
namespace Nop.Plugin.Misc.WebServices.Test
{
    [TestClass]
    public class TestBase
    {
        [TestMethod]
        public void TestMethod1()
        {  //See result in TestExplorer - test output 
            var a = new int[]{7,8};
            var b = new int[]
                    {12,23,343,6464,232,75676,213,1232,544,86,97867,43};
            Func<int, int, bool> numberHasDigit = 
                    (number
                     , digit) => 
                         ( number.ToString().Contains(digit.ToString()) );

            Debug.WriteLine("Unfiltered: All elements of 'b' for each element of 'a'");
            foreach(var l in a.SelectMany(aa => b))
                Debug.WriteLine(l);
            Debug.WriteLine(string.Empty);
            Debug.WriteLine("Filtered:" +  
            "All elements of 'b' for each element of 'a' filtered by the 'a' element");
            foreach(var l in a.SelectMany(aa => b.Where(bb => numberHasDigit(bb, aa))))
                Debug.WriteLine(l);
        }
    }
}

1

İşte SelectMany kullanan başka bir seçenek

var customerOrders = customers.SelectMany(
  c => orders.Where(o => o.CustomerId == c.Id)
    .Select(p => new {CustomerId = c.Id, OrderDescription = p.Description}));

Entity Framework veya LINQ to Sql kullanıyorsanız ve varlıklar arasında bir ilişki (ilişki) varsa, bunu yapabilirsiniz:

var customerOrders = customers.SelectMany(
  c => c.orders
   .Select(p => new {CustomerId = c.Id, OrderDescription = p.Description}));
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.