※環境ASP.NET MVC4(C#)

お世話になります。

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

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

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

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

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

`<SQL抽出内容>`

```

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

`<モックデータ>`

```

    var moq = 
    [
        {
            "親": 
            [
                {
                     "子": 
                     [
                        {
                             "孫コード": "001",
                             "孫名称"  : "部門１のサブ名称"
                        },
                        {
                             "孫コード": "002",
                             "孫名称"  : "部門１のサブ名称2"
                        }
                     ],
                     "子値": "部門１"
                 },
                {
                    "子": 
                    [
                        {
                            "孫コード": "001", 
                            "孫名称": "部門２のサブ名称" 
                        }
                    ],
                    "子値": "部門２"
                }
             ],
            "親値": "2019"
        },
        {
            "親": 
            [
                {
                     "子": 
                     [
                        {
                             "孫コード": "001",
                             "孫名称"  : "部門１のサブ名称"
                        }
                     ],
                     "子値": "部門１"
                 },
                {
                    "子": 
                    [
                        {
                            "孫コード": "001", 
                            "孫名称": "部門Ｂのサブ名称" 
                        },
                        {
                            "孫コード": "002", 
                            "孫名称": "部門Ｂのサブ名称" 
                        }                    
                    ],
                    "子値": "部門Ｂ"
                }
             ],
            "親値": "2018"
        }
    ];
    
```
CC BY-SA 4.0
2018-10-07T00:37:18.473
49024
2018-10-20T07:41:10.433
2018-10-07T02:56:26.463
20098
30402
question
c#
SQL抽出結果から配列の配列を作るには？
モックデータは複雑な構造のJSON形式ですので、SQL抽出結果をご希望の構造に変換するライブラリは存じ上げません。(おそらく存在しないと予想します)

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

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

```

    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();
            }
        }
    }
    
```

`DataContractJsonSerializer`ではJSONデータの整形ができませんが、提示された`<モックデータ>`と同一の構造が出力できます。  
値もSQL3行目の孫コードとSQL5,6行目の「部門Ａ」と「部門Ｂ」以外は一致します。
CC BY-SA 4.0
2018-10-08T00:00:07.990
49046
2018-10-08T00:00:07.990
9820
49024
answer
payaneco様

お返事が、大変・大変遅くなりまして申し訳ございませんでした。

ご提示頂いたソースサンプルを参考にこちらでもいろいろ考え  
思い通りの操作を実現することが出来ました。

最大のポイントは、やはり自前で3階層のリストを構築しなければならないという事につきますね。  
これをしない限り、またはこの元ネタが無い限り、どんな変換ライブラリ等があってもJSONは作成できませんですね。  
逆を言えば元ネタがあればどのライブラリを使用してもJSONは作成できる。  
payaneco様には３階層オブジェクトは自分で作成しないと無理なこと、これを教えられました。

JSONデータの作成方法はいろいろあるみたいですが私は理解しやすかったJson.NETを選択しました。

以下、かなりもたついた古めかしい実装ですがこれで実現できました。  
(変数名等、サンプル用に書き換えています)

ざっとした流れですが、  
あらかじめDBから取得したリストを順次なめ、  
ブレークしたらそれぞれのリストを追加していくという初歩的な書き方で3階層のオブジェクトを作り、  
JsonConvert.SerializeObjectでstring型の変数に入れ、それをビュー側(cshtml)のjavascriptで  
JSON.parseしてknockoutjs用の変数に設定してます。

* * *

##  C# 側

```

    public class 孫
    {
        public string 孫 { get; set; }
        public string 孫名 { get; set; }
        public string 孫Disp { get; set; }
    }
    public class 子
    {
        public string   子 { get; set; }
        public List<孫> 孫List { get; set; }
    }
    
    //親子孫リスト
    public class 親子孫List
    {
        public string 親 { get; set; }
        public List<子> 子List { get; set; }
    }
    
    public void CreateJson()
    {
        //データ取得
        var dao = new sampledao("接続先");
        var results = dao.sampleInfoList();
        if (results == null)
        {
            エラー処理
        }
    
        //親・子・孫のjson作成
        var key親 = string.Empty;  //ブレイクキー：親
        var key子 = string.Empty;  //ブレイクキー：子
        List<親子孫> JsonObj = new List<親子孫>();
    
        子 list子;
        孫 list孫;
    
        foreach (var result in results)
        {
            if (key親 != result.親)
            {
                親子孫 JsonObjtemp = new 親子孫();
                JsonObjtemp.親 = result.親;
                JsonObj.Add(JsonObjtemp);
                JsonObj[JsonObj.Count - 1].子List = new List<子>();
                key親 = result.親;
            };
    
            if (key子 != result.子)
            {
                list子 = new 子();
                list子.子 = result.子;
                list子.孫List = new List<孫>();
                JsonObj[JsonObj.Count - 1].子List.Add(list子);
                key子 = result.子;
            };
    
            list孫 = new 孫();
            list孫.孫 = result.孫;
            list孫.孫名 = result.孫名;
            list孫.孫Disp = result.孫 + " " + result.孫名;
            list子.孫List.Add(list孫);
        }
    
        ViewModel.親子孫List = JsonConvert.SerializeObject(JsonObj); //←ViewModel.親子孫Listはstringです。
        return;
    }
    
```

* * *

view側(cshtml)

## JavascriptでJSON.parseしたものをknockoutjsのobservable型の変数にセット

```

        <script type="text/javascript">
            var temp = function () {
                var self = this;
                self.親子孫 = ko.observable(JSON.parse('@Html.Raw(Model.親子孫List)'));
            ...
            ...
        </script>
    
```

以上、いろいろご教授頂きありがとうございました。  
これにて質問を閉じたいと思います。
CC BY-SA 4.0
2018-10-20T07:41:10.433
49475
2018-10-20T07:41:10.433
30436
49024
answer