Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -492,3 +492,5 @@ $RECYCLE.BIN/

# Vim temporary swap files
*.swp

.sonarlint/
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.3.3</Version>

<!-- Code Analysis & Quality -->
<AnalysisLevel>latest</AnalysisLevel>
Expand All @@ -14,6 +13,7 @@
<NoWarn>$(NoWarn);NU1901;NU1902;NU1903;NU1904</NoWarn>

<!-- Package Metadata -->
<Version>0.3.3</Version>
<Authors>HandyS11</Authors>
<Company>HandyS11</Company>
<PackageIcon>icon.png</PackageIcon>
Expand Down
91 changes: 69 additions & 22 deletions samples/erd/simple-context/EntityFramework/MyDbContext.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
using Microsoft.EntityFrameworkCore;

// ReSharper disable UnusedMember.Global
// ReSharper disable PropertyCanBeMadeInitOnly.Global
// ReSharper disable InconsistentNaming
// ReSharper disable EntityFramework.ModelValidation.UnlimitedStringLength

namespace EntityFramework;

public class MyDbContext : DbContext
Expand All @@ -9,43 +14,43 @@ public class MyDbContext : DbContext
public DbSet<Category> Categories { get; set; }
public DbSet<Publisher> Publishers { get; set; }
public DbSet<Review> Reviews { get; set; }
public DbSet<Profile> Profiles { get; set; }
public DbSet<BookDetail> BookDetails { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

// Author configuration
// One-to-One Optional: Author -> Profile
modelBuilder.Entity<Author>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(200);
entity.Property(e => e.Bio).HasMaxLength(1000);
});

// Publisher configuration
modelBuilder.Entity<Publisher>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(200);
entity.Property(e => e.Country).HasMaxLength(100);
});
entity.HasOne(e => e.Profile)
.WithOne(p => p.Author)
.HasForeignKey<Profile>(p => p.AuthorId)
.IsRequired(false);

// Category configuration
modelBuilder.Entity<Category>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(100);
entity.Property(e => e.Description).HasMaxLength(500);
entity.HasOne(e => e.Mentor)
.WithMany()
.HasForeignKey(e => e.MentorId);
});

// Book configuration
// One-to-One Required: Book -> BookDetail
modelBuilder.Entity<Book>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Title).IsRequired().HasMaxLength(300);
entity.Property(e => e.ISBN).HasMaxLength(13);

// One-to-Many: Publisher -> Books
entity.HasOne(e => e.Detail)
.WithOne(d => d.Book)
.HasForeignKey<BookDetail>(d => d.BookId)
.IsRequired();

// One-to-Many: Publisher -> Books (Required)
entity.HasOne(e => e.Publisher)
.WithMany(p => p.Books)
.HasForeignKey(e => e.PublisherId)
Expand All @@ -68,18 +73,34 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
j => j.HasOne<Book>().WithMany().HasForeignKey("BookId"));
});

// Review configuration
// Publisher configuration
modelBuilder.Entity<Publisher>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(200);
entity.Property(e => e.Country).HasMaxLength(100);
});

// Category configuration
modelBuilder.Entity<Category>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(100);
entity.Property(e => e.Description).HasMaxLength(500);
});

// Review configuration: One-to-Many Optional (Nullable BookId)
modelBuilder.Entity<Review>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Rating).IsRequired();
entity.Property(e => e.Comment).HasMaxLength(2000);

// One-to-Many: Book -> Reviews
entity.HasOne(e => e.Book)
.WithMany(b => b.Reviews)
.HasForeignKey(e => e.BookId)
.OnDelete(DeleteBehavior.Cascade);
.IsRequired(false)
.OnDelete(DeleteBehavior.SetNull);
});
}
}
Expand All @@ -90,10 +111,25 @@ public class Author
public string Name { get; set; } = string.Empty;
public string? Bio { get; set; }
public DateTime? BirthDate { get; set; }
public bool IsActive { get; set; }

public int? MentorId { get; set; }
public Author? Mentor { get; set; }

public Profile? Profile { get; set; }
public ICollection<Book> Books { get; set; } = [];
}

public class Profile
{
public int Id { get; set; }
public string BioData { get; set; } = string.Empty;
public string AvatarUrl { get; set; } = string.Empty;

public int AuthorId { get; set; }
public Author Author { get; set; } = null!;
}

public class Publisher
{
public int Id { get; set; }
Expand Down Expand Up @@ -124,18 +160,29 @@ public class Book
public int PublisherId { get; set; }
public Publisher Publisher { get; set; } = null!;

public BookDetail Detail { get; set; } = null!;
public ICollection<Author> Authors { get; set; } = [];
public ICollection<Category> Categories { get; set; } = [];
public ICollection<Review> Reviews { get; set; } = [];
}

public class BookDetail
{
public int Id { get; set; }
public string Summary { get; set; } = string.Empty;
public string Notes { get; set; } = string.Empty;

public int BookId { get; set; }
public Book Book { get; set; } = null!;
}

public class Review
{
public int Id { get; set; }
public int Rating { get; set; }
public string? Comment { get; set; }
public DateTime ReviewDate { get; set; }

public int BookId { get; set; }
public Book Book { get; set; } = null!;
public int? BookId { get; set; }
public Book? Book { get; set; }
}
148 changes: 70 additions & 78 deletions samples/erd/simple-context/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ relationships.

This example includes:

- **5 entities**: Author, Book, Category, Publisher, Review
- **7 entities**: Author, Book, Category, Publisher, Review, Profile, BookDetail
- **2 many-to-many relationships**: Book ↔ Author, Book ↔ Category
- **2 one-to-many relationships**: Publisher → Book, Book → Review
- **2 one-to-many relationships**: Publisher → Book (Required), Book → Review (Optional)
- **2 one-to-one relationships**: Author ↔ Profile (Optional), Book ↔ BookDetail (Required)

## Usage

Expand Down Expand Up @@ -40,53 +41,73 @@ The tool generates a **Mermaid ERD diagram** showing:
### Example Output

```mermaid
---
title: MyDbContext
---
erDiagram
Author {
int Id PK
string Name "required, max:200"
string Bio "string? | max:1000"
DateTime BirthDate "DateTime?"
}
Book {
int Id PK
string Title "required, max:300"
string ISBN "string? | max:13"
DateTime PublishedDate
int PageCount
int PublisherId FK
}
Category {
int Id PK
string Name "required, max:100"
string Description "string? | max:500"
}
Publisher {
int Id PK
string Name "required, max:200"
string Country "string? | max:100"
DateTime FoundedDate "DateTime?"
}
Review {
int Id PK
int Rating "required"
string Comment "string? | max:2000"
DateTime ReviewDate
int BookId FK
}
AuthorBook {
int AuthorId PK,FK
int BookId PK,FK
}
BookCategory {
int BookId PK,FK
int CategoryId PK,FK
}
Publisher ||--o{ Book : "Books"
Book ||--o{ Review : "Reviews"
Author ||--o{ AuthorBook : ""
Book ||--o{ AuthorBook : ""
Book ||--o{ BookCategory : ""
Category ||--o{ BookCategory : ""
Author {
int Id PK
int MentorId FK
string Bio "max:1000"
DateTime BirthDate
bool IsActive
string Name "required, max:200"
}
AuthorBook {
int AuthorId PK,FK
int BookId PK,FK
}
Book {
int Id PK
int PublisherId FK
string ISBN "max:13"
int PageCount
DateTime PublishedDate
string Title "required, max:300"
}
BookCategory {
int BookId PK,FK
int CategoryId PK,FK
}
BookDetail {
int Id PK
int BookId FK
string Notes "required"
string Summary "required"
}
Category {
int Id PK
string Description "max:500"
string Name "required, max:100"
}
Profile {
int Id PK
int AuthorId FK
string AvatarUrl "required"
string BioData "required"
}
Publisher {
int Id PK
string Country "max:100"
DateTime FoundedDate
string Name "required, max:200"
}
Review {
int Id PK
int BookId FK
string Comment "max:2000"
int Rating "required"
DateTime ReviewDate
}
Author ||--o{ Author : ""
Author ||--o{ AuthorBook : ""
Author |o--|| Profile : ""
Book ||--o{ AuthorBook : ""
Book ||--o{ BookCategory : ""
Book ||--|| BookDetail : ""
Book ||--o{ Review : ""
Category ||--o{ BookCategory : ""
Publisher ||--o{ Book : ""
```

### Rendered Diagram
Expand All @@ -98,6 +119,7 @@ The Mermaid diagram renders as a visual ERD showing:
- `||--o{` = One-to-Many (required)
- `|o--o{` = One-to-Many (optional)
- `||--||` = One-to-One (required)
- `||--o|` = One-to-One (optional)
- `}|--|{` = Many-to-Many (shown as two One-to-Many via join table)

## Key Features
Expand Down Expand Up @@ -160,33 +182,3 @@ The output is **GitHub/GitLab compatible** Mermaid syntax, so you can:
1. Copy the output directly into your `README.md`
2. Commit it to version control
3. It will render automatically in GitHub, GitLab, and other platforms

## Example Workflow

```bash
# 1. Generate ERD from your DbContext
projgraph erd MyProject/Data/ApplicationDbContext.cs > docs/database-erd.md

# 2. Commit to repository
git add docs/database-erd.md
git commit -m "docs: Add database ERD diagram"

# 3. Push - diagram will render automatically on GitHub!
git push
```

## Tips

💡 **Nullable Types**: Original C# types (including `?` for nullable) are preserved in comments

💡 **Join Tables**: Many-to-many relationships create explicit join table entities for clarity

💡 **Inheritance**: Base class properties are automatically included (e.g., `Id`, `CreatedAt` from `AuditEntity`)

💡 **MaxLength Constraints**: `[MaxLength(N)]` attributes are extracted and displayed as `max:N`

💡 **Complex Schemas**: Works with large DbContexts containing dozens of entities

💡 **Entity Framework Core**: Supports EF Core 6.0+ including fluent API configurations

💡 **Documentation**: Perfect for maintaining up-to-date database schema documentation
1 change: 0 additions & 1 deletion specs/002-dbcontext-erd/data-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ Represents a link between two entities.
- `TargetEntity`: String
- `Type`: Enum (`OneToOne`, `OneToMany`, `ManyToMany`)
- `IsRequired`: Boolean
- `Label`: String (usually the navigation property name)

## State Transitions

Expand Down
Loading