จัดกลุ่มตามหลายคอลัมน์


967

ฉันจะทำอย่างไรโดย GroupBy หลายคอลัมน์ใน LINQ

สิ่งที่คล้ายกับสิ่งนี้ใน SQL:

SELECT * FROM <TableName> GROUP BY <Column1>,<Column2>

ฉันจะแปลงให้เป็น LINQ ได้อย่างไร:

QuantityBreakdown
(
    MaterialID int,
    ProductID int,
    Quantity float
)

INSERT INTO @QuantityBreakdown (MaterialID, ProductID, Quantity)
SELECT MaterialID, ProductID, SUM(Quantity)
FROM @Transactions
GROUP BY MaterialID, ProductID

คำตอบ:


1212

ใช้ประเภทที่ไม่ระบุชื่อ

เช่น

group x by new { x.Column1, x.Column2 }

29
หากคุณยังใหม่ต่อการจัดกลุ่มด้วยประเภทที่ไม่ระบุชื่อการใช้คำหลัก 'ใหม่' ในตัวอย่างนี้จะใช้เวทมนตร์
คริส

8
ในกรณีที่ mvc ด้วย nHibernate ได้รับข้อผิดพลาดสำหรับปัญหา dll ปัญหาได้รับการแก้ไขโดย GroupBy (x => ใหม่ {x.Column1, x.Column2}, (คีย์, กลุ่ม) => ใหม่ {Key1 = key.Column1, Key2 = key.Column2, ผลลัพธ์ = group.ToList ()}
มิลาน

ฉันคิดว่าในกรณีนี้วัตถุใหม่จะถูกเปรียบเทียบโดยการอ้างอิงดังนั้นจึงไม่มีการจับคู่ - ไม่มีการจับกลุ่ม
HoGo

4
@HoGo วัตถุพิมพ์ที่ไม่ระบุชื่อใช้วิธีการ Equals และ GetHashCode ของตนเองซึ่งใช้เมื่อจัดกลุ่มวัตถุ
Byron Carasco

บิตยากที่จะเห็นโครงสร้างข้อมูลผลลัพธ์เมื่อคุณใหม่กับ Linq สิ่งนี้สร้างการจัดกลุ่มที่มีการใช้ประเภทที่ไม่ระบุชื่อเป็นคีย์หรือไม่
Jacques

760

ตัวอย่างขั้นตอน

.GroupBy(x => new { x.Column1, x.Column2 })

วัตถุชนิดใดที่ส่งคืน
mggSoft

5
@MGG_Soft เป็นประเภทไม่ระบุตัวตน
Alex

รหัสนี้ใช้ไม่ได้สำหรับฉัน: "ตัวระบุชนิดไม่ระบุชื่อไม่ถูกต้อง"
thalesfc

19
@Tom นี้ควรทำงานอย่างที่มันเป็น เมื่อคุณข้ามการตั้งชื่อเขตข้อมูลของประเภทที่ไม่ระบุชื่อ C # สมมติว่าคุณต้องการใช้ชื่อของคุณสมบัติ / ฟิลด์ที่เข้าถึงได้ในที่สุดจากการฉาย (ตัวอย่างของคุณเทียบเท่ากับ Mo0gles ')
Chris Pfohl

1
พบคำตอบของฉัน ฉันต้องการกำหนดเอนทิตีใหม่ (MyViewEntity) ที่มีคุณสมบัติ Column1 และ Column2 และชนิดส่งคืนคือ: IEnumerable <IGrouping <MyViewEntity, MyEntity >> และการจัดกลุ่มรหัส snip คือ: MyEntityList.GroupBy (myEntity => newEViewEntity Column1, Column2 = myEntity.Column2});
Amir Chatrbahr

467

ตกลงได้สิ่งนี้เป็น:

var query = (from t in Transactions
             group t by new {t.MaterialID, t.ProductID}
             into grp
                    select new
                    {
                        grp.Key.MaterialID,
                        grp.Key.ProductID,
                        Quantity = grp.Sum(t => t.Quantity)
                    }).ToList();

75
+1 - ขอบคุณสำหรับตัวอย่างที่ครอบคลุม ตัวอย่างคำตอบอื่น ๆ นั้นสั้นเกินไปและไม่มีบริบท นอกจากนี้คุณแสดงฟังก์ชั่นรวม (รวมในกรณีนี้) มีประโยชน์มาก ฉันพบว่าการใช้ฟังก์ชั่นรวม (เช่น MAX, MIN, SUM, ฯลฯ ) เคียงข้างกันกับการจัดกลุ่มให้เป็นสถานการณ์ทั่วไป
barrypicker

ที่นี่: stackoverflow.com/questions/14189537/…จะแสดงสำหรับตารางข้อมูลเมื่อจัดกลุ่มจะขึ้นอยู่กับคอลัมน์เดียวที่มีชื่อเป็นที่รู้จัก แต่จะทำอย่างไรถ้าคอลัมน์ที่จะจัดกลุ่มจะทำ จะต้องมีการสร้างแบบไดนามิก?
bg

สิ่งนี้มีประโยชน์มากในการทำความเข้าใจแนวคิดของการจัดกลุ่มและการใช้การรวมตัวกับมัน
rajibdotnet

1
ตัวอย่างที่ดี ... สิ่งที่ฉันกำลังมองหา ฉันยังต้องการการรวมดังนั้นนี่คือคำตอบที่สมบูรณ์แบบแม้ว่าฉันกำลังมองหาแลมบ์ดาฉันได้รับเพียงพอจากมันเพื่อแก้ปัญหาความต้องการของฉัน
Kevbo

153

สำหรับกลุ่มแยกตามหลายคอลัมน์ลองใช้แทน ...

GroupBy(x=> new { x.Column1, x.Column2 }, (key, group) => new 
{ 
  Key1 = key.Column1,
  Key2 = key.Column2,
  Result = group.ToList() 
});

วิธีเดียวกับที่คุณสามารถเพิ่ม Column3, Column4 เป็นต้น


4
นั่นเป็นประโยชน์มากและควรจะได้ upvotes มากขึ้น! Resultมีชุดข้อมูลทั้งหมดที่เชื่อมโยงกับคอลัมน์ทั้งหมด ขอบคุณมาก!
j00hi

1
หมายเหตุ: ฉันต้องใช้. AsEnumerable () แทนที่จะเป็น ToList ()
GMan

1
เยี่ยมมากขอบคุณสำหรับสิ่งนี้ นี่คือตัวอย่างของฉัน โปรดทราบว่า GetFees ส่งคืน IQFeryable <Fee> RegistryAccountDA.GetFees (registryAccountId, fromDate, toDate) .GroupBy (x => ใหม่ {x.AccountId, x.FeeName}, (คีย์, กลุ่ม) => ใหม่ {AccountId = key.AccountId , FeeName = key.FeeName, AppliedFee = group.Sum (x => x.AppliedFee) ?? 0M}) ToList ();
Craig B

เป็นไปได้ไหมที่จะได้คอลัมน์อื่น ๆ จากการสืบค้นนี้ซึ่งไม่ได้ถูกจัดกลุ่ม? หากมีอาร์เรย์ของวัตถุฉันต้องการรับวัตถุนี้จัดกลุ่มตามสองคอลัมน์ แต่รับคุณสมบัติทั้งหมดจากวัตถุไม่ใช่แค่สองคอลัมน์เหล่านั้น
FrenkyB

30

ตั้งแต่ C # 7 คุณยังสามารถใช้สิ่งอันดับคุณค่า:

group x by (x.Column1, x.Column2)

หรือ

.GroupBy(x => (x.Column1, x.Column2))

2
ตอนนี้ฉันคิดว่าคุณขาดอะไรพิเศษ) คุณไม่ได้ปิด ()
StuiterSlurf

ฉันได้เพิ่มมัน
นาธาน Tregillus

2
.GroupBy (x => ใหม่ {x.Column1, x.Column2})
Zahidul Islam Jamy

19

C # 7.1 หรือสูงกว่าการใช้TuplesและInferred tuple element names(ปัจจุบันมันทำงานได้เฉพาะกับlinq to objectsและมันก็ไม่ได้รับการสนับสนุนเมื่อต้นไม้จะต้องแสดงออกเช่นsomeIQueryable.GroupBy(...). ปัญหา Github ):

// declarative query syntax
var result = 
    from x in inMemoryTable
    group x by (x.Column1, x.Column2) into g
    select (g.Key.Column1, g.Key.Column2, QuantitySum: g.Sum(x => x.Quantity));

// or method syntax
var result2 = inMemoryTable.GroupBy(x => (x.Column1, x.Column2))
    .Select(g => (g.Key.Column1, g.Key.Column2, QuantitySum: g.Sum(x => x.Quantity)));

C # 3 ขึ้นไปโดยใช้anonymous types:

// declarative query syntax
var result3 = 
    from x in table
    group x by new { x.Column1, x.Column2 } into g
    select new { g.Key.Column1, g.Key.Column2, QuantitySum = g.Sum(x => x.Quantity) };

// or method syntax
var result4 = table.GroupBy(x => new { x.Column1, x.Column2 })
    .Select(g => 
      new { g.Key.Column1, g.Key.Column2 , QuantitySum= g.Sum(x => x.Quantity) });

18

คุณยังสามารถใช้ Tuple <> สำหรับการจัดกลุ่มที่พิมพ์ได้อย่างมาก

from grouping in list.GroupBy(x => new Tuple<string,string,string>(x.Person.LastName,x.Person.FirstName,x.Person.MiddleName))
select new SummaryItem
{
    LastName = grouping.Key.Item1,
    FirstName = grouping.Key.Item2,
    MiddleName = grouping.Key.Item3,
    DayCount = grouping.Count(), 
    AmountBilled = grouping.Sum(x => x.Rate),
}

4
หมายเหตุ: การสร้าง Tuple ใหม่ไม่ได้รับการสนับสนุนใน Linq To Entities
foolmoron

8

แม้ว่าคำถามนี้จะถามเกี่ยวกับคุณสมบัติของกลุ่มตามคลาส แต่ถ้าคุณต้องการจัดกลุ่มตามคอลัมน์ต่างๆกับวัตถุ ADO (เช่น DataTable) คุณต้องกำหนดรายการ "ใหม่" ให้กับตัวแปร:

EnumerableRowCollection<DataRow> ClientProfiles = CurrentProfiles.AsEnumerable()
                        .Where(x => CheckProfileTypes.Contains(x.Field<object>(ProfileTypeField).ToString()));
// do other stuff, then check for dups...
                    var Dups = ClientProfiles.AsParallel()
                        .GroupBy(x => new { InterfaceID = x.Field<object>(InterfaceField).ToString(), ProfileType = x.Field<object>(ProfileTypeField).ToString() })
                        .Where(z => z.Count() > 1)
                        .Select(z => z);

1
ฉันไม่สามารถทำแบบสอบถาม Linq "กลุ่ม c โดยใหม่ {c.Field <String> (" ชื่อ "), c.Field <String> (" CIF ")}" และคุณช่วยฉันได้มาก !! ข้อความค้นหาสุดท้ายคือ: "กลุ่ม c โดยใหม่ {titulo = c.Field <String> (" Title "), cif = c.Field <String> (" CIF ")}"
netadictos


4
.GroupBy(x => x.Column1 + " " + x.Column2)

บวกกับการนี้ยังช่วยในการจัดกลุ่มโดยจำนวนแบบไดนามิกของคุณสมบัติ:Linq.Enumerable.Aggregate() propertyValues.Aggregate((current, next) => current + " " + next)
Kai Hartmann

3
นี่เป็นคำตอบที่ดีกว่าใคร ๆ ก็ให้เครดิต มันอาจเป็นปัญหาได้ถ้ามีอินสแตนซ์ของชุดค่าผสมที่เพิ่มคอลัมน์ 1 ลงในคอลัมน์ 2 จะเท่ากับสิ่งเดียวกันสำหรับสถานการณ์ที่คอลัมน์ 1 แตกต่างกัน ("ab" "cde" จะตรงกับ "abc" "de") ที่กล่าวมานี้เป็นวิธีแก้ปัญหาที่ยอดเยี่ยมหากคุณไม่สามารถใช้งาน dynamic type ได้เพราะคุณกำลังสร้าง lambdas ไว้หลังกลุ่มโดยการแสดงออกแยกจากกัน
Brandon Barkley

3
"ab" "cde" จริง ๆ แล้วไม่ควรตรงกับ "abc" "de" ดังนั้นจึงเว้นว่างไว้
Kai Hartmann

1
แล้ว "abc de" "" และ "abc" "de" ล่ะ?
AlbertK

@ AlbertK ที่ไม่ทำงานฉันเดา
Kai Hartmann



1

สิ่งที่ควรทราบคือคุณต้องส่งวัตถุสำหรับนิพจน์แลมบ์ดาและไม่สามารถใช้อินสแตนซ์สำหรับชั้นเรียนได้

ตัวอย่าง:

public class Key
{
    public string Prop1 { get; set; }

    public string Prop2 { get; set; }
}

นี้จะรวบรวม แต่จะสร้างหนึ่งที่สำคัญต่อวงจร

var groupedCycles = cycles.GroupBy(x => new Key
{ 
  Prop1 = x.Column1, 
  Prop2 = x.Column2 
})

หากคุณไม่ต้องการตั้งชื่อคุณสมบัติหลักแล้วเรียกคืนคุณสมบัติเหล่านั้นคุณสามารถทำได้เช่นนี้แทน สิ่งนี้จะGroupByถูกต้องและให้คุณสมบัติหลักแก่คุณ

var groupedCycles = cycles.GroupBy(x => new 
{ 
  Prop1 = x.Column1, 
  Prop2= x.Column2 
})

foreach (var groupedCycle in groupedCycles)
{
    var key = new Key();
    key.Prop1 = groupedCycle.Key.Prop1;
    key.Prop2 = groupedCycle.Key.Prop2;
}

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.