Compare commits
4 Commits
master
...
feat/setup
| Author | SHA1 | Date | |
|---|---|---|---|
| 2be5694a6f | |||
| 196a07ef9a | |||
| 194609720a | |||
| 171a60089b |
63
Configurations.cs
Normal file
63
Configurations.cs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Models.Entities;
|
||||||
|
using Configurations;
|
||||||
|
|
||||||
|
public class ApplicationDbContext : DbContext
|
||||||
|
{
|
||||||
|
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
|
||||||
|
: base(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DbSet<Department> Departments => Set<Department>();
|
||||||
|
public DbSet<Role> Roles => Set<Role>();
|
||||||
|
public DbSet<Account> Accounts => Set<Account>();
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
base.OnModelCreating(modelBuilder);
|
||||||
|
|
||||||
|
// ®M¥Î©Ò¦³ EntityTypeConfiguration
|
||||||
|
modelBuilder.ApplyConfiguration(new DepartmentConfiguration());
|
||||||
|
modelBuilder.ApplyConfiguration(new RoleConfiguration());
|
||||||
|
modelBuilder.ApplyConfiguration(new AccountConfiguration());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int SaveChanges()
|
||||||
|
{
|
||||||
|
ApplyTimestamps();
|
||||||
|
return base.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
ApplyTimestamps();
|
||||||
|
return base.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyTimestamps()
|
||||||
|
{
|
||||||
|
var utcNow = DateTime.UtcNow;
|
||||||
|
|
||||||
|
foreach (var entry in ChangeTracker.Entries<BaseEntity>())
|
||||||
|
{
|
||||||
|
switch (entry.State)
|
||||||
|
{
|
||||||
|
case EntityState.Added:
|
||||||
|
entry.Entity.CreatedAt = utcNow;
|
||||||
|
entry.Entity.UpdatedAt = null;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EntityState.Modified:
|
||||||
|
// Á×§K×§ï CreatedAt
|
||||||
|
entry.Property(e => e.CreatedAt).IsModified = false;
|
||||||
|
entry.Entity.UpdatedAt = utcNow;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
89
Configurations/AccountConfiguration.cs
Normal file
89
Configurations/AccountConfiguration.cs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using Models.Entities;
|
||||||
|
|
||||||
|
namespace Configurations
|
||||||
|
{
|
||||||
|
public class AccountConfiguration : IEntityTypeConfiguration<Account>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<Account> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("accounts");
|
||||||
|
|
||||||
|
builder.HasKey(a => a.AccountId);
|
||||||
|
|
||||||
|
builder.Property(a => a.AccountId)
|
||||||
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
builder.Property(a => a.Name)
|
||||||
|
.HasColumnName("name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100);
|
||||||
|
|
||||||
|
builder.Property(a => a.Username)
|
||||||
|
.HasColumnName("username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50);
|
||||||
|
|
||||||
|
builder.Property(a => a.Password)
|
||||||
|
.HasColumnName("password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256);
|
||||||
|
|
||||||
|
builder.Property(a => a.Email)
|
||||||
|
.HasColumnName("email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(254);
|
||||||
|
|
||||||
|
builder.Property(a => a.DepartmentId)
|
||||||
|
.HasColumnName("department_id")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.RoleId)
|
||||||
|
.HasColumnName("role_id")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Status)
|
||||||
|
.HasColumnName("status")
|
||||||
|
.HasConversion<string>()
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20);
|
||||||
|
|
||||||
|
builder.Property(a => a.CreatedAt)
|
||||||
|
.HasColumnName("created_at")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.UpdatedAt)
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
builder.Property(a => a.ModifiedBy)
|
||||||
|
.HasColumnName("modified_by");
|
||||||
|
|
||||||
|
// 唯一索引
|
||||||
|
builder.HasIndex(a => a.Username)
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("UX_accounts_username");
|
||||||
|
|
||||||
|
builder.HasIndex(a => a.Email)
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("UX_accounts_email");
|
||||||
|
|
||||||
|
// 關聯與刪除行為
|
||||||
|
builder.HasOne(a => a.Department)
|
||||||
|
.WithMany(d => d.Accounts)
|
||||||
|
.HasForeignKey(a => a.DepartmentId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
builder.HasOne(a => a.Role)
|
||||||
|
.WithMany(r => r.Accounts)
|
||||||
|
.HasForeignKey(a => a.RoleId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
// 自參照外鍵:modified_by -> accounts.account_id (SET NULL)
|
||||||
|
builder.HasOne<Account>()
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(a => a.ModifiedBy)
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Configurations/DepartmentConfiguration.cs
Normal file
24
Configurations/DepartmentConfiguration.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using Models.Entities;
|
||||||
|
|
||||||
|
namespace Configurations
|
||||||
|
{
|
||||||
|
public class DepartmentConfiguration : IEntityTypeConfiguration<Department>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<Department> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("departments");
|
||||||
|
|
||||||
|
builder.HasKey(d => d.DepartmentId);
|
||||||
|
|
||||||
|
builder.Property(d => d.DepartmentId)
|
||||||
|
.HasColumnName("department_id");
|
||||||
|
|
||||||
|
builder.Property(d => d.DepartmentName)
|
||||||
|
.HasColumnName("department_name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Configurations/RoleConfiguration.cs
Normal file
24
Configurations/RoleConfiguration.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using Models.Entities;
|
||||||
|
|
||||||
|
namespace Configurations
|
||||||
|
{
|
||||||
|
public class RoleConfiguration : IEntityTypeConfiguration<Role>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<Role> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("roles");
|
||||||
|
|
||||||
|
builder.HasKey(r => r.RoleId);
|
||||||
|
|
||||||
|
builder.Property(r => r.RoleId)
|
||||||
|
.HasColumnName("role_id");
|
||||||
|
|
||||||
|
builder.Property(r => r.RoleName)
|
||||||
|
.HasColumnName("role_name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Models/Entities/Account.cs
Normal file
28
Models/Entities/Account.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Models.Entities
|
||||||
|
{
|
||||||
|
public class Account : BaseEntity
|
||||||
|
{
|
||||||
|
public int AccountId { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string Username { get; set; } = string.Empty;
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
public string Email { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public int DepartmentId { get; set; }
|
||||||
|
public int RoleId { get; set; }
|
||||||
|
public AccountStatus Status { get; set; }
|
||||||
|
|
||||||
|
public virtual Department? Department { get; set; }
|
||||||
|
public virtual Role? Role { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AccountStatus
|
||||||
|
{
|
||||||
|
Enabled,
|
||||||
|
Disabled,
|
||||||
|
Unverified
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Models/Entities/Department.cs
Normal file
12
Models/Entities/Department.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Models.Entities
|
||||||
|
{
|
||||||
|
public class Department
|
||||||
|
{
|
||||||
|
public int DepartmentId { get; set; }
|
||||||
|
public string DepartmentName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public virtual ICollection<Account> Accounts { get; set; } = new HashSet<Account>();
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Models/Entities/Role.cs
Normal file
12
Models/Entities/Role.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Models.Entities
|
||||||
|
{
|
||||||
|
public class Role
|
||||||
|
{
|
||||||
|
public int RoleId { get; set; }
|
||||||
|
public string RoleName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public virtual ICollection<Account> Accounts { get; set; } = new HashSet<Account>();
|
||||||
|
}
|
||||||
|
}
|
||||||
46
Program.cs
Normal file
46
Program.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// 讀取連線字串
|
||||||
|
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
|
||||||
|
?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
|
||||||
|
|
||||||
|
// EF Core + Pomelo MySQL 註冊
|
||||||
|
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
||||||
|
{
|
||||||
|
var serverVersion = new MariaDbServerVersion(new Version(10, 6, 0));
|
||||||
|
|
||||||
|
options.UseMySql(connectionString, serverVersion, mySqlOptions =>
|
||||||
|
{
|
||||||
|
// 字元集設定
|
||||||
|
mySqlOptions.CharSet(CharSet.Utf8Mb4);
|
||||||
|
mySqlOptions.CharSetBehavior(CharSetBehavior.AppendToAllColumns);
|
||||||
|
// 可選:重試策略
|
||||||
|
mySqlOptions.EnableRetryOnFailure(5, TimeSpan.FromSeconds(10), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 開發環境顯示更詳細的資料庫日誌
|
||||||
|
if (builder.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
options.EnableDetailedErrors();
|
||||||
|
options.EnableSensitiveDataLogging();
|
||||||
|
options.LogTo(Console.WriteLine, LogLevel.Information, DbContextLoggerOptions.DefaultWithLocalTime);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Logging(只在開發環境加強輸出 EF Core 訊息)
|
||||||
|
if (builder.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
builder.Logging.AddConsole()
|
||||||
|
.AddFilter("Microsoft.EntityFrameworkCore.Database.Command", LogLevel.Information)
|
||||||
|
.AddFilter("Microsoft.EntityFrameworkCore.Infrastructure", LogLevel.Information);
|
||||||
|
}
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
Loading…
Reference in New Issue
Block a user