質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.22%

ASP.NETの認証機能のAspNetUsersテーブルに自動採番(identity)のカラムを追加すると更新時にエラーになる

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 89

woodcube

score 10

前提・実現したいこと

ASP.NET Coreでユーザが複数の店舗情報を持つDBの作成をしています。
店舗テーブルに、ユーザのIDを持たせ、所有者がわかるようにします。

店舗テーブルに、認証機能のユーザID(AspNetUsers.Id)を持たせようとしましたが、
IdはGUIDで長く読みにくい為、int型に変更して自動採番をさせようとしました。
ただ、AspNetUsers.Idをint型などにすると、Roleなど他のテーブルへの影響があり、
nvarchar型のままでユニークで短い文字列を生成するよりは、クラスを継承して
int UserId(auto increment)を追加する事にしました。
※認証系は既存のAspNetUsers.Id(GUID)で扱い、
ビジネスロジック系はAspNetUsers.UserId(int)で識別するという事です。

認証機能の登録の過程の「Click here to confirm your account」をクリックするとエラーになってしまいます。

対処・対応の方法として、次の4パターンを考えました。
1.店舗レコードへ書くUser識別を諦めてGUIDにしておく(できれば避けたい)
2.ユーザIDをGUIDなくロジックで生成する(重複なく生成する必要がある)
3._userManager.ConfirmEmailAsyncでUserIdの項目が更新対象になっている(からエラーになると思われる)ので、ここを見直す(方法が分からない)
4.更新対象のレコード・カラムをピンポイント(SQL文を発行)で更新する(ここだけSQL?)

「2.」の方法で確実にIdを生成する方法が思いつきません
→Count+1とか、ランダム生成して重複してないかチェックするのは賢いとは思えない
「3.」のケースで特定の項目だけ更新の対象外にする方法とかがあるのでしょうか?

※個人的には「2.」でうまく生成できる方法が後々にも楽なのかと思っています。

やった事

・ASP.MET COREでWEBアプリケーションを作成(認証は無しで作成)。
・新規スキャフォールディングでIDを指定し認証を有効にする(すべてのファイルをオーバーライド)。
・認証のユーザクラスIdentityUserに対して継承を行い、App01FindUserを作成しUserIdを追加。この項目をDatabaseGeneratedOption.Identityでauto incrementに指定↓

public class App01FindUser : IdentityUser
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 
    public int UserId { get; set; } 
}


・マイグレーションとアップデートを行い、テーブルを作成。
イメージ説明

・ユーザを登録し、「Click here to confirm your account」をクリックするとエラーになります。
Email Confirmは、下記の「エラーが発生する該当のソースコード」に載せてあります。
新規スキャフォールディングでオーバーライドしてから何も変更していません。
(userIdという変数がありますが、元々あり今回のUserIdとは正しく区別されてるようです)
※データは実際に登録されています。
※UserIdはauto increment指定なので、設定不要という認識で一切値を設定していません。

発生している問題・エラーメッセージ

イメージ説明

エラーが発生する該当のソースコード

ConfirmEmail.cshtml.cs

namespace App01Find.Areas.Identity.Pages.Account
{
    [AllowAnonymous]
    public class ConfirmEmailModel : PageModel
    {
        private readonly UserManager<App01FindUser> _userManager;

        public ConfirmEmailModel(UserManager<App01FindUser> userManager)
        {
            _userManager = userManager;
        }

        [TempData]
        public string StatusMessage { get; set; }

        public async Task<IActionResult> OnGetAsync(string userId, string code)
        {
            if (userId == null || code == null)
            {
                return RedirectToPage("/Index");
            }

            var user = await _userManager.FindByIdAsync(userId);
            if (user == null)
            {
                return NotFound($"Unable to load user with ID '{userId}'.");
            }

            code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
            var result = await _userManager.ConfirmEmailAsync(user, code);  //←ここでエラー
            StatusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email.";
            return Page();
        }
    }
}

補足情報(FW/ツールのバージョンなど)

Visual Studio 2019
ASP.NET CORE 5

うまくいかなかった原因

回答者の協力により、下記の事が分りました。

AspNetUsersの更新は、変更箇所以外のカラムもすべて更新対象としており、auto incrementのカラムはエラーになる事がわかりました。
コーディングの量に対して効果が低いので、auto incrementのカラムは諦めました。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • SurferOnWww

    2021/02/23 21:11

    話は通じてますでしょうか? 

    「Email Confirmation 関係の実装はどのようにしているのか」について、今は使ってないようですが、今後使うのか使わないのかどうするのですか?・・・をお聞きしてます。

    キャンセル

  • woodcube

    2021/02/23 21:42

    ごめんなさい、勘違いしてました。

    今はデフォルトのままですが、最終的にはきちんと実装する予定です。
    今はローカルでメールが送れないとかで動作確認出来ないのでいじってないですが、そのうちazureに搭載してemail confirmationは必須にするつもりでいます。

    お手数おかけします

    キャンセル

  • SurferOnWww

    2021/02/23 22:22

    そうですか、使わないのであればデフォルトで options.SignIn.RequireConfirmedAccount が true に設定されているのを false にすれば対症療法的には・・・とか思っていたのですが。明日また考えてみます。

    キャンセル

回答 1

checkベストアンサー

+1

まずエラーになる原因ですが、質問者さんが質問に書いた、

3._userManager.ConfirmEmailAsyncでUserIdの項目が更新対象になっている(からエラーになると思われる)ので、ここを見直す(方法が分からない)

というのはその通りのようで、ConfirmEmailAsync メソッドで Identity の UserId フィールドも更新しようとするからのようです。

以下の画像は EF Core のログを Visual Studio の「出力」ウィンドウに表示できるようにし、ユーザー登録後に RegisterConfirmation ページのリンク "Click here to confirm your account" をクリックして ConfirmEmail ページを呼び出し、_userManager.ConfirmEmailAsync(user, code) を実行したときのものです。

イメージ説明

AspNetUsers テーブルの EmailConfirmed フィールドのみ false ⇒ ture に更新すれば良いところ、他のフィールドにも更新前と同じ値が渡されて UPDATE されています。

上の画像の例では、プロファイル情報に HandleName を追加しています。それにも値が渡され UPDATE されています。上の例は、質問者さんのケースと違って Identity 列ではなく、ただの NVARCHAR(128) なので問題なく UPDATE できています。

「ここを見直す」には ConfirmEmailAsync メソッドの実装を書き直す他ありませんが、それは現実的ではなさそうです。無理にやったとしても、直すのはそこだけでなく、Account/Manage/Index 他のページでユーザー情報の更新を行うときに使う _userManager.UpdateAsync(user) なども直さないと同じ問題が出ます。

イメージ説明

というわけで現実的なのは、追加プロファイル情報 UserId を Identity にするのをやめて普通に int 型にしておき、

2.ユーザIDをGUIDなくロジックで生成する(重複なく生成する必要がある)

という選択肢になるかと思います。(他の選択肢 1 と 4 はやる気はないと理解)

「2.」の方法で確実にIdを生成する方法が思いつきません
→Count+1とか、ランダム生成して重複してないかチェックするのは賢いとは思えない

「賢いとは思えない」とのことですが、Identity にできない以上は Register.cshtml.cs の実装の中で何とか重複してない Id を取得して設定する他なさそうです。

そこがどうしても気に入らなければ、選択肢 1 に戻るか、Id に代えて UserName を使うか(UserName は一意になるようになっています)、Id に int 型 Identity を使うよう ASP.NET Core Identity を独自実装する(以下の記事参照)ということになると思います。

ASP.NET Core Identity 独自実装(その1)
http://surferonwww.info/BlogEngine/post/2020/09/04/custom-storage-providers-for-aspnet-core-identity.aspx

上の独自実装案が、質問者さんが質問のコメントに書いた、

結局は、「AspNetUsers.Idへの値をGUIDでなく重複していない連番を振っていきたい」です。

に近いものだと思います。だた、これに Role の実装と、さらには Email Confirmation の実装まで追加しないと望む形にはならないと思いますので、そこまで時間と労力をかけてやる価値があるかは疑問ですが。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2021/02/24 14:35

    詳しく調べていただきありがとうございます。
    やはり、更新の必要が無い項目まで更新しようとしていた訳ですね。
    深い所までのステップ実行ができてなかったのですが、EF Coreのログを取れば良いというのが勉強になりました。

    UserIdは中止して、IdにGUIDでなく自動採番していく方法にします。
    (重複しないIDの生成は、職業プログラマだった昔に、空き番が見つけられずInsertがどんどん遅くなるシステムを見た事があるので敬遠してました。そもそも桁数が少なすぎる仕様なのが問題でしたが゙・・・)
    UserNameは途中変更を認める予定ですので、これをキーにするのは止めておきます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.22%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • トップ
  • ASP.NETに関する質問
  • ASP.NETの認証機能のAspNetUsersテーブルに自動採番(identity)のカラムを追加すると更新時にエラーになる