WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

Linq の GroupBy

by WebSurfer 4. December 2020 12:33

Linq to Objects で GroupBy メソッドを使って IEnumerable<IGrouping<TKey, TElement>> オブジェクトを取得できるということを備忘録として書いておきます。

SELECT クエリ

SQL 文の GROUP BY 句はグループごとの集計を取るために使われるものと理解しています。例えば上の画像にあるように、Products テーブルから SupplierID 別に商品単価の最高と最低の値を取得するというように。(DB は Microsoft が提供するサンプルデータベース Northwind です)

なので、GROUP BY 句で指定したフィールド(上の例では SupplierID)以外や、最大値と最小値を返す MAX(), MIN()、合計を返す SUM() や平均を返す AVG() など以外を SELECT することはできません。

例えば、上の画像の SELECT クエリで ProductName を SELECT しようとすると "列 'NORTHWIND.dbo.Products.ProductName' は選択リスト内では無効です。この列は集計関数または GROUP BY 句に含まれていません" というエラーになります。

Linq でも同様で、下のような Select メソッドなしで使うものではないと思っていました。

var result = await _context.Products.GroupBy(p => p.SupplierId).
                   Select(g => new 
                   { 
                       Id = g.Key, 
                       Max = g.Max(g => g.UnitPrice), 
                       Min = g.Min(g => g.UnitPrice) 
                   }).
                   ToListAsync();

Select メソッドなしで使うと、例えば以下のようにすると "Client side GroupBy is not supported" というエラーになります。(注: これは Core 3.0 以降の話で、Core 2.x 以前はサポートされていたそうです。.NET Framework の EF でもサポートされています。詳しくは Breaking changes included in EF Core 3.x の最初のセクション LINQ queries are no longer evaluated on the client を見てください)

var result1 = await _context.Products.GroupBy(p => p.SupplierId).
                    ToListAsync();

// または

var result2 = await (from p in _context.Products
                     group p by p.SupplierId into g
                     select g).ToListAsync();

そのエラーの原因は Linq to Entities で上のような GroupBy を使った Linq 式は SQL 文に変換できないということのようです。(ただし、Core 2.x 以前と .NET Framework では EF 側でオブジェクトの生成をサポートしていて、上記のコードでも OK です)

でも、Linq to Objects の場合は話が違ってきます。以下のコードは ASP.NET Core MVC のアクションメソッドですが、IEnumerable<IGrouping<int?, Products>> オブジェクトを model に取得して View に渡すことができています。

public async Task<IActionResult> ListBySupplier2()
{
    // Linq to Objects で GroupBy を使うのは問題ない
    List<Products> productList = await _context.Products.ToListAsync();
    var model = productList.GroupBy(p => p.SupplierId);

    return View(model);
}

Visual Studio 2019 のデバッガで上のコードの model を展開すると以下の画像のようになっています。

デバッガで見た model

これが全くの予想外で驚いたので備忘録として残しておくことにした次第です。(自分が無知なだけということかもしれませんけど)

これが何の役に立つのかと言われると答えに窮しますが、例えば以下の View を使って SupplierID 別にテーブルを作ると言ったことができます。

@model IEnumerable<IGrouping<int?, Products>>

@{
    ViewData["Title"] = "ListBySupplier2";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h1>ListBySupplier2</h1>

@foreach (var item in Model)
{
    <table class="table">
        <thead>
            <tr>
                <th>
                    @Html.DisplayNameFor(m => item.First().ProductId)
                </th>
                <th>
                    @Html.DisplayNameFor(m => item.First().ProductName)
                </th>
                <th>
                    @Html.DisplayNameFor(m => item.First().SupplierId)
                </th>
                <th>
                    @Html.DisplayNameFor(m => item.First().UnitPrice)
                </th>
            </tr>
        </thead>
        <tbody>
            @foreach (var product in item)
            {
                <tr>
                    <td>
                        @Html.DisplayFor(m => product.ProductId)
                    </td>
                    <td>
                        @Html.DisplayFor(m => product.ProductName)
                    </td>
                    <td>
                        @Html.DisplayFor(m => product.SupplierId)
                    </td>
                    <td>
                        @Html.DisplayFor(m => product.UnitPrice)
                    </td>
                </tr>
            }
        </tbody>
    </table>
}

上のコードで foreach (var item in model) の item は IGrouping<int?, Products> に、foreach (var product in item) の product は Products になります。

結果、ブラウザには以下のように SupplierID 別のテーブルが表示されます。

結果

ORDER BY [SupplierID] で並べて一つのテーブルに表示した方がスマートかつ見やすいと思いますが、それはとりあえず置いときます。(笑)

Tags: , ,

ADO.NET

ReportViewer にハイパーリンク設定

by WebSurfer 23. November 2020 18:23

ASP.NET Web Forms アプリ用の ReportViewer のテキストにハイパーリンクを設定する方法を備忘録として書いておきます。LocalReport の場合ですので注意してください。ServerReport で同じことができるかは不明です。

ReportViwer にハイパーリンク設定

上の画像は Product Name 列のテキストがハイパーリンクとなるように設定したものです。上から 3 番目の Aniseed Syrup をマウスでポイントして、それに設定したリンク先が画面の左下に表示されています。

ReportViewer のハイパーリンクの設定方法は以下の Microsoft のドキュメントに説明があります。

ドキュメントにある「プロパティ」とか「アクション」は何かと言うと、Visual Studio で .rdlc ファイルを開いてテキストボックスを右クリックすると出てくるメニューの[テキストボックスのプロパティ...]で表示される「テキストボックスのプロパティ」ダイアログと[アクション]タブのことです。

テキストボックスのプロパティ

そのダイアログで[URL に移動する(U)]を選択すると、テキストをクリックしたときの遷移先の URL を���力する[URL の選択(S)]テキストボックスが現れます。

テキストボックスに http または https から始まる絶対 URL を入力すると、ブラウザに表示されたとき、テキストがその URL へのハイパーリンクとして設定されます。相対 URL では無視されますので注意してください

.aspx ファイルの ReportViewer のコードの LocalReport 要素に EnableHyperlinks="True" を設定するのを忘れないようにしてください。忘れると "レポート 'Report1' にハイパーリンクが含まれます。このレポートの EnableHyperlinks プロパティが設定されていません" というエラーになります。

フィールドのデータに URL を含めてそれをリンク先として設定することもできます。また、上の画像のダイアログの[fx」をクリックすると表示されるダイアログで以下のように式を入力することもできます。

式の設定

上の画像で、式は C# の文字列の結合の場合と似ていますが、C# と違って ToString() を付けないと URL が設定されないので注意してください。エラーも出ず設定が無視されます。

最後に、ハイパーリンクのスタイルですが、下の画像のように、color: Black; text-decolation: none; が設定されています。

ハイパーリンクの CSS

なので、何もしなければ文字の色は他のテキストと同じ黒で、マウスをポイントしても何も変わりません。変えたい場合は以下のような CSS を追加すれば可能です。

<style type="text/css">
    a {
        text-decoration: none !important;
        color: #5C80B1 !important;
    }

    a:hover {
        text-decoration:underline !important;
    }
</style>

この記事の一番上の画像は上の CSS を適用したものです。色が他のテキストと違っていますし、マウスをポイントするとアンダーラインが表示されるのが分かりますでしょうか。

Tags: ,

ASP.NET

ReportViewer v15 のツールバー

by WebSurfer 22. November 2020 14:04

ASP.NET Web Forms アプリ用の ReportViewer v15 のツールバーの話です。Width プロパティで指定する幅がツールバーのアイコンを一行で表示するのに十分でない場合、その幅に入りきらないアイコンはツールバーに表示されません。

ReportViewer v15

上の画像は Width がデフォルトの 400px の場合ですが、ズーム、エクスポート、印刷、検索のアイコンが 400px の幅に入りきらず、表示されていません。(アプリは「チュートリアル : ローカル処理モードでの ReportViewer Web サーバー コントロールとデータベースのデータ ソースの使用法」のものです。本題とは関係ないことですがご参考まで)

それを以下の画像のように表示する方法を書きます。

ツールバーに全アイコンを表示

ちなみに、v12 では以下の画像のように、何もしなくても全部のアイコンが表示されます。(なぜ、v15 でも同じにしなかったのかの理由は不明です。わざわざ手間をかけて非表示しているということは何かそうせざるを得ない事情があるのかもしれません・・・)

v12 のツールバー

ならば、わざわざ v15 にアップグレードしなくても v12 を使えば良いと思うかもしれませんが、v12 は印刷に ActiveX を使っているので IE でしか印刷ができないという問題があります。ズームと印刷のアイコンは Chrome, Edge, Firefox などでは表示さえされません。また、IE11 でも問題があって、ドキュメントモードを 10 以下にしないと Height プロパティの設定が無視されます。

v15 ではそのあたりの問題が解消されており、ズームと印刷は Chrome, Edge, Firefox でも使用可能となっています。

なので、新規開発で ReportViewer を使うなら v15 の一択になると思いますが、上に述べたツールバーの問題を解決しないと、エクスポートや印刷のアイコンが現れず Excel にエクスポートできないとか印刷できないとかで使い難いと思います。

解決方法としては:

  1. ReportViewer の Width プロパティの値を固定する場合、ツールバーに不要な項目は非表示にして、固定した幅に必要なアイコンが収まるようにする。
  2. ReportViewer 幅がブラウザの表示幅一杯になるようにする。即ち、Width プロパティを 100% に設定し親要素の幅は設定しない。ブラウザの表示幅に応じてアイコンは表示されるので、この解決方法が一番よさそうです。
  3. JavaScript を使って表示する。

・・・ということが考えられます。上の 1, 2 のやり方は説明するまでもないことですので、以下に 3 番目の方法だけを書きます。その結果がこの記事の上から 2 番目の画像です。

まず、なぜ表示されないかですが、Width で設定した幅からはみ出るアイコンが含まれる div 要素に ReportViewer が自動的に display: none を設定するからです。下の画像を見てください。

html ソース

これを何とかする方法はないかと、以下の記事などを読んでみましたが、プロパティの設定なとで簡単に解決する方法はなさそうでした。

上の 3 番目の記事の JavaScript API を利用して操作することも考えましたが、display: none を display: inline-block に書き換えることはできるものの、スクロールバーを操作したりすると元の display: none に戻ってしまいます。

元の display: none に戻ってしまうというところは何ともできないので、ツールバーをクリックすると display: none を display: inline-block に書き換えられる手段を提供するのがよさそうです。

JavaScript で書き換えても、スクロールバーを操作すると ReportViewer が自動的に display: none に戻してしまいますが、その場合は再度ツールバーをクリックして再表示するということで対応してもらう他なさそうです。

そのサンプルコードは以下の通りです。コードの中の ToolBarButtonsCell というのはツールバーの div 要素に付与されているクラス名です。上の html ソースの画像を見てください。それをハードコーディングするのはちょっと抵抗がありますが、他に適当な手段が見つかりません。

<script src="Scripts/jquery-3.4.1.js"></script>
<script type="text/javascript">
    //<![CDATA[

    // ツールバーに全項目を表示するための対応を考えたのが下記

    window.onload = function () {
        $("div.ToolBarButtonsCell").on('click', changecss);
    };

    function changecss() {
        $("div.ToolBarButtonsCell > div").each(function () {
            var display = $(this).css("display");
            if (display == "none") {
                $(this).css("display", "inline-block");
            }
        });
    };
    //]]>
</script>

上にも書きましたが、v12 では全部のアイコンが表示されていたのに、v15 でわざわざ手間をかけて非表示しているということは何かそうせざるを得ない事情があって、それを上のような方法で無理やり表示すると、予期せぬ好ましからざる副作用が出るかもしれません。今のところ問題には遭遇してませんが、それが気がかりです。

最後になりますが、ReportViewer v15 は Visual Studio には含まれておらず、ツールボックスにも表れてきません。自力で NuGet インストールして設定する必要がありますので注意してください。

インストールの方法などは Microsoft のドキュメント「レポート ビューアー コントロールを使用した Reporting Services の統合 - 概要」を見てください。

Tags: , ,

ASP.NET

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  January 2021  >>
MoTuWeThFrSaSu
28293031123
45678910
11121314151617
18192021222324
25262728293031
1234567

View posts in large calendar