C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[14]: C# LINQ IEnumerableからDataTable


(過去ログ 155 を表示中)

[トピック内 21 記事 (1 - 20 表示)]  << 0 | 1 >>

■90273 / inTopicNo.1)  C# LINQ IEnumerableからDataTable
  
□投稿者/ Kawauso (1回)-(2019/02/21(Thu) 18:56:32)

分類:[C#] 

お世話になります。
C#初心者です。。

IEnumerable<string[]> から DataTableへ変換したいです。。。
IEnumerable<string[]>の要素[0]には列名が入ってるイメージですが、そもそも回し方がよくわかってないので、
例題をおしえていただけますでしょうか。。

Csvから IEnumerable<string[]>にするところまではできました。。。

private void button2_Click(object sender, EventArgs e) {
    string wkCsvPath = @"C\Test.csv";
    // Csvデータのコンテキストを生成
    IEnumerable<string[]> context = Context(wkCsvPath, ",", Encoding.GetEncoding(932));
    // データテーブルへ上記のCsvデータを格納
    DataTable dtHoge = new DataTable();
}

// 指定されたCSVファイルへのコンテキストを生成する
private IEnumerable<string[]> Context(
    string path, string separator = ",", Encoding encoding = null) {
    using (Stream stream =
        new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) {
        using (TextFieldParser parser =
            new TextFieldParser(stream, encoding ?? Encoding.UTF8, true, false)) {
            parser.TextFieldType = FieldType.Delimited;
            parser.Delimiters = new[] { separator };
            parser.HasFieldsEnclosedInQuotes = true;
            parser.TrimWhiteSpace = true;
            while (parser.EndOfData == false) {
                string[] fields = parser.ReadFields();
                yield return fields;
            }
        }
    }
}

引用返信 編集キー/
■90274 / inTopicNo.2)  Re[1]: C# LINQ IEnumerableからDataTable
□投稿者/ 魔界の仮面弁士 (2069回)-(2019/02/21(Thu) 19:49:37)
2019/02/21(Thu) 19:53:08 編集(投稿者)

No90273 (Kawauso さん) に返信
> string wkCsvPath = @"C\Test.csv";

C:\Test.csv ではないようですが、
「現在のフォルダーの下にある C というフォルダーの中にある Test.csv ファイル」
の意味で間違いありませんか?



> IEnumerable<string[]> から DataTableへ変換したいです。。。
> IEnumerable<string[]>の要素[0]には列名が入ってるイメージ

これでどうでしょう。


static DataTable CreateTable(IEnumerable<string[]> records)
{
  var tbl = new DataSet("Example") { CaseSensitive = true }.Tables.Add("Kawauso");
  foreach (var colName in records.First())
  {
    tbl.Columns.Add(colName, typeof(string)).AllowDBNull = true;
  }
  foreach (var colData in records.Skip(1))
  {
    tbl.Rows.Add(colData);
  }
  tbl.AcceptChanges();
  return tbl;
}
引用返信 編集キー/
■90275 / inTopicNo.3)  Re[2]: C# LINQ IEnumerableからDataTable
□投稿者/ Kawauso (2回)-(2019/02/21(Thu) 21:08:41)
魔界の仮面弁士 さん
早速のご回答ありがとうございます。

はい、正しくは、C:\Test.csv でした(汗)

records.First()で1行目が取れるんですね。。
records.Skip(1)で1行目を飛ばすんですね。。。
IEnumerableがイマイチ理解できておらず、、、

ありがとうございます、、
引用返信 編集キー/
■90277 / inTopicNo.4)  Re[3]: C# LINQ IEnumerableからDataTable
□投稿者/ 魔界の仮面弁士 (2070回)-(2019/02/22(Fri) 10:06:49)
No90275 (Kawauso さん) に返信
> records.First()で1行目が取れるんですね。。

そうですね。そのほかにこういったものがあります。

First … 先頭行を返す。データ数が 0 件の場合は例外発生。
Single … 先頭行を返す。データ数が 1 件でない場合は例外発生。
FirstOrDefault … 先頭行を返す。データ数が 0 件の場合は既定値を返す。
SingleOrDefault … 先頭行を返す。データ数が 0 件の場合は既定値を返す。2 件以上のデータがあると例外発生。



First の場合、行データが無い場合(0バイトの CSV ファイルの時)には
例外が発生することになります。回避したい場合は、たとえばこんな感じ。


// --- 案1 ---
foreach (var colName in records.DefaultIfEmpty(Enumerable.Empty<string>()).FirstOrDefault())
{
  tbl.Columns.Add(colName, typeof(string)).AllowDBNull = true;
}

// --- 案2 ---
foreach (var colName in records.FirstOrDefault() ?? new string[0])
{
  tbl.Columns.Add(colName, typeof(string)).AllowDBNull = true;
}

// --- 案3 ---
if (records.Any())
{
  foreach (var colName in records.First())
  {
    tbl.Columns.Add(colName, typeof(string)).AllowDBNull = true;
  }
}


ただしこれでも、
 ・同じ列名が複数存在する CSV
 ・行によって列数の異なる CSV
などは失敗しますのでご注意ください。

といっても、そういう CSV は異常データでしょうし、
むしろ例外となってくれた方が都合が良いかも。
引用返信 編集キー/
■90278 / inTopicNo.5)  Re[1]: C# LINQ IEnumerableからDataTable
□投稿者/ WebSurfer (1775回)-(2019/02/22(Fri) 10:42:26)
No90273 (Kawauso さん) に返信

> IEnumerable<string[]> から DataTableへ変換したいです。。。
> Csvから IEnumerable<string[]>にするところまではできました。。。

CSV ファイル ⇒ IEnumerable<string[]> 型オブジェクト ⇒ DataTable

と CSV から直接 DataTable を作らないでワンクッション置いてるようですが、それは
必須ですか?

必須でなければ、TextFieldParser の string[] fields = parser.ReadFields(); で
fields を取得したたびに DataTable に加えていけば一手間減ってよりスマートにな
ると思うのですが。

以下の時事のコードの終わりの方に TextFieldParser を使って CSV を読んで DataTable
を作る例がありますので興味があれば見てください。CSV ファイルの一行目がヘッダーの
場合の例ですが、ヘッダーが無くても対応可能です(サンプルコードはヘッダー情報を元に
DataTable の骨格を作っていますが、それを自力でコードを書いて作成することで)

CSV パーサー
http://surferonwww.info/BlogEngine/post/2010/10/28/CSV-parser.aspx

他に Jet Provider を利用する例もサンプルコードには含まれています。schema.ini を
作れば DataTable の列の型など細かく設定できますし、コード量もかなり少なくできま
す。Jet Provider を使った方がメリットが多いかもしれません。
引用返信 編集キー/
■90280 / inTopicNo.6)  Re[2]: C# LINQ IEnumerableからDataTable
□投稿者/ Kawauso (3回)-(2019/02/22(Fri) 11:54:59)
2019/02/22(Fri) 12:08:14 編集(投稿者)
>魔界の仮面弁士 さん
そうなんですね。Csvが0バイトのケースや、列名重複やについてはむしろ例外になってくれたほうがありがたいので、
このメソッドを呼ぶ上位でTry Catchして、エラーをキャッチしたいと思いました。

>WebSurfer さん
正直言うと、僕もワンクッション置く必要無いと思っています。。。
ただ、ジェネリクス型?IEnumerable<string[]>だけで事足りるような気がしますが、
先生がDatatableで作ってくださいって言ってて。。

LINQのこと、僕はあまりよくわかっていないのですが、DataTableでなくても、ジェネリクス型?IEnumerable<string[]>は、
WhereとかGroupbyとかが使えるので、WebSurfer さんが言うように、Jet Provider使って、直接DataTableに入れるか、
あるいは、このまま、IEnumerable<string[]>に対して検索していくかで良いような気がしています。

(今回の課題は、Csvの列は可変で、列の属性とかが読まないとわかんない感じの要件なんです。。。なので、Jetなんとかの、schema.ini は利用できない気がしてます。。
ちゃんと説明すると、条件設定XMLみたいなのがあって、そこには列名と、条件値とかが書かれてて、それを読み込んで、Csvを読み込んで、
Csvに対して、このXMLから読み込んだ条件でDataTableに対して絞り込む感じなんです。)

なので、ちょっと気になるのは、Jet ProviderでCsvからDataTableに入れると、勝手に型指定されちゃって、
あとで、こうやって、
var recSets = dtKawauso.AsEnumerable();
var q1 = from recodes in recSets
         group recodes by recodes.Field<string>("kawausoCol01") into recode
         select new {
             recKey = recode.Key,
             recCount = recode.Count()
         };

クエリ式?でLINQ使う時に、recodes.Field<string>の <型>の部分がわからなくて躓く気がするので、
魔界の仮面弁士 さんがDataTable作るメソッド教えてくれたときに全部 列の属性?を文字型にしてくれてる感じが
都合が良い気がしています。すみません拙くて、、伝わりますかね。。

他にもちょっと躓いてる感じがするので、またちゃんと整理するので、
お聞きしてもよいでしょうか><

今、DataTableに対して、LINQで抽出するのと、DataTableのSelectメソッド使うのとが複合的になってて、
DataTableのSelectメソッドだと、抽出した結果が多いとすごく遅いんです。なんでだろうか。
50000行とかあると、3分とかかかっちゃうんです。

なので、この辺ちゃんとGoogleとか見て、なんで遅いのとかちゃんと整理して解決しなかったら、また聞かせてください。

引用返信 編集キー/
■90281 / inTopicNo.7)  Re[3]: C# LINQ IEnumerableからDataTable
□投稿者/ WebSurfer (1776回)-(2019/02/22(Fri) 12:12:42)
No90280 (Kawauso さん) に返信

> 先生がDatatableで作ってくださいって言ってて。。

その理由を先生に聞いてください。(そもそも、先生がいるならここで質問すべきではなく、
最初から先生に聞くべきだと思いますけど)

想像ですが、IEnumerable<string[]> ではダメなのでは? CSV ファイルの全フィールドは
文字列で string 型として取得しても良いのであればそれでもいいかもしれませんが。でも、
実際は数値とか日付とかもあるのでは?

> ちょっと気になるのは、Jet ProviderでCsvからDataTableに入れると、勝手に型指定されちゃって、

上に書いたこと読んでますか?

schema.ini を作れば DataTable の列の型など細かく設定できます。
引用返信 編集キー/
■90282 / inTopicNo.8)  Re[4]: C# LINQ IEnumerableからDataTable
□投稿者/ Kawauso (4回)-(2019/02/22(Fri) 12:34:36)
WebSurfer さん
ご指摘ありがとうございます。ここで訊いたほうが参考になりそうなので。。

実際は数値や日時型もありますが、LINQで検索する際に、その型指定する必要があるため、
(※後述ですが、Csvの列が可変で、属性も読まないとわからない感じなので、)
stringのほうが都合が良いのでは、と考えています。

> schema.ini を作れば DataTable の列の型など細かく設定できます。
schema.iniは列数固定が前提なのではないでしょうか。
今回は、Csvは列数が可変なため、読み込んでみないとわからないため、利用することができないと
思いましたがいかがでしょうか。
引用返信 編集キー/
■90283 / inTopicNo.9)  Re[3]: C# LINQ IEnumerableからDataTable
□投稿者/ 魔界の仮面弁士 (2072回)-(2019/02/22(Fri) 12:41:57)
No90280 (Kawauso さん) に返信
> そうなんですね。Csvが0バイトのケースや、列名重複やについてはむしろ例外になってくれたほうがありがたいので、
> このメソッドを呼ぶ上位でTry Catchして、エラーをキャッチしたいと思いました。

列名無し(長さゼロの文字列)だった場合には、列名が自動付与されます。
※先頭行が「,,,」だった場合など。


ついでに、foreach ループ無しで列を作ってみる例。

tbl.Columns.AddRange(records.First().Select(colName => new DataColumn(colName)).ToArray());



> 先生がDatatableで作ってくださいって言ってて。。
DataTable は変更通知などを備えているので、コントロール(DataGridView 等)にバインドする際に都合がよかったりします。

ただ、CSV を最終的に DataTable にするにしても、
 CSV ファイル ⇒ IEnumerable<string[]> 型オブジェクト ⇒ DataTable
ではなく、直接
 CSV ファイル ⇒ DataTable
という手順にすることはできそうです。どちらが良いというものではないですが。


> Jetなんとかの、schema.ini は利用できない気がしてます。。
この方法だと、型判定の問題のほか、最大文字数制限や最大列数制限などの問題もありますね。

なお、今回使うかどうかはさておき、
 Microsoft.JET.OLEDB.3.51
 Microsoft.JET.OLEDB.4.0
などは既に非推奨となっているので、使うとしても
 Microsoft.ACE.OLEDB.12.0
などの方が良いでしょう。


> Jet ProviderでCsvからDataTableに入れると、勝手に型指定されちゃって、
型は Schema.ini で指定することになりますね。


> group recodes by recodes.Field<string>("kawausoCol01") into recode
recode 書き直す、再符号化する
record 記録(する)、レコード


> recodes.Field<string>の <型>の部分がわからなくて躓く気がするので、
rec.Field<dynamic>(列) や (dynamic)rec[列] という手も。


> 列の属性?を文字型にしてくれてる感じが
tbl.Columns.Add(colName, typeof(string)) は
tbl.Columns.Add(colName) と書いても同じ意味になります。


> DataTableのSelectメソッドだと、抽出した結果が多いとすごく遅いんです。なんでだろうか。
そういうものということで。

IEnumerable<> に対する Select は式木(expression-tree)を構築した上での遅延実行ですが、
DataTable に対する Select は字句解析(DataExpression)からの即時実行で、比較的低速です。

主キーでの絞り込みの場合は、dataTable.Select の代わりに dataTable.Rows.Find を使えます。
引用返信 編集キー/
■90284 / inTopicNo.10)  Re[5]: C# LINQ IEnumerableからDataTable
□投稿者/ 魔界の仮面弁士 (2073回)-(2019/02/22(Fri) 12:48:47)
No90282 (Kawauso さん) に返信
>>schema.ini を作れば DataTable の列の型など細かく設定できます。
> schema.iniは列数固定が前提なのではないでしょうか。

CSV 側の最大列数が 255 以下であるならば、とりあえず
 Col1=Col1 Memo
 Col2=Col2 Memo
 Col3=Col3 Memo
  :
 Col255=Col255 Memo
などとしておく手はあります。(実際の列数が不足している場合は無視される)

ただ、今回のケースでは OLEDB 経由にするメリットは薄いかも。
引用返信 編集キー/
■90285 / inTopicNo.11)  Re[6]: C# LINQ IEnumerableからDataTable
□投稿者/ Kawauso (5回)-(2019/02/22(Fri) 13:03:26)
2019/02/22(Fri) 13:05:12 編集(投稿者)
魔界の仮面弁士 さん
ありがとうございます!

>ついでに、foreach ループ無しで列を作ってみる例。
>tbl.Columns.AddRange(records.First().Select(colName => new DataColumn(colName)).ToArray());
ラムダ式ってやつですよね?昨日本で読みました。1行でかけるやつですよね。
便利だけどこの構文を理解できなくて、ちゃんと使えない人がまだまだ多いって聞いたことがあります。
僕はちゃんと使えるようになりたいです。。

>DataTable は変更通知などを備えているので、コントロール(DataGridView 等)にバインドする際に都合がよかったりします。
なるほど。他コントロールへの適用することを考慮してる可能性ですね。
根拠がわかるとしっくりきますね。。。

>なお、今回使うかどうかはさておき、
> Microsoft.JET.OLEDB.3.51
> Microsoft.JET.OLEDB.4.0
>などは既に非推奨となっているので、使うとしても
> Microsoft.ACE.OLEDB.12.0
>などの方が良いでしょう。
Jet Providerは古いってことなんですかね。。

> group recodes by recodes.Field<string>("kawausoCol01") into recode
> recode 書き直す、再符号化する
> record 記録(する)、レコード
お恥ずかしい(汗)なんか英語が弱いんですよね。変数とか命名するときあとからみると恥ずかしくなります。

> rec.Field<dynamic>(列) や (dynamic)rec[列] という手も。
dynamicで指定する方法ですか、これピンと来てないのでちゃんと調べてみますね。

>tbl.Columns.Add(colName, typeof(string)) は
>tbl.Columns.Add(colName) と書いても同じ意味になります。
型指定を省略すると、string型になるということですね。

>IEnumerable<> に対する Select は式木(expression-tree)を構築した上での遅延実行ですが、
>DataTable に対する Select は字句解析(DataExpression)からの即時実行で、比較的低速です。
こないだ基本情報受けたときにこんな感じの出てきました。こうやってちゃんと繋がっていく感じが実感できると嬉しいですね。

がんばります!

引用返信 編集キー/
■90286 / inTopicNo.12)  Re[5]: C# LINQ IEnumerableからDataTable
□投稿者/ WebSurfer (1777回)-(2019/02/22(Fri) 13:18:09)
No90282 (Kawauso さん) に返信

> ここで訊いたほうが参考になりそうなので。。

少なくとも何故 DataTable なのかを先生に聞いてその理由を書いてください。
引用返信 編集キー/
■90287 / inTopicNo.13)  Re[6]: C# LINQ IEnumerableからDataTable
□投稿者/ Kawauso (6回)-(2019/02/22(Fri) 13:26:04)
WebSurfer さん
>少なくとも何故 DataTable なのかを先生に聞いてその理由を書いてください。
ちょうどさっき訊いたのですが、他コントロールへのバインドを考慮してるので、DataTableでとのことでした。
魔界の仮面弁士 さんが仰ったことと同じですね。

今のところ、単純にチェック結果のレコードをまたCsvにして出力するだけなんですけどね。
InputとOutputの列構造は同じ構造で、条件に一致したレコードだけが出力されるイメージ。

引用返信 編集キー/
■90288 / inTopicNo.14)  Re[7]: C# LINQ IEnumerableからDataTable
□投稿者/ WebSurfer (1778回)-(2019/02/22(Fri) 14:12:21)
No90287 (Kawauso さん) に返信

> 他コントロールへのバインドを考慮してるので、DataTableでとのことでした。

バインドして何をしようとしているのですかね?

> 今のところ、単純にチェック結果のレコードをまたCsvにして出力するだけなんですけどね。
> InputとOutputの列構造は同じ構造で、条件に一致したレコードだけが出力されるイメージ。

その意味が分かりませんが、表示だけなら DataTable である必要はないので、以下の記事の「非
接続型のデータ更新」のセクションに書いてあるようなことに利用するつもりなのかな(読むのが
面倒なら、図1と図2だけでも見てください。一目瞭然なので)

DB 設計者のための明解 ADO.NET 第 1 回
https://docs.microsoft.com/ja-jp/previous-versions/cc482903(v=msdn.10)

DataTable をどう利用するかは今後の話なのでとりあえず置いといて、CSV から DataTable を作
るなら No90278 で紹介した記事の TextFieldParser を使うサンプルコードのようにしてはいかが
ですか?

CSV にはヘッダがあるそうなので、サンプルコードをそのまま使えると思います。

それでは期待した結果にならないなら、どこがどう期待と違うのか書いてください。
引用返信 編集キー/
■90304 / inTopicNo.15)  Re[8]: C# LINQ IEnumerableからDataTable
□投稿者/ Kawauso (7回)-(2019/02/25(Mon) 10:46:12)
2019/02/25(Mon) 10:47:51 編集(投稿者)
魔界の仮面弁士 さん

LINQのラムダ式を動的に指定する方法がどうしてもわからず、、、
質問させていただいてもよろしいでしょうか。。

以下のように、条件テーブル:dtConditionと、チェック対象テーブル:dtMainがあったとして、
LINQのGroupBy拡張メソッドへ動的に値を指定したいのですが、
そのやり方が不明で、、どうしてもべた書きにせざるを得ないんです。
何か方法があるものなのでしょうか。


■サンプルソース
---------------------------------------------------------------------------------
using System.Linq;
using System.Data;

namespace ConsoleApp25 {
    class Program {
        static void Main(string[] args) {

            // ■条件テーブル
            DataTable dtCondition = new DataTable();

            dtCondition.Columns.Add("SEQ", typeof(int)).AllowDBNull = true;
            dtCondition.Columns.Add("Condition", typeof(string)).AllowDBNull = true;

            DataRow dr = dtCondition.NewRow();
            dr["SEQ"] = 1;
            dr["Condition"] = "KAWAUSO_COL01";
            dtCondition.Rows.Add(dr);

            dr = dtCondition.NewRow();
            dr["SEQ"] = 2;
            dr["Condition"] = "KAWAUSO_COL02";
            dtCondition.Rows.Add(dr);

            dr = dtCondition.NewRow();
            dr["SEQ"] = 3;
            dr["Condition"] = "KAWAUSO_COL03";
            dtCondition.Rows.Add(dr);

            // ■チェック対象テーブル
            DataTable dtMain = new DataTable();

            dtMain.Columns.Add("SEQ", typeof(int)).AllowDBNull = true;
            dtMain.Columns.Add("KAWAUSO_COL01", typeof(string)).AllowDBNull = true;
            dtMain.Columns.Add("KAWAUSO_COL02", typeof(string)).AllowDBNull = true;
            dtMain.Columns.Add("KAWAUSO_COL03", typeof(string)).AllowDBNull = true;

            dr = dtMain.NewRow();
            dr["SEQ"] = 1;
            dr["KAWAUSO_COL01"] = "TESTVALUE01";
            dr["KAWAUSO_COL02"] = "TESTVALUE02";
            dr["KAWAUSO_COL03"] = "TESTVALUE03";
            dtMain.Rows.Add(dr);

            dr = dtMain.NewRow();
            dr["SEQ"] = 2;
            dr["KAWAUSO_COL01"] = "TESTVALUE01";
            dr["KAWAUSO_COL02"] = "TESTVALUE02";
            dr["KAWAUSO_COL03"] = "TESTVALUE03";
            dtMain.Rows.Add(dr);

            dr = dtMain.NewRow();
            dr["SEQ"] = 3;
            dr["KAWAUSO_COL01"] = "TESTVALUE01";
            dr["KAWAUSO_COL02"] = "TESTVALUE02";
            dr["KAWAUSO_COL03"] = "TESTVALUE03-01";
            dtMain.Rows.Add(dr);

            string targetCol01 = dtCondition.Rows[0]["Condition"].ToString();
            string targetCol02 = dtCondition.Rows[1]["Condition"].ToString();
            string targetCol03 = dtCondition.Rows[2]["Condition"].ToString();

            var recSets = dtMain.AsEnumerable();

            // LINQ 静的ラムダ式 → ここを動的にするにはどうすれば…
            var q1 = from records in recSets
                     group records by new {
                         recCol01 = records.Field<string>(targetCol01),
                         recCol02 = records.Field<string>(targetCol02),
                         recCol03 = records.Field<string>(targetCol03)
                     } into record
                     select new {
                         recKey = record.Key,
                         recCount = record.Count()
                     };

            var targets = q1.Where(x => x.recCount > 1);

        }
    }
}
---------------------------------------------------------------------------------

引用返信 編集キー/
■90307 / inTopicNo.16)  Re[9]: C# LINQ IEnumerableからDataTable
□投稿者/ 魔界の仮面弁士 (2076回)-(2019/02/25(Mon) 12:25:37)
2019/02/25(Mon) 12:29:18 編集(投稿者)

No90304 (Kawauso さん) に返信
> LINQのラムダ式を動的に指定する方法がどうしてもわからず、、、

元質問から離れてきているので、別スレッドで質問しなおした方が良いと思います。


> 以下のように、条件テーブル:dtConditionと、チェック対象テーブル:dtMainがあったとして、
> LINQのGroupBy拡張メソッドへ動的に値を指定したいのですが、


どの部分を「動的」にしたいのでしょうか?

ラムダ式や匿名型は、コンパイル時点で「静的」に組み込まれる物なので、
要件によっては相性が悪い可能性があります。

where 時の AND 条件を後付けするぐらいならば比較的簡単ですが、
匿名型が多用されたり、OR 条件が絡んだ物となると途端に厄介になります。
https://www.atmarkit.co.jp/fdotnet/dotnettips/986dynamiclinq/dynamiclinq.html
https://www.atmarkit.co.jp/ait/articles/1412/16/news134.html


System.Linq.Expressions.LambdaExpression で動的に組み上げて
差し込むことも可能ですが、「やりたいこと」が曖昧なので、
それが適切な手段かどうかは判断致しかねます。
引用返信 編集キー/
■90313 / inTopicNo.17)  Re[10]: C# LINQ IEnumerableからDataTable
□投稿者/ 魔界の仮面弁士 (2077回)-(2019/02/25(Mon) 15:57:11)
2019/02/25(Mon) 16:37:54 編集(投稿者)

No90307 (魔界の仮面弁士) に追記
>>以下のように、条件テーブル:dtConditionと、チェック対象テーブル:dtMainがあったとして、
>>LINQのGroupBy拡張メソッドへ動的に値を指定したいのですが、
>>
>> var q1 = from records in recSets
>>     group records by new {
>>      recCol01 = records.Field<string>(targetCol01),
>>      recCol02 = records.Field<string>(targetCol02),
>>      recCol03 = records.Field<string>(targetCol03)
>>     } into record
>>     select new {
>>      recKey = record.Key,
>>      recCount = record.Count()
>>     };
>> var targets = q1.Where(x => x.recCount > 1);
>
> どの部分を「動的」にしたいのでしょうか?

とりあえず、GroupBy のキーとして指定している匿名型を差し替えてみました。

var conditions = dtCondition.AsEnumerable().Select(r => r.Field<string>("Condition")).ToArray();

var q0 = dtMain.AsEnumerable().Select(row => conditions.Select(colName => row.Field<string>(colName)).ToArray());

var q1 = q0.GroupBy(items => new StringItems(items)).Select(r => new { recKey = r.Key.Items.ToArray(), recCount = r.Count() });

var targets2 = q1.Where(x => x.recCount > 1);


---
上記の StringItems は、下記のクラス実装です。


// GroupBy キーの Equals メソッドの定義
public class StringItems
{
 public ReadOnlyCollection<string> Items;
 public StringItems(string[] items) { Items = Array.AsReadOnly(items); }
 public override int GetHashCode() { return Items.Aggregate(0, (a, b) => a ^ b.GetHashCode()); }
 public override bool Equals(object other) { StringItems o = other as StringItems; return o == null ? false : o.Items.SequenceEqual(Items); }
}

---
匿名型の Equals では、「全てのプロパティが一致しているかどうか」で
一致判定が行われます。上記の StringItems クラスはそれを模倣したものです。


string[] keys1 = { "AA", "BB", "CC" };
string[] keys2 = { "AA", "BB", "CC" };

// 内容は同じでも、別のインスタンスなので「false」と判定されてしまう
bool isSameKey1 = keys1.Equals(keys2);

// すべての文字列が同じ内容なので「true」
bool isSameKey2 = new StringItems(keys1).Equals(new StringItems(keys2));
引用返信 編集キー/
■90314 / inTopicNo.18)  Re[11]: C# LINQ IEnumerableからDataTable
□投稿者/ Kawauso (8回)-(2019/02/25(Mon) 18:40:06)
魔界の仮面弁士 さん

すぐに返信できずに申し訳ございませんでした。
ありがとうございます。

GroupBy部分を動的にしたかったのですが、System.Linq.Expressions.LambdaExpression にて
動的に組み上げる方法があるのですね。

あとから頂いているサンプルまで頂いて、ありがとうございます。

var conditions = dtCondition.AsEnumerable().Select(r => r.Field<string>("Condition")).ToArray();
var q0 = dtMain.AsEnumerable().Select(row => conditions.Select(colName => row.Field<string>(colName)).ToArray());
var q1 = q0.GroupBy(items => new StringItems(items)).Select(r => new { recKey = r.Key.Items.ToArray(), recCount = r.Count() });
var targets2 = q1.Where(x => x.recCount > 1);


targets2で取得した結果値をWhere句に放り込み、重複レコードを特定することで、
要件を実現できそうです。

>元質問から離れてきているので、別スレッドで質問しなおした方が良いと思います。

スミマセン… 内容ずれてきましたので次回からは別スレッドにするよう留意します。
ご指摘ありがとうございます。

引用返信 編集キー/
■90315 / inTopicNo.19)  Re[12]: C# LINQ IEnumerableからDataTable
□投稿者/ 魔界の仮面弁士 (2078回)-(2019/02/25(Mon) 19:35:10)
2019/02/25(Mon) 19:49:13 編集(投稿者)

No90313 (魔界の仮面弁士) に追記
> public class StringItems
> {

上記クラスに
 public override string ToString() { return "{" + string.Join(", ", Items.Select(s => "\"" + s + "\"")) + "}"; }
を付与しておくと、デバッグ時に視認しやすいかと思います。
(あるいは、DebuggerDisplay 属性)



No90314 (Kawauso さん) に返信
> var conditions = dtCondition.AsEnumerable().Select(r => r.Field<string>("Condition")).ToArray();

今回の場合、上記は
 // string[] conditions = { targetCol01, targetCol02, targetCol03 };
 string[] conditions = { "KAWAUSO_COL01", "KAWAUSO_COL02", "KAWAUSO_COL03"};
と同じ結果が得られますね。



> targets2で取得した結果値をWhere句に放り込み、重複レコードを特定することで、
> 要件を実現できそうです。

重複レコードの取得が目的だったのであれば、こういう書き方ができます。


// 重複しているレコードの主キー(SEQ)を列挙する場合
IEnumerable<int> conflictSEQ = dtMain.AsEnumerable()
 .Select(row => new { row, key = new StringItems(conditions.Select(colName => row.Field<string>(colName)).ToArray()) })
 .GroupBy(x => x.key, x => x.row).Where(x => x.Skip(1).Any())
 .SelectMany(x => x.Select(r => r.Field<int>("SEQ")));


// 重複しているキーごとの DataRow を列挙する例
Dictionary<StringItems, DataRow[]> conflict = dtMain.AsEnumerable()
  .Select(row => new { row, key = new StringItems(conditions.Select(colName => row.Field<string>(colName)).ToArray()) })
  .GroupBy(x => x.key, x => x.row).Where(x => x.Skip(1).Any())
  .ToDictionary(x => x.Key.Items, x => x.ToArray());
引用返信 編集キー/
■90316 / inTopicNo.20)  Re[13]: C# LINQ IEnumerableからDataTable
 
□投稿者/ Kawauso (9回)-(2019/02/25(Mon) 21:07:26)
2019/02/25(Mon) 21:09:40 編集(投稿者)
No90315 (魔界の仮面弁士 さん) に返信
魔界の仮面弁士 さん

ありがとうございます!まだ睨めっこしてます…

一発で重複チェックできるんですね…。
メソッドチェーンのチェーンのチェーンでなんだか頭がこんがらがってきました。。。(汗)

ええと、GroupByする可変のカラムは、自作でこさえた、StringItemsメソッドに渡して生成するところまではわかりました。
例えば、このGroupBy句に転送する,可変のカラムが以下の3種だったとして、
{ "KAWAUSO_COL01", "KAWAUSO_COL02", "KAWAUSO_COL03"};
この列の値 != "" の条件をWhere句で加えたい場合は、やっぱり厄介になりますかね。。

イメージ的には、以下のような感じを想定しているのですが、、、

// 重複しているレコードの主キー(SEQ)を列挙する場合
IEnumerable<int> conflictSEQ = dtMain.AsEnumerable()
// .Where(row => row.Field<string>("KAWAUSO_COL01") != "" &
//           row.Field<string>("KAWAUSO_COL02") != "" &
//           row.Field<string>("KAWAUSO_COL03") != "")
 .Select(row => new { row, key = new StringItems(conditions.Select(colName => row.Field<string>(colName)).ToArray()) })
 .GroupBy(x => x.key, x => x.row).Where(x => x.Skip(1).Any())
 .SelectMany(x => x.Select(r => r.Field<int>("SEQ")));

以下で絞ればいいのですかね?
// 重複しているキーごとの DataRow を列挙する例
Dictionary<StringItems, DataRow[]> conflict = dtMain.AsEnumerable()
 .Select(row => new { row, key = new StringItems(conditions.Select(colName => row.Field<string>(colName)).ToArray()) })
 .GroupBy(x => x.key, x => x.row).Where(x => x.Skip(1).Any())
 .ToDictionary(x => x.Key.Items, x => x.ToArray());


やはりちゃんと理解できていない気がしますので、
まずはチェーンを一つずつ紐解いていきます。

引用返信 編集キー/

次の20件>
トピック内ページ移動 / << 0 | 1 >>

管理者用

- Child Tree -