SQL抽出結果から配列の配列を作るには？


※環境ASP.NET MVC4(C#)

お世話になります。

目的→knockoutjsで3連動のドロップダウンリストを実現する

公式サンプルを参考に3連動用(親・子・孫)のモックデータを作り  
実現できることは確認済です。

しかし3連動用のデータは本来はDBから抽出します。  
今はIListで取得しています。  
問題なのは、SQL抽出結果をそのモックデータと同じ構造にすることです(←両者後述します)。  
いろんなサイトで調べましたが、難儀し結果わからず、質問した次第です。

SQL抽出結果からモックデータと同じ構造の配列を作るにはどうしたらよいのでしょうか？  
親の配列の中に子の配列があり、子の中にコードと名称の配列がある(？)イメージです。  
この辺不得意分野で、分かりやすい説明も頂けたら助かります。  
そして非常に急いでおります(汗)  
どなたかご教授お願い致します。

以下SQL抽出内容とモックデータ構造です。  
(値は例として書き換えています。)

`<SQL抽出内容>`

[code]

    親      子   孫コード    孫名称
    2019, 部門１, 001,    部門１のサブ名称
    2019, 部門１, 002,    部門１のサブ名称2
    2019, 部門２, 002,    部門２のサブ名称
    2018, 部門１, 001,    部門１のサブ名称
    2018, 部門Ａ, 001,    部門Ａのサブ名称
    2018, 部門Ａ, 002,    部門Ａのサブ名称2
    
[/code]

`<モックデータ>`

[code]

    var moq = 
    [
        {
            "親": 
            [
                {
                     "子": 
                     [
                        {
                             "孫コード": "001",
                             "孫名称"  : "部門１のサブ名称"
                        },
                        {
                             "孫コード": "002",
                             "孫名称"  : "部門１のサブ名称2"
                        }
                     ],
                     "子値": "部門１"
                 },
                {
                    "子": 
                    [
                        {
                            "孫コード": "001", 
                            "孫名称": "部門２のサブ名称" 
                        }
                    ],
                    "子値": "部門２"
                }
             ],
            "親値": "2019"
        },
        {
            "親": 
            [
                {
                     "子": 
                     [
                        {
                             "孫コード": "001",
                             "孫名称"  : "部門１のサブ名称"
                        }
                     ],
                     "子値": "部門１"
                 },
                {
                    "子": 
                    [
                        {
                            "孫コード": "001", 
                            "孫名称": "部門Ｂのサブ名称" 
                        },
                        {
                            "孫コード": "002", 
                            "孫名称": "部門Ｂのサブ名称" 
                        }                    
                    ],
                    "子値": "部門Ｂ"
                }
             ],
            "親値": "2018"
        }
    ];
    
[/code]




モックデータは複雑な構造のJSON形式ですので、SQL抽出結果をご希望の構造に変換するライブラリは存じ上げません。(おそらく存在しないと予想します)

`DataContractJsonSerializer`などの標準的なライブラリを使ってJSONへ変換することは可能です。  
下記のサンプルコードが参考になるかご確認ください。

なお下記のサンプルコードを実行するには、参照設定で`System.Runtime.Serialization`の追加とNuGetで`System.Data.SQLite.Core`の追加が必要です。

[code]

    using System;
    using System.Collections.Generic;
    using System.Data.SQLite;
    using System.IO;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Json;
    
    namespace ConsoleApp1
    {
        class Program
        {
            const string MOC_SQL = @"
    select 2019 親, '部門１' 子, '001' 孫コード, '部門１のサブ名称' 孫名称 union all 
    select 2019 親, '部門１' 子, '002' 孫コード, '部門１のサブ名称2' 孫名称 union all 
    select 2019 親, '部門２' 子, '002' 孫コード, '部門２のサブ名称' 孫名称 union all 
    select 2018 親, '部門１' 子, '001' 孫コード, '部門１のサブ名称' 孫名称 union all 
    select 2018 親, '部門Ａ' 子, '001' 孫コード, '部門Ａのサブ名称' 孫名称 union all 
    select 2018 親, '部門Ａ' 子, '002' 孫コード, '部門Ａのサブ名称2' 孫名称 ";
            static void Main(string[] args)
            {
                //DB構造読み取り
                var list = LoadMockList();
                using (var stream = new MemoryStream())
                {
                    //JSONシリアライズ
                    var serializer = new DataContractJsonSerializer(typeof(MockList));
                    serializer.WriteObject(stream, list);
                    stream.Position = 0;
                    using (var reader = new StreamReader(stream))
                    {
                        //出力
                        Console.Write(reader.ReadToEnd());
                    }
                }
            }
    
            /// <summary>
            /// データベースからモックのJSONを作成する
            /// </summary>
            /// <returns></returns>
            private static MockList LoadMockList()
            {
                var list = new MockList();
                //todo SQLiteを使ったサンプルコードなので必要なDB接続に読み替えること
                var scsb = new SQLiteConnectionStringBuilder { DataSource = ":memory:" };
                using (var cn = new SQLiteConnection(scsb.ToString()))
                {
                    cn.Open();
                    using (var cmd = new SQLiteCommand(cn))
                    {
                        //SQLで値を取得
                        cmd.CommandText = MOC_SQL;
                        var reader = cmd.ExecuteReader();
                        while (reader.Read())
                        {
                            //構造を読み取ってリスト化
                            list.Merge(reader);
                        }
                    }
                }
                return list;
            }
        }
    
        public class MockList : List<MockParent>
        {
            /// <summary>
            /// 親値から要素を追加/取得し、子要素を追加する
            /// </summary>
            /// <param name="reader"></param>
            /// <returns></returns>
            public void Merge(SQLiteDataReader reader)
            {
                MockParent parent;
                if (Exists(m => m.Value == reader["親"].ToString()))
                {
                    parent = Find(m => m.Value == reader["親"].ToString());
                    parent.Merge(reader);
                }
                else
                {
                    parent = new MockParent(reader);
                    Add(parent);
                }
            }
        }
    
        [DataContract]
        public class MockParent
        {
            [DataMember(Name = "親", Order = 1)]
            public List<MockChild> Children { get; set; }
            [DataMember(Name = "親値", Order = 2)]
            public string Value { get; set; }
    
            public MockParent()
            {
                Children = new List<MockChild>();
            }
    
            public MockParent(SQLiteDataReader reader) : this()
            {
                Children.Add(new MockChild(reader));
                Value = reader["親"].ToString();
            }
    
            /// <summary>
            /// 子値から要素を追加/取得し、孫要素を追加する
            /// </summary>
            /// <param name="reader"></param>
            /// <returns></returns>
            public void Merge(SQLiteDataReader reader)
            {
                MockChild child;
                if (Children.Exists(m => m.Value == reader["子"].ToString()))
                {
                    child = Children.Find(m => m.Value == reader["子"].ToString());
                    child.Grandsons.Add(new MockGrandson(reader));
                }
                else
                {
                    child = new MockChild(reader);
                    Children.Add(child);
                }
            }
        }
    
        [DataContract]
        public class MockChild
        {
            [DataMember(Name = "子", Order = 1)]
            public List<MockGrandson> Grandsons { get; set; }
            [DataMember(Name = "子値", Order = 2)]
            public string Value { get; set; }
    
            public MockChild()
            {
                Grandsons = new List<MockGrandson>();
            }
    
            public MockChild(SQLiteDataReader reader) : this()
            {
                Grandsons.Add(new MockGrandson(reader));
                Value = reader["子"].ToString();
            }
        }
    
        [DataContract]
        public class MockGrandson
        {
            [DataMember(Name = "孫コード")]
            public string Code { get; set; }
            [DataMember(Name = "孫名称")]
            public string Value { get; set; }
    
            public MockGrandson(SQLiteDataReader reader)
            {
                Code = reader["孫コード"].ToString();
                Value = reader["孫名称"].ToString();
            }
        }
    }
    
[/code]

`DataContractJsonSerializer`ではJSONデータの整形ができませんが、提示された`<モックデータ>`と同一の構造が出力できます。  
値もSQL3行目の孫コードとSQL5,6行目の「部門Ａ」と「部門Ｂ」以外は一致します。

