ASP.NET Core Database First With Razor Pages


  1. 說明
    1. Nuget Packages
    2. 建立 DBContext
    3. 依賴注入
      1. 連線字串集中 appsettings
      2. 依賴注入所需的介面與實作類別
    4. Razoe Page

筆記 ASP.NET Core Razor Pages 專案如何使用 Database First 的方式進行開發。

logo

說明

Nuget Packages

首先由 Nuget 進行下列的相依元件安裝,Proxies 不一定需要安裝,如果需要使用 UseLazyLoadingProxies 則必須安裝。

Install-PackageMicrosoft.EntityFrameworkCore.Design -Version 6.0.10
Install-PackageMicrosoft.EntityFrameworkCore.SqlServer -Version 6.0.10
Install-PackageMicrosoft.EntityFrameworkCore.Tools -Version 6.0.10
Install-PackageMicrosoft.EntityFrameworkCore.Proxies -Version 6.0.10

建立 DBContext

使用 Nuget Package Manager Console 建立 DBContext 以及自動由 DB Table 產生 Model。

Scaffold-DbContext
  -Connection "Server=.;Database=Pokemon;Trusted_Connection=True;"
  Microsoft.EntityFrameworkCore.SqlServer
  -OutputDir Models -context PokemonContext

完成上述指令後,會自動產生下列的兩個類別:

/Models/Pokemon.cs

public partial class Pokemon
{
    public int Pno { get; set; }
    public string Name { get; set; } = null!;
    public string NameCt { get; set; } = null!;
    public byte Hp { get; set; }
    public byte Atk { get; set; }
    public byte Def { get; set; }
    public byte SpAtk { get; set; }
    public byte SpDef { get; set; }
    public byte Speed { get; set; }
    public string Type1 { get; set; } = null!;
    public string? Type2 { get; set; }
}

PokemonContext.cs

public partial class PokemonContext : DbContext
{
    public PokemonContext(DbContextOptions<PokemonContext> options)
        : base(options)
    {
    }

    public virtual DbSet<Pokemon> Pokemons { get; set; } = null!;

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            optionsBuilder.UseSqlServer("Server=.;Database=Pokemon;Trusted_Connection=True;");
        }
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Pokemon>(entity =>
        {
            entity.HasKey(e => e.Pno);

            entity.Property(e => e.Pno).ValueGeneratedNever();

            entity.Property(e => e.Hp).HasColumnName("HP");

            entity.Property(e => e.Name)
                .HasMaxLength(50)
                .IsUnicode(false);

            entity.Property(e => e.NameCt)
                .HasMaxLength(50)
                .HasColumnName("Name_CT");

            entity.Property(e => e.Type1)
                .HasMaxLength(10)
                .IsUnicode(false)
                .IsFixedLength();

            entity.Property(e => e.Type2)
                .HasMaxLength(10)
                .IsUnicode(false)
                .IsFixedLength();
        });

        OnModelCreatingPartial(modelBuilder);
    }

    partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

在自動產生的 PokemonContext.cs 當中,我們需要主動移除沒有參數的建構子,否則系統會發生錯誤。

尚未移除無參數建構子,系統會發生錯誤截圖

依賴注入

首先需要在 Program.cs 當中加入 AddDbContextPool,會需要提供 Context 類別以及,設定相關的 option。

原本產生的連線字串是直接接字串方式提供,調整為儲存在 appsettings.json 的方式取用。

接著再用依賴注入的方式,採用 AddScoped 以 Respository Pattern 的方式將介面以及實作的類別提供,完成注入。

Program.cs

using Microsoft.EntityFrameworkCore;

builder.Services.AddDbContextPool<PokemonContext>(
        options => options
            .UseLazyLoadingProxies()
            .UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddScoped<IPokemonRepository, PokemonSqlServerRepository>();

var app = builder.Build();

連線字串集中 appsettings

appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Server=.;Database=Pokemon;Trusted_Connection=True;"
  }
}

集中後,只要在 Program.cs 使用下列的語法就可以取得連線字串的內容:

builder.Configuration.GetConnectionString("DefaultConnection")

預設由 Scaffold 所產生出的 Context,會主動檢查連線字串,在未設定時使用預設:

PokemonContext.cs

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    {
        optionsBuilder.UseSqlServer("Server=.;Database=Pokemon;Trusted_Connection=True;");
    }
}

因為已經集中在 appsettings.json 故可以取消這個設定,或者設定為其他備援的資料庫連線字串 🙂

依賴注入所需的介面與實作類別

因為要採用依賴注入的方式,並且結合 Respository Pattern 來設計,因此需要先定義介面以及實作類別。

IPokemonRepository.cs

namespace PokemonCenter.Models
{
	public interface IPokemonRepository
	{
        IEnumerable<Pokemon> GetPokemons();
        Pokemon GetPokemon(int pid);
    }
}

PokemonSqlServerRepository.cs

namespace PokemonCenter.Models
{
	public class PokemonSqlServerRepository : IPokemonRepository
	{
    private readonly PokemonContext _context;

    public PokemonSqlServerRepository(PokemonContext context)
    {
        _context = context;
    }
    public Pokemon GetPokemon(int pid)
		{
			return _context.Pokemons.FirstOrDefault(p => p.Pno == pid) ?? new Pokemon();
		}

		public IEnumerable<Pokemon> GetPokemons()
		{
			return _context.Pokemons;
		}
	}
}

Razoe Page

public class PokedexModel : PageModel
{

    private IPokemonRepository PokemonRepo { get;}
    public IEnumerable<Pokemon> Pokemons { get; set; } = null!;
    public PokedexModel(IPokemonRepository pokemonRepo)
    {
        PokemonRepo = pokemonRepo;
    }
    public void OnGet()
    {
        Pokemons = PokemonRepo.GetPokemons().ToList();
    }
}
@page
@model PokemonCenter.Pages.PokedexModel

<table class="table">
	<thead>
		<tr>
			<th>Name</th>
			<th>名稱</th>
			<th>屬性</th>
			<th></th>
		</tr>
	</thead>
	<tbody>
		@foreach (var pokemon in Model.Pokemons)
		{
			<tr class="align-middle">
				<td><a asp-page="/Pokemon" asp-route-Pid="@pokemon.Pno">@pokemon.Name</a></td>
				<td>@pokemon.NameCt</td>
				<td>@pokemon.Type1 @(pokemon.Type2 == null ? "" : $" / {pokemon.Type2}")</td>
				<td>
					<img src="~/imgs/@(pokemon.Pno).png" alt="" />
				</td>
			</tr>
		}
	</tbody>
</table>