0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

.NET8 Blazor Identity WebApp で複数のデータベース コンテキストを使用

Posted at

.NET8 Blazor Identity WebApp で複数のDbContextを使用

現在、複数のデータベースにアクセスする Blazor Web アプリケーションに取り組んでいます。
アプリケーションは 3 つのデータベースにアクセスします:

  • 2 つは Postgresql データベースで、そのうち 1 つはメイン アプリケーションと Identity Server 認証の両方に使用されます
  • 残りの 1 つは Oracle データベースです

複数のデータベースを使用するには、複数のデータベース コンテキスト オブジェクトを使用するだけです。

接続文字列

  1. まず、appsettings.json ファイルで各データベースの接続文字列を定義しましょう
    "ConnectionStrings": {
        "ApplicationConnection": "Host=127.0.0.1;Port=5432;Database=db1;Username=user1;Password=pasword1",
        "OracleConnection": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.2)(PORT=1521))(CONNECT_DATA=(SERVER=dedicated)(SERVICE_NAME=service2)));User Id=user2;  Password=pasword2;",
        "AnotherConnection": "Host=127.0.0.3;Port=5432;Database=db3;Username=user3;Password=password3"
    },
    

設定

  1. 次に、データベース コンテキスト ファクトリをサービスとして Program.cs ファイルに登録しましょう
    var applicationConnectionString = builder.Configuration.GetConnectionString("ApplicationConnection") ?? throw new InvalidOperationException("Connection string 'ApplicationConnection' not found.");
    builder.Services.AddDbContextFactory<ApplicationDbContext>(options =>
        options.UseNpgsql(applicationConnectionString));
    var anotherConnectionString = builder.Configuration.GetConnectionString("AnotherConnection") ?? throw new InvalidOperationException("Connection string 'AnotherConnection' not found.");
    builder.Services.AddDbContextFactory<AnotherDbContext>(options =>
        options.UseNpgsql(anotherConnectionString));
    var oracleConnectionString = builder.Configuration.GetConnectionString("OracleConnection") ?? throw new InvalidOperationException("Connection string 'OracleConnection' not found.");
    builder.Services.AddDbContextFactory<OracleDbContext>(options =>
        options.UseOracle(oracleConnectionString));*/
    builder.Services.AddDatabaseDeveloperPageExceptionFilter();
    
    builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddRoles<IdentityRole>() // 大事!ロールを使用するとき。
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddSignInManager()
        .AddDefaultTokenProviders();
    
  2. まだ「Program.cs」ファイルで、アプリケーション データベースをシードしましょう
    using (var scope = app.Services.CreateScope())
    {
        var services = scope.ServiceProvider;
    
        var context = services.GetRequiredService<ApplicationDbContext>();
        context.Database.EnsureCreated();
        ApplicationDbInitializer.Initialize(context);
    }
    

データベース コンテキスト

各データベースのデータベース コンテキスト オブジェクトを作成しましょう

ApplicationDbContext

  1. Data\ApplicationDbContext.cs で定義されている ApplicationDbContext は、IdentityDbContext クラスから継承して認証データも処理します:
    using MyApp.Data.Models;
    using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore;
    
    namespace MyApp.Data
    {
        public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : IdentityDbContext<ApplicationUser>(options)
        {
            public DbSet<Datatype1> Data1 { get; set; }
            public DbSet<Datatype2> Data2 { get; set; }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);
    
                modelBuilder.Entity<Datatype1>().ToTable("Data1");
                modelBuilder.Entity<Datatype2>().ToTable("Data2");
            }
    
            public override void Dispose()
            {
                base.Dispose();
            }
    
            public override ValueTask DisposeAsync()
            {
                return base.DisposeAsync();
            }
        }
    }
    

AnotherDbContext

  1. Data\AnotherDbContext.cs で定義されている AnotherDbContext は、ただの DbContext クラスから継承します:
    using MyApp.Data.Models;
    using Microsoft.EntityFrameworkCore;
    
    namespace MyApp.Data
    {
        public class AnotherDbContext(DbContextOptions<AnotherDbContext> options) : DbContext(options)
        {
            public DbSet<Datatype3> Data3 { get; set; }
        
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);
    
                modelBuilder.Entity<Datatype3>().ToTable("Data3");
            }
    
            public override void Dispose()
            {
                base.Dispose();
            }
    
            public override ValueTask DisposeAsync()
            {
                return base.DisposeAsync();
            }
        }
    }
    

OracleDbContext

  1. Data\OracleDbContext.cs で定義されている OracleDbContext も、ただの DbContext クラスから継承します:
    using MyApp.Data.Models;
    using Microsoft.EntityFrameworkCore;
    
    namespace MyApp.Data
    {
        public class OracleDbContext(DbContextOptions<OracleDbContext> options) : DbContext(options)
        {
            public DbSet<Datatype4> Data4 { get; set; }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);
    
                modelBuilder.Entity<Datatype4>().ToTable("Data4");
            }
    
            public override void Dispose()
            {
                base.Dispose();
            }
    
            public override ValueTask DisposeAsync()
            {
                return base.DisposeAsync();
            }
    
        }
    }
    

マイグレーション

パッケージ マネージャー コンソールで:

  1. プロジェクトのフォルダーを開きます
    cd .\DMSofuSakiTorokuSystem
    
  2. アプリケーション データベースのみの初期移行を作成します
    Add-Migration Initial-Create -OutputDir Data\Migrations -Context ApplicationDbContext
    
  3. アプリケーション データベースのみを更新します
    Update-Database -Context ApplicationDbContext
    

※ 他の 2 つのデータベースはアプリケーションによって管理されません

シード

DbInitializer

  1. Data\ApplicationDbInitializer.cs ファイル内で、データベースにデータをシードするために Program.cs の中で呼び出した Initialize メソッドを含む ApplicationDbInitializer クラスを作成します。データベース初期化子は、Data\ApplicationDbContext.cs で定義された 2 つのテーブルと、認証に関連し IdentityDbContext から継承されたテーブルをシードします:
    using MyApp.Data.Models;
    using Microsoft.AspNetCore.Identity;
    using System.Globalization;
    
    namespace MyApp.Data
    {
        public class ApplicationDbInitializer
        {
            public static void Initialize(ApplicationDbContext context)
            {
                context.Database.EnsureCreated();
                if (context.Data1.Any() || context.Data2.Any() || context.Users.Any())
                {
                    // シードされました
                    return;
                }
                else
                {
                    // テーブル 1 ↓
                    IList<Datatype1> data1 = new List<Datatype1> {
                        new() { Id = 1, Name = "name1" },
                        new() { Id = 2, Name = "name2" }
                    };
                    context.Data1.AddRange(data1);
    
                    // テーブル 2 ↓
                    IList<Datatype2> data2 = new List<Datatype2> {
                        new() { Id = "1",  Value="value1"},
                        new() { Id = "2",  Value="value2"},
                    };
                    context.Data2.AddRange(data2);
    
                    // ↓ ↓ ↓ ↓ 認証のテーブル ↓ ↓ ↓ ↓
                    var hasher = new PasswordHasher<IdentityUser>();
                    IList<ApplicationUser> users = new List<ApplicationUser> {
                        new() {
                            Id = "user-id-01",
                            UserName = "user1",
                            NormalizedUserName = "USER1",
                            Email = "user1@domain.co.jp",
                            NormalizedEmail = "USER1@DOMAIN.CO.JP",
                            EmailConfirmed = true,
                            PasswordHash = hasher.HashPassword(null, "password1"),
                            SecurityStamp = Guid.NewGuid().ToString("D")
                        },
                        new() {
                            Id = "user-id-02",
                            UserName = "user2",
                            NormalizedUserName = "USER2",
                            Email = "user2@domain.co.jp",
                            NormalizedEmail = "USER2@DOMAIN.CO.JP",
                            EmailConfirmed = true,
                            PasswordHash = hasher.HashPassword(null, "password2"),
                            SecurityStamp = Guid.NewGuid().ToString("D")
                        }
                    };
                    context.Users.AddRange(users);
                    IList<IdentityRole> roles = new List<IdentityRole> {
                        new() {
                            Id="Admin",
                            Name="Admin"
                        },
                        new() {
                            Id="User",
                            Name="User"
                        },
                    };
                    context.Roles.AddRange(roles);
                    IList<IdentityUserRole<string>> userRoles = new List<IdentityUserRole<string>> {
                        new() {
                            UserId="user-id-01",
                            RoleId="Admin"
                        },
                        new() {
                            UserId="user-id-02",
                            RoleId="User"
                        }
                    };
                    context.UserRoles.AddRange(userRoles);
                }
                context.SaveChanges();
            }
        }
    }
    

注意点

データベース コンテキストとイニシャライザーの実装中に 2 つの問題が発生しました。

  • 移行の作成時に、データベース テーブルに一部の列が作成されません。
  • データベースは、UTC ではないため、Parse() または ParseExact() で文字列を解析することによって作成された DateTime 値を受け入れませんでした。

フィールド対プロパティ

  1. { get; set; } の代わりに = を使用してデフォルト値で Name パブリック プロパティを初期化すると、プロパティではなくフィールドを宣言することになりますEF Core がデータベース スキーマを構築するときにフィールドではなくプロパティを検索するため、EF Core はモデルにこれらのプロパティを含めない可能性があることを意味します。データベースのテーブルで「Name」カラムが作成されません。
    using Microsoft.EntityFrameworkCore;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    
    namespace MyApp.Data.Models
    {
        [Table("Data1"), PrimaryKey("Id")]
        public class PersonalDataRequest
        {
            [Key]                    // このカラムが作成されます。
            public int Id { get; set; } = -1;
            [NotMapped]
            public string? IdString  // このカラムは作成されないように指示されてます。
            {
                get => Id == -1 ? "" : Id.ToString();
                set { }
            }
            [Required]
            public string Name = ""; // このカラムが作成されません。
        }
    }
    
  2. Name」パブリック プロパティ宣言に { get; set; } を追加すると、EF Core は同じ名前の列をデータベース内のテーブルに追加します:
    [Required]
    public string Name { get; set; } = ""; // このカラムが作成されす!
    

DateTime

  1. データベースは、Parse() または ParseExact() で文字列を解析して作成された DateTime 値を受け入れません。これは、これらが UTC ではないためです。 DateTimeUTC にするには、DateTimeToUniversalTime() メソッドを呼び出す必要があります:
    string format = "yyyy/MM/dd HH:mm:ssK";
    CultureInfo provider = CultureInfo.InvariantCulture;
    Datatype dataItem = new() {
        // ...
        CreatetAt=DateTime.ParseExact("2024/04/20 09:00:00+00:00", format, provider).ToUniversalTime();
    }
    

参考

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?