C#でコレクションを左外部結合してみた

C#で、自分で定義したクラスのコレクションを、LINQを使って左外部結合してみました。

目次

  1. 左外部結合とは
  2. 試してみた

左外部結合とは

2つのテーブルの列の値が同じレコードと、列の値が一致しない左側のテーブルのレコードを取得するものです。左外部結合とかleft outer joinとかでググると、たくさん説明が出てきます。

LINQ を使うと、コレクションに対して左外部結合ができます。

コレクション(A)とコレクション(B)の左外部結合をする場合のクエリは、こんな感じです。

var query = from 変数名(A) in コレクション(A)
            join 変数名(B) in コレクション(B)
            on 変数名(A).比較するプロパティ equals 変数名(B).比較するプロパティ into コレクション(C)
            from 変数名(C) in コレクション(C).DefaultIfEmpty(new コレクション(B)のクラス {右側の要素が無い場合の規定値})
            select new {プロパティ = 変数名(A).プロパティ, プロパティ = 変数名(C).プロパティ};

コレクション(A)が左側のテーブル、コレクション(B)が右側のテーブル、コレクション(C)はデータを一時的に格納するところです。コレクション(C)は事前に定義する必要はありません。

DefaultIfEmpty()というメソッドがありますが、これはnullのときに返す規定値を指定するものです。つまり、右側のテーブルがnullのときの規定値をここで指定します。

括弧内に何も指定しなければnullが返ります。ただし、その後ろのselectのところで変数名(C)を参照していたりすると、エラーになります。

試してみた

この2つのテーブルを、左外部結合してみます。 EDLINのところに注目です。

サンプル

テーブルはそれぞれCSVファイルにして、 TextFieldParser で読み込みます。

コードはこんな感じにしました。

using System;
using System.Linq;
using System.Collections.ObjectModel;
using Microsoft.VisualBasic.FileIO;

namespace trial_linq
{
    class Program
    {
        static void Main(string[] args)
        {
            // 結合するコレクション
            Collection<RelationEditorOs> RelationTable = new Collection<RelationEditorOs>();
            Collection<OSList> OSLists = new Collection<OSList>();

            // 1つめのテーブルの読み込み
            TextFieldParser parser = new TextFieldParser("relation_editor_os.csv", System.Text.Encoding.GetEncoding("Shift_JIS"));
            using (parser)
            {
                parser.TextFieldType = FieldType.Delimited;
                parser.SetDelimiters(",");
                parser.CommentTokens = new string[] { "#" };
                parser.HasFieldsEnclosedInQuotes = true;
                parser.TrimWhiteSpace = true;

                while (!parser.EndOfData)
                {
                    string[] row = parser.ReadFields();
                    RelationEditorOs rel = new RelationEditorOs();
                    rel.ID = int.Parse(row[0]);
                    rel.EditorName = row[1];
                    rel.EditorID = int.Parse(row[2]);
                    rel.OSID = int.Parse(row[3]);
                    RelationTable.Add(rel);
                }
            }

            // 2つめのテーブルの読み込み
            TextFieldParser parser2 = new TextFieldParser("os_table.csv", System.Text.Encoding.GetEncoding("Shift_JIS"));
            using (parser)
            {
                parser2.TextFieldType = FieldType.Delimited;
                parser2.SetDelimiters(",");
                parser2.CommentTokens = new string[] { "#" };
                parser2.HasFieldsEnclosedInQuotes = true;
                parser2.TrimWhiteSpace = true;

                while (!parser2.EndOfData)
                {
                    string[] row = parser2.ReadFields();
                    OSList rel = new OSList();
                    rel.ID = int.Parse(row[0]);
                    rel.OsName = row[1];
                    rel.IssureID = int.Parse(row[2]);
                    OSLists.Add(rel);
                }
            }

            // クエリ
            var query = from p in RelationTable
                        join q in OSLists on p.OSID equals q.IssureID into r
                        from s in r.DefaultIfEmpty(new OSList {IssureID = -1, OsName = "N/A" })
                        select new { EditorName = p.EditorName, OSID = p.OSID, OSTableID = s.IssureID, OSName = s.OsName };

            // クエリの実行と出力
            Console.WriteLine("Editor\tOS ID\tIssure\tOS Name");
            foreach (var s in query)
            {
                string str = string.Empty;
                str = s.EditorName + "\t" + s.OSID + "\t" + s.OSTableID + "\t" + s.OSName;
                Console.WriteLine(str);
            }

        }

        public class RelationEditorOs
        {
            public int ID { get; set; }
            public string EditorName { get; set; }
            public int EditorID { get; set; }
            public int OSID { get; set; }
        }

        public class OSList
        {
            public int ID { get; set; }
            public string OsName { get; set; }
            public int IssureID { get; set; }
        }
    }
}

クエリ以外のところは、 内部結合のとき とほぼ同じです。

出力結果はこうなりました。

出力

表にするとこんな感じです。

(注意:秀丸、MeryのMS-DOS版やMSX版はありません。VimもMSX版はたぶんありません。EDLINはMS-DOS用のエディタです。)

EDLINが、内部結合では削除されましたが、左外部結合のときには出力されています。

公開日

広告