LoginSignup
7
6

More than 5 years have passed since last update.

LINQで複数のプロパティをキーに重複を除去&子要素の先頭を取得

Last updated at Posted at 2016-09-18

複数プロパティをキーに重複を除去

例として、以下のような「商品」クラスがあるとして

SalesItem.cs
    /// <summary>
    /// 商品
    /// </summary>
    public class SalesItem
    {
        /// <summary>
        /// ID
        /// </summary>
        [Key, Column(TypeName = "int")]
        public int Id { get; set; }

        /// <summary>
        /// 商品コード
        /// </summary>
        [Column(TypeName = "nvarchar")]
        public string SalesItemCode { get; set; }

        /// <summary>
        /// 商品名
        /// </summary>
        [Column(TypeName = "nvarchar")]
        public string SalesItemName { get; set; }
    }

商品コードと商品名で重複を除去して集計するには以下のようになる。

test.linq
// LINQPadでLanguageにC# Expressionを選んでいる場合の例。
SalesItems.GroupBy(s => new { s.SalesItemCode, s.SalesItemName }).Select(g => g.FirstOrDefault()).ToList().Dump() // LINQPad上で結果を見る場合は .Dump() を追加

なんだか若干分かりにくいが、上記のようにGroupBy、Select、FirstOrDefaultを組み合わせるとできる。
どうやらIGroupingインターフェイスをFirstOrDefaultすると最初のデータが取得されるらしい。
LINQ to ObjectsでもLINQ to EntitiesでもOK。
ちなみにEntity Frameworkの場合、LINQPadでSQLを確認すると以下のようなSQLが発行されている。

発行されるsql.sql
SELECT 
    [Limit1].[SalesItemId] AS [SalesItemId], 
    [Limit1].[SalesItemCode] AS [SalesItemCode], 
    [Limit1].[SalesItemName] AS [SalesItemName]
    FROM   (SELECT DISTINCT 
        [Extent1].[SalesItemCode] AS [SalesItemCode], 
        [Extent1].[SalesItemName] AS [SalesItemName]
        FROM [dbo].[SalesItems] AS [Extent1] ) AS [Distinct1]
    OUTER APPLY  (SELECT TOP (1) 
        [Extent2].[SalesItemId] AS [SalesItemId], 
        [Extent2].[SalesItemCode] AS [SalesItemCode], 
        [Extent2].[SalesItemName] AS [SalesItemName]
        FROM [dbo].[SalesItems] AS [Extent2]
        WHERE ([Distinct1].[SalesItemCode] = [Extent2].[SalesItemCode]) AND ([Distinct1].[SalesItemName] = [Extent2].[SalesItemName]) ) AS [Limit1]

※DISTINCTしたテーブルをOUTER APPLYしてSELECT TOP 1。
 うーん、あんまり見慣れない感じのSQLだけど集計効率的にはどうなのだろうか・・・

子要素の先頭を取得

あるデータに子要素が複数あって1件だけ取得したい場合も同様の実装で実現できる。
以下のようなUserクラスを内包するLoginクラスがあり、同じユーザーのレコードが複数あるとして、
※Userクラスの中身は割愛

Login.cs
    /// <summary>
    /// ログイン情報
    /// </summary>
    public class Login
    {
        /// <summary>
        /// ログインID
        /// </summary>
        [Key, Column(TypeName = "uniqueidentifier")]
        public Guid LoginId { get; set; }

        /// <summary>
        /// ログインユーザID
        /// </summary>
        [Column(TypeName = "int")]
        public int UserId { get; set; }

        [ForeignKey("UserId")]
        public User User { get; set; }
    }

Userごとの1件分のLoginを取得するコードは以下のようになる。

test.linq
// LINQPadでLanguageにC# Expressionを選んでいる場合の例。
Logins.GroupBy(l => l.UserId).Select(g => g.FirstOrDefault()).Dump() // LINQPad上で結果を見る場合は .Dump() を追加
7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6