【C#】LINQ

実行環境

■Windows10
■Visual Studio Community 2019
■WPF(Prism Blank App)

 

LINQとは

LINQ(Language Language Integrated Query)とは、統合言語クエリと呼ばれるものです。
C#の3.0から使えるようになった機能です。
SQLの書き方に似ているだけで、SQLとは別物になります。

LINQの特徴

特徴①
インテリセンスに対応している。

特徴②
実行時エラーではなく、コンパイラエラーになる。

特徴③
クエリ構文とメソッド構文という2つの書き方がある。
ざっくりとした違いは、クエリ構文がSQLっぽい書き方でメソッド構文がラムダ式を用いる書き方です。

 

クエリ構文

配列に対してのクエリ構文

xaml側にButtonを1つ用意します。

<Grid>
  <StackPanel HorizontalAlignment="Left" Margin="10">
     <Button Content="Button1"
              Command="{Binding Button1Command}"
              Height="30"
              Width="50">
       </Button>
    </StackPanel>        

  <ContentControl prism:RegionManager.RegionName="ContentRegion" />
</Grid>

次にButton1を押下した際の処理を書いていきます。

public MainWindowViewModel()
{
  Button1Command = new DelegateCommand(Button1CommandExecute);
}        
// Button1
public DelegateCommand Button1Command { get; }        

private void Button1CommandExecute()
{
  int[] nums = { 1, 2, 3, 4, 5, 6, 7 };            
  // fromで対象を決める,whereで条件,selectで抽出する項目を指定
   var result1 = from num in nums
                 where num >= 3
                 select num;            
  Console.WriteLine(string.Join(",", result1));
}

実行。

Button1を押下すると、コンソール上にクエリ構文で書いた処理結果が表示されます。
ポイントとしては、foreach文をイメージしてもらえると良いかと思います。
また、selectが最後に来ており、SQLとは書く順番違ってきます。

次にstring配列での実装方法もみていきましょう。
また、Buttonを1つ追加しておきます。
そして、ViewModelに実行した際の処理を書きます。

public MainWindowViewModel()
{
  Button2Command = new DelegateCommand(Button2CommandExecute);
}        
// Button2
public DelegateCommand Button2Command { get; }        

private void Button2CommandExecute()
{
   string[] values = { "あ", "いい", "ううう", "ええええ", "おおおおお", "あいう" };
   var result1 = from val in values
                 /* whereで「い」を含む文字列を指定 */
                 where val.Contains("い")
          /* orderbyで降順に */
                 orderby val descending
                 select val;

  Console.WriteLine("result1:" + string.Join(",", result1));
}

実行。

Button2を押下すると、コンソール上にクエリ構文での結果が表示されます。

カスタムクラスに対してのクエリ構文

xaml側にButtonを1つ追加し、ViewModelフォルダに任意のクラスを1つ作成します。

/* ここでは、Itemクラスを作成 */
public sealed class Item
{
  /* 完全コンストラクタパターン */
  public Item(int itemId, string ItemName, int price)
  {
     ItemId = itemId;
     ItemName = itemName;
     Price = price;
  }
  public int ItemId { get; }
  public string ItemName { get; }
  public int Price { get; }
}
public MainWindowViewModel()
{
  Button3Command = new DelegateCommand(Button3CommandExecute);
}        
// Button3
public DelegateCommand Button3Command { get; }

private void Button3CommandExecute()
{
   var items = new List<Item>();
   items.Add(new Item(10, "i10A", 100));
   items.Add(new Item(20, "s20A", 200));
   items.Add(new Item(30, "u30A", 300));
   items.Add(new Item(40, "i40A", 400));

   /* クエリ構文 */
   var result1 = from i in items
                 where i.ItemName[0] == 'i'
                 select i;

   foreach(var val in result1)
   {
      Console.WriteLine($"result1 id={val.ItemId} name={val.ItemName} price={val.Price}");
   }
}

実行。

Button3を押下すると、クエリ構文のwhereで条件を指定したものがコンソール上に表示されます。

 

クエリ構文で内部結合(INNER JOIN)する方法

xaml側にButtonを1つ、内部結合をするためにクラスを2つ準備します。

   /* 任意のクラスを2つ用意 */
   public sealed class Sale
    {
        /* 完全コンストラクタパターン */
        public Sale(int saleId, int customerId, DateTime saleDateTime)
        {
            SaleId = saleId;
            CustomerId = customerId;
            SaleDateTime = saleDateTime;
        }

        public int SaleId { get; }
        public int CustomerId { get; }
        public DateTime SaleDateTime { get; }
    }

    public sealed class SaleItem
    {
        public SaleItem(int saleId, int productId, int saleCount)
        {
            SaleId = saleId;
            ProductId = productId;
            SaleCount = saleCount;
        }

        // SaleクラスのSaleIdと紐づける
        public int SaleId { get; }
        public int ProductId { get; }
        public int SaleCount { get; }
    }

次にMainWindowViewModelにデータと処理を書いていきます。

     public DelegateCommand Button7Command { get; }

        private void Button7CommandExecute()
        {
            var sales = new List<Sale>();
            sales.Add(new Sale(10, 100, Convert.ToDateTime("2021/2/17 10:13:10")));
            sales.Add(new Sale(11, 100, Convert.ToDateTime("2021/2/18 10:13:10")));
            sales.Add(new Sale(12, 101, Convert.ToDateTime("2021/2/17 10:13:10")));            var saleItems = new List<SaleItem>();
            
       // Idで紐付け
            saleItems.Add(new SaleItem(10, 1, 2));
            saleItems.Add(new SaleItem(10, 2, 5));
            saleItems.Add(new SaleItem(11, 1, 2));
            saleItems.Add(new SaleItem(12, 2, 4));
            saleItems.Add(new SaleItem(12, 1, 2));

       // 書き方はSQLとほぼ一緒
            var result1 = from a in sales
                          join b in saleItems
                          on a.SaleId equals b.SaleId
                          select new
                          {
                              a.SaleId,
                              a.CustomerId,
                              a.SaleDateTime,
                              b.ProductId,
                              b.SaleCount
                          };

            foreach(var val in result1)
            {
                Console.WriteLine(val);
            }
         }

実行。

SaleIdで2つのデータが内部結合されています。

以上がクエリ構文での内部結合になります。

 

クエリ構文で外部結合(LEFT JOIN)する方法

xaml側にButtonを1つ追加します。

     public DelegateCommand Button8Command { get; }

        private void Button8CommandExecute()
        {
            var sales = new List<Sale>();
            sales.Add(new Sale(10, 1, 100, Convert.ToDateTime("2021/2/17 10:13:10")));
            sales.Add(new Sale(11, 1, 100, Convert.ToDateTime("2021/2/18 10:13:10")));
            sales.Add(new Sale(12, 1, 101, Convert.ToDateTime("2021/2/17 10:13:10")));            var saleItems = new List<SaleItem>();            saleItems.Add(new SaleItem(10, 1, 1, 2));
            saleItems.Add(new SaleItem(10, 1, 2, 5));
       // 外部結合できているか確認のため、コメントアウトしておく
            //saleItems.Add(new SaleItem(11, 1, 1, 2));
            //saleItems.Add(new SaleItem(12, 1, 2, 4));
            //saleItems.Add(new SaleItem(12, 1, 1, 2));

            var result1 = from a in sales
                          join b in saleItems
                          // 外部結合
                          on a.SaleId equals b.SaleId into bb
                          // bにはnullか外部結合してきたデータが入る
                          from b in bb.DefaultIfEmpty()
                          select new
                          {
                              a.SaleId,
                              a.CustomerId,
                              a.SaleDateTime,
                              // nullのときの処理を下記のように書く
                              ProductId = b?.ProductId ?? -1,
                              SaleCount = b?.SaleCount ?? 0,
                          };            foreach (var val in result1)
            {
                Console.WriteLine(val);
            }
        }

実行。

内部結合の時とは少し違い、nullが入った際の処理を追加しておく必要があります。

以上がクエリ構文による外部結合になります。

 

メソッド構文(ラムダ式)

xaml側にButtonを1つ追加し、ViewModel側に処理を書いていきます。

     // Button13
        public DelegateCommand Button13Command { get; }

        private void Button13CommandExecute()
        {
            var items = new List<Item>();
            items.Add(new Item(10, "i10A", 100));
            items.Add(new Item(20, "s20A", 200));
            items.Add(new Item(30, "u30A", 200));
            items.Add(new Item(40, "i40A", 400));
            items.Add(new Item(50, "i50", 400));

            // クエリ構文で書くと下記になります
            var query = from i in items
                        where i.Price == 400
                        select i;

       // メソッド構文(ラムダ式)
            var result1 = items.Where(i => i.Price == 400);

       foreach (var val in result1)
            {
                Console.WriteLine($"result1: id= {val.ItemId} name={val.ItemName} price={val.Price}");
            }
        }

実行。

Button13を押下すると、ラムダ式で書いてあるPriceが400のものが表示されているのがわかります。

また、Orderbyを使う方法もやってみます。
xaml側にButtonを1つ追加して、ViewModel側に下記のようにOrderbyを書きます。

     public DelegateCommand Button16Command { get; }

        private void Button16CommandExecute()
        {
            var items = new List<Item>();
            items.Add(new Item(10, "i10A", 100));
            items.Add(new Item(20, "s20A", 200));
            items.Add(new Item(30, "u30A", 200));
            items.Add(new Item(40, "i40A", 400));
            items.Add(new Item(50, "i50", 400));

       // Orderby
       var result1 = items.OrderBy(x => x.Price);
            foreach(var val in result1)
            {
                Console.WriteLine($"result1: id= {val.ItemId} name={val.ItemName} price={val.Price}");
            }

            Console.WriteLine("--------result2----------");

            // Orderby(降順)
            var result2 = items.OrderByDescending(x => x.Price);
            foreach (var val in result2)
            {
                Console.WriteLine($"result2: id= {val.ItemId} name={val.ItemName} price={val.Price}");
            }
        }

実行。

Button16を押下すると、Priceを基準に昇順と降順でデータが表示されていることがわかります。
このようにして、Orderbyは使用していきます。

以上がメソッド構文の書き方になります。