우선 Supabase 에 관해서는 아래의 글을 참고하시기 바랍니다.
영상에서는 매우 짧게 언급한 부분인데, Supabase는API 접근 뿐만 아니라, DB (Postgresql) 서버로 직접 접근도 허용하기 때문에, EF Core의 관리도구 DB를 관리를 할 수 있습니다.
이 글은 EF 도구를 이용해서 Supabase 테이블을 관리하는 방법을 보여줍니다.
- Supabase 프로젝트 생성
위 글의 영상을 참고해서 프로젝트를 생성합니다.
예제를 위해 아래와 같이 생성했습니다.
프로젝트를 생성할 때, 데이터 베이스 관리자 계정(postgres)의 비밀 번호를 잘 저장해 둡니다.
- 1 연결 문자열 저장.
대시보드 => 설정 => 데이터베이스 => 연결문자열 => .Net 을 선택해서, 연결 문자열을 복사해서 잘 저장해둡니다.
- DB 관리 용 프로젝트 생성.
클래스 라이브러리 프로젝트를 생성합니다.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Folder Include="EntityConfiguraions\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="EFCore.NamingConventions" Version="7.0.2" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
사용한 패키지는 아래와 같습니다.
- EFCore.NamingConventions : DB 식별자를 스네이크 케이스로 만들기 위한 컨벤션 생성도구입니다.
- Microsoft.EntityFrameworkCore.Design : 디자인을 위한 패키지입니다.
- Npgsql.EntityFrameworkCore.PostgreSQL: DbContext 의 PostgreSQL 제공자입니다.
Supabase API를 사용하는 클라이언트 앱은 CRUD를 위해, DbContext(와 IQueryable) 대신, Supabase가 제공하는 API를 사용합니다. (이에 관해서는 API 문서를 확인하시기 바랍니다.)
이때, Supabase가 발급한 API key를 이용하게 되는데, 이 키는 보안 문제에 대해 좀 더 자유롭습니다.
이 프로젝트는 Supabase의 DB를 관리하기 위한 용도로, 클라이언트 앱과 무관해서 함께 배포되지 않습니다. 그렇기 때문에 이 프로젝트에는 시크릿(DB 암호 등)을 마구 써도 상관이 없습니다.
- 모델 클래스 생성.
Supabase는 사용자 관리를 auth 스키마의 user 테이블을 통해 자체적으로 관리하는데, 사용자는 이 테이블을 관리할 수 없습니다. 이는, EF 관리도구로 관리할 수 없음을 의미합니다.
따라서, 사용자 프로필을 위한 테이블을 public 스키마에 별도로 만들고, public 스키마의 다른 테이블들이 이 테이블을 마치 User 테이블로 참조하도록 만듭니다.
public class UserProfile
{
public Guid Id { get; set; }
public DateOnly? Birth { get; set; }
public Gender? Gender { get; set; }
public string? Address { get; set; }
public string? Zipcode { get; set; }
public string? City { get; set; }
public string? Country { get; set; }
public Guid UserId { get; set; }
}
이 테이블과 auth.user 테이블의 관계는 EF 도구가 아닌, Supabase 대시 보드에서 수동으로 설정합니다. (잠시 후에 보여집니다)
- DbContext
public class AppDbContext : DbContext
{
readonly string _dbConnectionString;
public AppDbContext(string connectionString)
{
_dbConnectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseNpgsql(_dbConnectionString) // DB 제공자
.UseSnakeCaseNamingConvention(); // 스네이크 케이스 컨벤션
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// PK에 UUID 생성 함수 사용.
modelBuilder.HasPostgresExtension("uuid-ossp");
// IEntityTypeConfiguration 구현한 모든 객체들의 테이블 생성.
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
}
}
이 객체의 생성자는 DbContext의 일반적인 형태와 달리, string 을 매개변수로 받도록 했습니다.
이는 아무 생각 없이 CRUD 용으로 사용했을 때, 에러를 유발하여 상기시키도록 만들기 위한 것입니다.
- 엔티티 타입 설정자
internal class UserProfileConfiguration : IEntityTypeConfiguration<UserProfile>
{
public void Configure(EntityTypeBuilder<UserProfile> builder)
{
builder.Property(x => x.UserId)
.IsRequired();
}
}
보통 데이터 모델에 특성을 부여하여 컬럼 속성을 지정하는 어노테이션 방식을 많이 사용하시는데, 저는 configuration 방식을 선호합니다.
보시다시피, UserId 속성은 관계 설정이 없는 그냥 데이터 컬럼에 지나지 않습니다. 따라서, EF 도구는 이 컬럼에 대해 별다른 관리를 하지 않습니다.
- 디자인 타임 팩토리
EF 디자인 도구는 디자인 타임에 DbContext를 생성할 때, IDesignTimeDbContextFactory<T> 가 있다면, 그 객체의 CreateDbContext를 호출합니다.
public class DTFactoryAppDbContext : IDesignTimeDbContextFactory<AppDbContext>
{
public AppDbContext CreateDbContext(string[] args)
{
var cs = "User Id=postgres;Password=LE705uGXd8NnbZS7;Server=db.alpnbkjusdyyaobvvskf.supabase.co;Port=5432;Database=postgres";
return new(cs);
}
}
앞서 저장한 데이터 베이스 비밀번호를 연결 문자열에 삽입하여 생성자에 주입했습니다.
앞서 언급했 듯이 이 프로젝트는 배포되는 프로젝트가 아니기에, 코드에 연결문자열을 직접 노출해도 상관이 없습니다.
- DB 관리
모든 준비는 끝났습니다. 이제 부터는 code-first 접근법으로 DB를 관리하면 됩니다.
예제를 위해, cli 툴로 마이그레이션을 생성하고, 데이터 베이스를 업데이트합니다.
dotnet ef migrations add Initial
dotnet ef database update
- 관계 수동 설정.
앞서 설명한 대로, auth.users 테이블과 public.user_profile의 관계는 ef 도구로 설정할 수 없기 때문에, Supabase 대시보드에서 이 둘의 관계를 맺어 줍니다.
모든 게 문제가 없다면, Supabase 에 public.user_profile 테이블이 생성되어 있을 것입니다.
user_id 컬럼의 제일 오른 쪽에 있는 편집 버튼을 누르고
![]()
“외래키 관계 추가” 버튼을 누르고,
아래와 같이 설정을 합니다.
보시다시피, 사용자가 탈퇴를 하면, 관련 프로필도 삭제되도록 Cascade delete을 설정했습니다.
수동으로 설정되는 부분은 이 곳이 전부입니다.
이제부터는 클라이언트 앱들은 public.user_profile 을 마치 user 처럼 사용하면 됩니다.
예제를 위해, Entity를 하나 더 추가합니다.
Entity 추가
프로젝트에 UserProfile 과 관계를 맺는 Destination 엔티티를 추가합니다.
public class Destination
{
public Guid Id { get; set; }
public string Alias { get; set; } = string.Empty;
public string ConsgineeName { get; set; } = string.Empty;
public string PhoneNumber { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
public string Zipcode { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string Country { get; set; } = string.Empty;
// Navigation
public UserProfile UserProfile { get; set; } = new();
}
관계를 Navigation 속성으로 표현했는데, 이로 인해 user_profile_id 컬럼이 테이블에 추가됩니다.
auth.users 의 Row 가 Delete 되었을 때, 프로필 테이블의 행위를 정의하기 위해, 설정 객체를 추가합니다.
internal class DestinationConfiguration : IEntityTypeConfiguration<Destination>
{
public void Configure(EntityTypeBuilder<Destination> builder)
{
builder.HasOne(x => x.UserProfile)
.WithMany()
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
}
}
다시 DB를 업데이트 합니다.
dotnet ef migrations add DestinationCreated
dotnet ef database update
대시보드를 통해 destination 테이블의 user_profile_id 의 외래키 관계를 확인해보면, 코드에서 설정한 대로 설정되었음을 알 수 있습니다.
결론
@honeyhead 님께서 말씀하셨듯, 완벽하지는 않지만 Supabase는 C# API 를 제공합니다.
데스크탑 앱, 모바일 앱을 위한 다양한 프레임워크가 존재하는 닷넷의 입장에서는, API 서비스를 쉽고 빠르게 구축할 수 있어, 큰 장점이 될 것 같습니다. 또한 무료로 사용할 수 있는 것도 큰 장점이고, 유료도 비싸지 않아 초기 운영비 절감에 많은 도움이 될 것 같습니다.
여기에 EF 도구를 이용하면, 데이터 베이스 관리가 더 안전하고 효율적이기 때문에, 다른 개발 플랫폼보다 사용 용이성이 더 크다고 할 수 있을 것 같습니다.
물론 부잣집 개발자 분들은 공감하시지 못하겠지만요. ^^





