WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

EF Core で SQL Server に渡される SQL 文の確認

by WebSurfer 19. February 2021 16:27

開発時に Entity Framework から SQL Server に渡される SQL 文を確認したいことがあると思います。EF6 と違って EF Core ではそれは少々面倒だったのですが、EF Core 5.0 で導入された「シンプルなログ」という機能を使えば EF6 並みに簡単にできるようになったとのことなので試してみました。

EF Core の SQL 文

その「シンプルなログ」機能を使って EF Core が SQL Server に発行した SQL 文を Visual Studio の出力ウィンドウに表示したのが上の画像です。

基本的には DbContextOptionsBuilder.LogTo メソッドを使ってコンテキストクラスにアクセスして EF Core ログを取得し、指定の場所にログを書き出すという操作になるようです。

NuGet パッケージの追加、構成の変更、コードの大幅な書き換えなどは必要なく、Microsoft のドキュメント「シンプルなログ」に書かれているように、コンテキストクラスの OnConfiguring メソッドに一行追加するだけで EF Core ログの取得が可能になります。

具体的には、例えば先の記事「スキャフォールディング機能 (CORE)」でリバースエンジニアリングで作成したコンテキストクラスを例に取ると、以下のように自動生成された OnConfiguring メソッドの中で LogTo メソッドを使って EF Core ログを取得し、それを Debug.WriteLine メソッドで書き出すという一行を追加すれば OK です。

using Microsoft.EntityFrameworkCore;
using MvcCore5App.Models;

namespace MvcCore5App.DAL
{
    public partial class NorthwindContext : DbContext
    {
        public NorthwindContext(DbContextOptions<NorthwindContext> options)
            : base(options)
        {
        }

        public virtual DbSet<Category> Categories { get; set; }
        public virtual DbSet<Product> Products { get; set; }
        public virtual DbSet<Supplier> Suppliers { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            // 出力ウィンドウに EF Core ログを表示
            optionsBuilder.LogTo(msg => System.Diagnostics.Debug.WriteLine(msg));

            // ・・・中略・・・
        }

        // ・・・中略・・・
    }
}

上の LogTo メソッドにより、Visual Studio でデバッグ実行した際に EF Core ログが出力ウィンドウに表示され、その中に EF Core から SQL Server に渡される SQL 文が含まれるようになります。

例えば、以下のような Linq to Entities 文をコントローラーのアクションメソッドに書いて Visual Studio でデバッグ実行し、そのアクションメソッドをブラウザから呼び出してから出力ウィンドウを見ると、この記事の一番上の画像の SQL 文が発行されているのが確認できます。

namespace MvcCore5App.Controllers
{
    public class ProductsController : Controller
    {
        private readonly NorthwindContext _context;

        public ProductsController(NorthwindContext context)
        {
            _context = context;
        }

        public IActionResult Index()
        {
            // どのような SQL 文が投げられるかの検証用
            var list = _context.Products.OrderBy(p => p.ProductId).
                                         Skip(20).Take(10).ToList();

            // ・・・中略・・・
        }

        // ・・・中略・・・
    }
}

上に紹介した「シンプルなログ」は開発時限定でログを取ることを目的としているようで、運用時などでのログの取得には別の機能があるそうです。詳しくは Microsoft のドキュメント「ログとインターセプトの概要」を見てください。


最後に、EF6 で SQL Server に渡される SQL 文を確認する方法もついでに書いておきます。下の画像が EF6 のログを出力ウィンドウに書き出して、その中に含まれる SQL 文の部分を示したものです。

EF6 の SQL 文

基本的には EF Core と同様にコンテキストクラスからログを得て Visual Studio の出力ウィンドウに書き出すのですが、それには DbContext.Database.Log プロパティ を以下のように使います。

namespace Mvc5App.Controllers
{
    public class ProductsController : Controller
    {
        private NORTHWINDEntities db = new NORTHWINDEntities();

        public ActionResult Index()
        {
            // 出力ウィンドウに EF6 ログを表示
            db.Database.Log = msg => System.Diagnostics.Debug.Write(msg);

            var list = db.Products.OrderBy(p => p.ProductID).
                                   Skip(20).Take(10).ToList();

            // ・・・中略・・・
        }
    }
}

元々はログを取るための機能で、SQL 文を確認するだけでなく他にもいろいろ情報が取得できるようです。詳しくは Microsoft のドキュメント「データベース操作のログ記録と受信」を見てください。

Tags: , , ,

CORE

ロールに属するユーザー一覧を表示

by WebSurfer 13. February 2021 11:19

ASP.NET Core MVC アプリでユーザー認証・承認を行うのに ASP.NET Core Identity を利用するケースで、ロールにアサインされたユーザー一覧を表示するサンプルを書きます。

ロールに属するユーザー一覧の表示

先の記事「ASP.NET Identity のロール管理 (CORE)」で、管理者がロールの表示・追加・変更・削除を行うためのサンプルを紹介しましたが、それの Details アクションメソッド / ビューに実装してみます。(先の記事では Details は未実装でした)

(1) ビューモデルの追加

コントローラーからビューに渡すモデルとして UsersInRole クラスを追加します。

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

// 追加
using Microsoft.AspNetCore.Identity;
using MySQLIdentity.Areas.Identity.Data;

namespace MySQLIdentity.Models
{
    // ・・・中略(先の記事のコードと同じ)・・・

    // 追加
    public class UsersInRole
    {
        public IdentityRole Role { set; get; }
        public IList<MySQLIdentityUser> Users { set; get; }
    }
}

(2) Details アクションメソッドを追加

先の記事のコントローラー RoleController にアクションメソッド Details を追加します。

using System.Collections.Generic;
using System.Linq;
// ・・・中略(先の記事のコードと同じ)・・・

namespace MySQLIdentity.Controllers
{
    public class RoleController : Controller
    {
        // ・・・中略(先の記事のコードと同じ)・・・

        // 以下のアクションメソッドを追加
        public async Task<IActionResult> Details(string id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var role = await _roleManager.FindByIdAsync(id);
            if (role == null)
            {
                return NotFound();
            }

            var model = new UsersInRole
            {
                Role = role,
                Users = await _userManager.GetUsersInRoleAsync(role.Name)
            };

            return View(model);
        }

        // ・・・中略(先の記事のコードと同じ)・・・
    }
}

(3) ビュー Details.cshtml を追加

@model MySQLIdentity.Models.UsersInRole

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

<h1>Details</h1>

<h4>Users in Role "@Model.Role.Name"</h4>

<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Users.First().UserName)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Users.First().HandleName)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Users.First().PhoneNumber)
            </th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Users)
        {
            <tr>

                <td>
                    @Html.DisplayFor(modelItem => item.UserName)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.HandleName)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.PhoneNumber)
                </td>
            </tr>
        }
    </tbody>
</table>

<p>
    @Html.ActionLink("Edit", "Edit", new { id = Model.Role.Id }) |
    @Html.ActionLink("Back to List", "Index")
</p>

一覧表示画面 Index の Details リンクをクリックし、Member ロールに属するユーザー一覧を表示したのが上の画像です。

Tags: , , ,

CORE

System.Text.Json の JsonElement をパース

by WebSurfer 8. February 2021 13:03

.NET Core 3.0 以降で利用できる JsonSerializer クラスDeserialize<TValue>(String, JsonSerializerOptions) メソッドを使って JSON 文字列を JsonElement 構造体のオブジェクトにデシリアライズし、それをパースして下の画像のように表示する方法を書きます。

JsonElement をパース

基本的には先の記事「Json.NET の JToken をパース」と同じことを行っています。違うのは先の記事では JToken クラスにデシリアライズしていたものが JsonElement 構造体になったところです。

それに伴い、先の記事では JToken が JObject, JArray, JValue のどれにキャストできるかによってオブジェクトなのか配列なのかプリミティブ値なのかを調べてそれぞれ処理を分けていたところが、この記事の JsonElement の場合は JsonElement.ValueKind プロパティを使って JsonValueKind 列挙型のどれに該当するかを調べて処理を分けることになります。

また、foreach ループでの反復処理を、Object が対象の場合は JsonElement.EnumerateObject メソッドを使って、Array が対象の場合は JsonElement.EnumerateArray メソッドを使って列挙子を取得して行うところも先の記事とは異なります。

そのあたりは以下のサンプルコードのコメントに書きましたので見てください。

まず、Deserialize<JsonElement>(jsonText) で JSON 文字列を JsonElement 型のオブジェクトにデシリアライズします。その後 Parse メソッドで JsonElement オブジェクトの中身がプリミティブ型なのか Object なのか Array なのかを再帰的に解析し結果をコンソールに出力したのが上の画像です。

using System;
using System.Text.Json;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using System.IO;

namespace ConsoleAppJson
{
    class Program
    {
        static void Main(string[] args)
        {
            string path = @"C:\Users\...\ConsoleAppJson\";
            string file = "TextFile5.txt";
            string jsonText = "";

            using (StreamReader sr = File.OpenText(path + file))
            {
                jsonText = sr.ReadToEnd();
            }

            var jelem = JsonSerializer.Deserialize<JsonElement>(jsonText);
            Parse(0, jelem);
        }

        private static void Parse(int padding, JsonElement jelem)
        {
            // JsonElement.ValueKind プロパティを使って JsonValueKind 列挙型
            // のどれに該当するかを調べて処理を分ける
            if (jelem.ValueKind == JsonValueKind.False ||
                jelem.ValueKind == JsonValueKind.Null ||
                jelem.ValueKind == JsonValueKind.Number ||
                jelem.ValueKind == JsonValueKind.String ||
                jelem.ValueKind == JsonValueKind.True)
            {
                string str = $"value = {jelem}";
                Console.WriteLine(str.PadLeft(str.Length + padding));
            }
            else if (jelem.ValueKind == JsonValueKind.Object)
            {
                // Object の場合は EnumerateObject() で列挙子を取得し、以下の
                // ように foreach ループで JsonProperty を取得できる。
                // JsonProperty というのは単一の {"name":"value"} オブジェクト
                // と思えばよさそう
                foreach (JsonProperty jprop in jelem.EnumerateObject())
                {
                    if (jprop.Value.ValueKind == JsonValueKind.False ||
                        jprop.Value.ValueKind == JsonValueKind.Null ||
                        jprop.Value.ValueKind == JsonValueKind.Number ||
                        jprop.Value.ValueKind == JsonValueKind.String ||
                        jprop.Value.ValueKind == JsonValueKind.True)
                    {
                        string str = $"name = {jprop.Name}, value = {jprop.Value}";
                        Console.WriteLine(str.PadLeft(str.Length + padding));
                    }
                    else if (jprop.Value.ValueKind == JsonValueKind.Object)
                    {
                        string str = $"name = {jprop.Name}";
                        Console.WriteLine(str.PadLeft(str.Length + padding));
                        Parse(padding + 2, jprop.Value);
                    }
                    else if (jprop.Value.ValueKind == JsonValueKind.Array)
                    {
                        string str = $"name = {jprop.Name}";
                        Console.WriteLine(str.PadLeft(str.Length + padding));
                        int index = 1;
                        // Array の場合は EnumerateArray() で列挙子を取得し、以下のよう
                        // に foreach ループで配列内の各要素 (JsonElement) を取得できる
                        foreach (JsonElement jelemInArray in jprop.Value.EnumerateArray())
                        {
                            string idx = $"array index {index}";
                            Console.WriteLine(idx.PadLeft(idx.Length + padding + 1));
                            Parse(padding + 2, jelemInArray);
                            index++;
                        }
                    }
                    else
                    {
                        // JsonValueKind.Undefined 以外はここに来ない(はず)
                        Console.WriteLine(jelem.ToString());
                    }
                }
            }
            else if (jelem.ValueKind == JsonValueKind.Array)
            {
                int index = 1;
                // Array の場合 EnumerateArray() で列挙子を取得し、以下のよう
                // に foreach ループで配列内の各要素 (JsonElement) を取得できる
                foreach (JsonElement jelemInArray in jelem.EnumerateArray())
                {
                    string idx = $"array index {index}";
                    Console.WriteLine(idx.PadLeft(idx.Length + padding + 1));
                    Parse(padding + 2, jelemInArray);
                    index++;
                }
            }
            else
            {
                // JsonValueKind.Undefined 以外はここに来ない(はず)
                Console.WriteLine(jelem.ToString());
            }
        }
    }
}

上の画像を出力するのに使った JSON 文字列は以下の通りです。先の記事と同じです。

{
  "Title": "This is my title",
  "Response": {
    "Version": 1,
    "StatusCode": "OK",
    "Result": {
      "Profile": {
        "UserName": "SampleUser",  
        "IsMobileNumberVerified": false,
        "MobilePhoneNumber": null
      },
      "ObjectArray" : [
          {"Code": 2000,"Description": "Fail"},
          {"Code": 3000,"Description": "Success"}
      ],
      "lstEnrollment": "2021-2-5"
    },
    "Message": {
      "Code": 1000,      
      "Description": "OK"
    }
  },
  "StringArray" : ["abc", "def", "ghi"]
}

Tags: , , , ,

CORE

About this blog

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

Calendar

<<  February 2021  >>
MoTuWeThFrSaSu
25262728293031
1234567
891011121314
15161718192021
22232425262728
1234567

View posts in large calendar