GoNtext brings the familiar Entity Framework Core patterns to Go, providing a type-safe, LINQ-style ORM with automatic change tracking, migrations, and fluent querying capabilities.
- 🎯 EF Core-Style API: Familiar patterns for .NET developers
- 🔍 LINQ Queries: Type-safe querying with method chaining
- 🏗️ Entity-based Queries: GORM-style struct patterns with comparison operators ✨
- 📊 Enhanced Aggregations: Entity-based Sum, Average, Min, Max operations ✨
- 🔗 Include/Select Support: Load relationships and specific fields
- 📈 Change Tracking: Automatic entity change detection
- 🔄 Migrations: Code-first database migrations with Go files
- 💾 DbSets: Type-safe entity collections with generics
- 🗃️ Multiple Databases: PostgreSQL, MySQL, SQLite support
- 🐘 PostgreSQL Pascal Case: Automatic field name translation with quoted identifiers
- 🚀 Zero Configuration: Automatic database-specific optimizations
- ⚡ Field Validation: Runtime validation with clear error messages
- 🔢 Comparison Operators: Support for >,<,>=,<=,!=in all query patterns ✨
go get github.com/shepherrrd/gontextpackage main
import (
    "github.com/google/uuid"
    "github.com/shepherrrd/gontext"
)
// Define your entities
type User struct {
    ID       uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()"`
    Username string    `gorm:"uniqueIndex;not null"`
    Email    string    `gorm:"uniqueIndex;not null"`
    Name     string    `gorm:"not null"`
}
// Create your DbContext
type AppContext struct {
    *gontext.DbContext
    Users *gontext.LinqDbSet[User]
}
func NewAppContext(connectionString string) (*AppContext, error) {
    ctx, err := gontext.NewDbContext(connectionString, "postgres")
    if err != nil {
        return nil, err
    }
    users := gontext.RegisterEntity[User](ctx)
    return &AppContext{
        DbContext: ctx,
        Users:     users,
    }, nil
}GoNtext automatically handles PostgreSQL case-sensitive identifiers! No manual configuration required.
When using PostgreSQL, GoNtext automatically:
- Uses struct names as table names: Userstruct →"User"table (notusers)
- Uses field names as column names: Usernamefield →"Username"column
- Quotes all identifiers: Proper PostgreSQL case-sensitive handling
- Translates all queries: WHERE, ORDER BY, SELECT - everything works automatically
type User struct {
    ID           uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()"`
    Username     string    `gorm:"uniqueIndex;not null"`
    Email        string    `gorm:"uniqueIndex;not null"`
    PasswordHash string    `gorm:"not null"`
    IsActive     bool      `gorm:"not null;default:true"`
    CreatedAt    time.Time `gorm:"autoCreateTime"`
}
// Setup - Zero configuration needed!
ctx, _ := gontext.NewDbContext("postgres://user:pass@localhost/db", "postgres")
userSet := gontext.RegisterEntity[User](ctx)
// All queries automatically use quoted PostgreSQL identifiers:
user, _ := userSet.WhereField("Username", "john").FirstOrDefault()
// SQL: SELECT * FROM "User" WHERE "Username" = 'john'
users, _ := userSet.OrderByField("Email").ToList()
// SQL: SELECT * FROM "User" ORDER BY "Email" ASC
userSet.WhereField("IsActive", true).Delete()
// SQL: DELETE FROM "User" WHERE "IsActive" = true- ✅ Table Names: Userstruct becomes"User"table (Pascal case)
- ✅ Column Names: Usernamefield becomes"Username"column (Pascal case)
- ✅ All Query Types: INSERT, SELECT, UPDATE, DELETE - all automatically translated
- ✅ Complex Queries: WHERE with AND/OR/parentheses, LIKE, IN - all supported
- ✅ Comparison Operators: Support for >,<,>=,<=,!=in Where conditions ✨
- ✅ Zero Boilerplate: No TableName()methods needed, no manual quoting
OLD WAY (not needed anymore):
func (User) TableName() string {
    return "User" // ❌ Don't do this anymore!
}NEW WAY (automatic):
type User struct {
    // Just define your struct - GoNtext handles the rest! ✅
    Username string
    Email    string
}GoNtext automatically uses the struct name (User) as the table name with proper PostgreSQL quoting.
GoNtext supports multiple query patterns for maximum flexibility!
// Age comparisons
users, _ := ctx.Users.Where("Age", ">18").ToList()         // Age > 18
users, _ := ctx.Users.Where("Age", ">=21").ToList()        // Age >= 21  
users, _ := ctx.Users.Where("Age", "<65").ToList()         // Age < 65
users, _ := ctx.Users.Where("Age", "<=30").ToList()        // Age <= 30
users, _ := ctx.Users.Where("Age", "!=25").ToList()        // Age != 25
// String comparisons
users, _ := ctx.Users.Where("Username", "!=admin").ToList() // Username != 'admin'
// Numeric fields
files, _ := ctx.Files.Where("Size", ">1048576").ToList()   // Size > 1MB// Static typing with entity structs
user, _ := ctx.Users.Where(&User{Email: "[email protected]"}).FirstOrDefault()
// Entity-based queries with comparison operators ✨ NEW!
users, _ := ctx.Users.Where(&User{Age: ">18"}).ToList()           // Age > 18
files, _ := ctx.Files.Where(&File{Size: ">=1048576"}).ToList()    // Size >= 1MB
users, _ := ctx.Users.Where(&User{Username: "!=admin"}).ToList()  // Username != 'admin'
// Combined entity patterns
activeAdults, _ := ctx.Users.Where(&User{IsActive: true, Age: ">=18"}).ToList()// Field-based OR with operators
users, _ := ctx.Users.Where("Age", ">=18").Or("Role", "admin").ToList()
users, _ := ctx.Users.Where("Status", "!=inactive").Or("Priority", ">5").ToList()
// Entity-based OR operations ✨ NEW!
users, _ := ctx.Users.Where(&User{Role: "admin"}).Or(&User{Age: ">=65"}).ToList()
users, _ := ctx.Users.Where(&User{Email: "[email protected]"}).Or(&User{Username: "admin"}).ToList()
// Mixed pattern OR operations
users, _ := ctx.Users.Where("IsActive", true).Or(&User{Role: "admin"}).ToList()GoNtext provides multiple patterns for aggregations and ordering!
// Entity-based Sum - specify field using entity pattern
totalSize, _ := ctx.Files.Sum(&File{Size: 0})                    // Sum file sizes
totalRevenue, _ := ctx.Orders.Sum(&Order{Amount: 0.0})           // Sum order amounts
// Entity-based Average  
avgAge, _ := ctx.Users.Average(&User{Age: 0})                    // Average user age
avgRating, _ := ctx.Products.Average(&Product{Rating: 0.0})      // Average product rating
// Entity-based Min/Max
minPrice, _ := ctx.Products.Min(&Product{Price: 0.0})            // Minimum price
maxScore, _ := ctx.GameResults.Max(&GameResult{Score: 0})        // Maximum score
// Chain with WHERE conditions
totalAdultAge, _ := ctx.Users.Where("Age", ">=18").Sum(&User{Age: 0})
avgActiveUserAge, _ := ctx.Users.Where(&User{IsActive: true}).Average(&User{Age: 0})// Field name aggregations (backward compatible)
totalSize, _ := ctx.Files.SumField("Size")
avgAge, _ := ctx.Users.AverageField("Age")  
minPrice, _ := ctx.Products.MinField("Price")
maxScore, _ := ctx.GameResults.MaxField("Score")
// With comparison operators
expensiveItemsTotal, _ := ctx.Products.Where("Price", ">100").SumField("Price")
youngUsersAvgAge, _ := ctx.Users.Where("Age", "<25").AverageField("Age")// Multiple ordering patterns
users, _ := ctx.Users.OrderBy("Name").ToList()                     // String field name
users, _ := ctx.Users.OrderBy(func(u User) interface{} {            // Function selector
    return u.CreatedAt 
}).ToList()
// Descending order
users, _ := ctx.Users.OrderByDescending("Age").ToList()             // String field name  
users, _ := ctx.Users.OrderByDescending(func(u User) interface{} {  // Function selector
    return u.LastLogin 
}).ToList()
// Entity-based ordering (uses first non-zero field)
users, _ := ctx.Users.OrderByAscending(&User{Name: "placeholder"}).ToList()
users, _ := ctx.Users.OrderByDescendingEntity(&User{CreatedAt: time.Now()}).ToList()
// Chain with other operations
topSpenders, _ := ctx.Users.
    Where("IsActive", true).
    OrderByDescending("TotalSpent").
    Take(10).
    ToList()// E-commerce analytics
type Product struct {
    ID          uint    `gorm:"primaryKey"`
    Name        string  `gorm:"not null"`
    Price       float64 `gorm:"not null"`
    Rating      float64 `gorm:"default:0"`
    InStock     bool    `gorm:"default:true"`
    CategoryID  uint    `gorm:"not null"`
}
// Get total inventory value for in-stock premium products
inventoryValue, _ := ctx.Products.
    Where(&Product{InStock: true}).
    Where("Price", ">50").
    Sum(&Product{Price: 0.0})
// Find average rating of highly-rated products
avgRating, _ := ctx.Products.
    Where("Rating", ">=4.0").
    Average(&Product{Rating: 0.0})
// Get top 5 most expensive products by category
topProducts, _ := ctx.Products.
    Where(&Product{CategoryID: 1}).
    OrderByDescending("Price").
    Take(5).
    ToList()
// Complex aggregation with OR conditions
totalValue, _ := ctx.Products.
    Where("Rating", ">=4.5").
    Or(&Product{InStock: true}).
    Sum(&Product{Price: 0.0})GoNtext provides EF Core-style Include and Select functionality with type safety!
// Include single relationship
users, _ := ctx.Users.Include("Posts").ToList()
// Include multiple relationships
users, _ := ctx.Users.Include("Posts", "Profile").ToList()
// Auto-include all relationships
users, _ := ctx.Users.IncludeAll().ToList()
// Chain with other operations
users, _ := ctx.Users.Include("Posts").Where("IsActive", true).ToList()// Select only specific fields
users, _ := ctx.Users.Select("ID", "Username", "Email").ToList()
// Exclude sensitive fields
users, _ := ctx.Users.Omit("PasswordHash").ToList()
// Combine Include and Select
users, _ := ctx.Users.Include("Posts").Select("ID", "Username").ToList()// ✅ Valid - compiles and runs
users, _ := ctx.Users.Include("Posts").ToList()
// ❌ Invalid - panics with: Field 'Post' not found on User
users, _ := ctx.Users.Include("Post").ToList()  // Typo in field nameGoNtext validates field names at runtime and provides clear error messages for fast debugging.
// Load users with their posts and profiles, excluding sensitive data
results, _ := ctx.Users.
    Include("Posts", "Profile").
    Omit("PasswordHash").
    Where("IsActive", true).
    OrderBy("Username").
    Skip(0).Take(10).
    ToList()
// Auto-detect and load all relationships
users, _ := ctx.Users.IncludeAll().ToList()You can override the default table naming by implementing the TableName() method:
type User struct {
    Id       uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()"`
    Username string    `gorm:"uniqueIndex;not null"`
    Email    string    `gorm:"uniqueIndex;not null"`
}
// Custom table name - overrides default "User"
func (User) TableName() string {
    return "app_users" // Will create "app_users" table instead of "User"
}
type Product struct {
    Id   uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()"`
    Name string    `gorm:"not null"`
}
// No TableName() method - uses default "Product" table nameGoNtext respects the TableName() method across all operations:
- ✅ CRUD Operations: ctx.Users.Add(),ctx.Users.Find(), etc. use custom table name
- ✅ LINQ Queries: ctx.Users.WhereField().ToList()uses custom table name
- ✅ Migrations: Migration files generate SQL for custom table names
- ✅ PostgreSQL: Automatically quotes custom table names: "app_users"
// Setup with custom table names
ctx, _ := gontext.NewDbContext("postgres://user:pass@localhost/db", "postgres")
userSet := gontext.RegisterEntity[User](ctx)   // Uses "app_users" table
productSet := gontext.RegisterEntity[Product](ctx) // Uses "Product" table
// All operations use the custom table names automatically
user, _ := userSet.WhereField("Username", "john").FirstOrDefault()
// SQL: SELECT * FROM "app_users" WHERE "Username" = 'john'
product, _ := productSet.WhereField("Name", "laptop").FirstOrDefault()
// SQL: SELECT * FROM "Product" WHERE "Name" = 'laptop'- Legacy databases: Match existing table names
- Naming conventions: Follow company/team standards (e.g., tbl_users,app_users)
- Multi-tenant: Different table prefixes per tenant
- Database conventions: Snake_case, plural names, etc.
GoNtext now supports GORM-style static typing with struct patterns! Use familiar GORM syntax alongside EF Core-style LINQ methods.
- 🔍 Struct-based Where Clauses: Use &User{Email: "[email protected]"}instead of strings
- 🚀 Multiple Query Patterns: Support for SQL, field names, and struct patterns
- 🔗 OR Operations: Chain WHERE and OR conditions with struct patterns
- ⚡ GORM-Compatible: Drop-in replacement for common GORM operations
- 🎯 Type-Safe: Compile-time checking with struct patterns
GoNtext supports 3 query patterns:
// Pattern 1: SQL with parameters (traditional)
user, _ := ctx.Users.Where("Email = ?", "[email protected]").FirstOrDefault()
// Pattern 2: Field name with value (EF Core style)
user, _ := ctx.Users.Where("Email", "[email protected]").FirstOrDefault()
// Pattern 3: Struct pattern (GORM style) ✨ NEW!
user, _ := ctx.Users.Where(&entities.User{Email: "[email protected]"}).FirstOrDefault()Use OR conditions with struct patterns for complex queries:
// Login with email OR username (like GORM)
user, _ := ctx.Users.Where(&entities.User{Email: "[email protected]"}).
                    OrField("Username", "john").
                    FirstOrDefault()
// Multiple OR conditions with structs
users, _ := ctx.Users.Where(&entities.User{Role: "admin"}).
                      OrEntity(entities.User{Role: "manager"}).
                      ToList()
// Mixed patterns
user, _ := ctx.Users.Where("IsActive", true).
                     OrField("Role", "admin").
                     FirstOrDefault()Use familiar GORM patterns with GoNtext's change tracking:
// Create (GORM style)
user := &entities.User{Username: "john", Email: "[email protected]"}
err := ctx.Users.Create(user)
// Save (creates or updates like GORM)
user.Email = "[email protected]"
err := ctx.Users.Save(user)
// First with struct pattern (like GORM)
user, err := ctx.Users.First(&entities.User{Email: "[email protected]"})
// Update with struct pattern
user.Username = "newusername"
err := ctx.Users.Update(*user)
// Find by ID (GORM style)
user, err := ctx.Users.Find(userID)Perfect for authentication with email OR username:
func LoginUser(emailOrUsername, password string) (*User, error) {
    // Search by email OR username using static typing
    user, err := ctx.Users.WhereField("email", emailOrUsername).
                           OrField("username", emailOrUsername).
                           FirstOrDefault()
    if err != nil || user == nil {
        return nil, fmt.Errorf("invalid credentials")
    }
    // Verify password...
    return user, nil
}- Zero Runtime Overhead: Struct patterns compile to optimized SQL
- PostgreSQL Optimized: Automatic field name translation with quoted identifiers
- GORM Compatible: Familiar methods work the same way
- Change Tracking: Automatic entity state management like EF Core
Choose what you want to learn:
Learn the fundamentals:
- Creating entities
- Saving changes
- Querying data
- Updates and deletes
Database schema management:
- Creating migrations
- Model snapshots
- Schema evolution
- Database updates
Advanced querying:
- Where conditions
- Ordering and pagination
- String operations
- Aggregations
- Method chaining
The built-in CLI has limitations. For proper migrations, you need to set up entity registration:
// File: migrations_context.go
func CreateDesignTimeContext() (*gontext.DbContext, error) {
    ctx, err := gontext.NewDbContext("your-db-url", "postgres")
    if err != nil {
        return nil, err
    }
    // Register ALL your entities
    gontext.RegisterEntity[User](ctx)
    gontext.RegisterEntity[Post](ctx)
    // ... register every entity
    return ctx, nil
}// Add to your main.go
func handleMigrations() {
    if len(os.Args) > 1 && os.Args[1] == "migrate:add" {
        // Use your design-time context for migrations
        ctx, _ := CreateDesignTimeContext()
        // Generate migration files
    }
}See Migrations Example for complete setup.
- 🎯 Familiar: Uses EF Core patterns you already know
- 🔍 Type-Safe: LINQ queries with compile-time checking
- 🚀 Powerful: Full SQL capabilities via GORM integration
- 📦 Complete: Migrations, change tracking, and DbContext patterns
- 🔄 Flexible: Use LINQ or drop down to raw SQL when needed
GoNtext brings the best of Entity Framework Core to Go!
| EF Core (C#) | GoNtext (Go) | 
|---|---|
| context.Users.Add(user) | ctx.Users.Add(user) | 
| context.SaveChanges() | ctx.SaveChanges() | 
| context.Users.Where(x => x.IsActive).ToList() | ctx.Users.Where("IsActive", true).ToList() | 
| context.Users.Where(x => x.IsActive).ToList() | ctx.Users.Where(&User{IsActive: true}).ToList()✨ | 
| context.Users.FirstOrDefault(x => x.Id == id) | ctx.Users.ById(id) | 
| context.Users.FirstOrDefault(x => x.Id == id) | ctx.Users.Where(&User{Id: id}).FirstOrDefault()✨ | 
| context.Users.OrderBy(x => x.Email) | ctx.Users.OrderBy("Email") | 
| context.Users.Where(x => x.Age > 18) | ctx.Users.Where("Age", ">18")✨ | 
| context.Users.Where(x => x.Age > 18) | ctx.Users.Where(&User{Age: ">18"})✨ NEW! | 
| context.Orders.Sum(x => x.Amount) | ctx.Orders.Sum(&Order{Amount: 0.0})✨ NEW! | 
| context.Users.Average(x => x.Age) | ctx.Users.Average(&User{Age: 0})✨ NEW! | 
| Pascal case tables ( Users) | Pascal case tables ( "User") ✨ | 
| Pascal case columns ( IsActive) | Pascal case columns ( "IsActive") ✨ | 
| [Table("app_users")] class User | func (User) TableName() string { return "app_users" }✨ | 
| Traditional GORM (Go) | GoNtext (Go) | 
|---|---|
| db.Where(&User{Email: "test"}).First(&user) | ctx.Users.Where(&User{Email: "test"}).FirstOrDefault()✨ | 
| db.Where("email = ?", email).Or("username = ?", username).First(&user) | ctx.Users.Where("Email", email).Or("Username", username).FirstOrDefault()✨ | 
| db.Where("age > ?", 18).Find(&users) | ctx.Users.Where("Age", ">18").ToList()✨ | 
| db.Where("age > ?", 18).Find(&users) | ctx.Users.Where(&User{Age: ">18"}).ToList()✨ NEW! | 
| db.Create(&user) | ctx.Users.Add(user)✨ (EF Core style) | 
| db.Save(&user) | ctx.Users.Save(&user)✨ | 
| db.First(&user, id) | ctx.Users.Find(id)✨ | 
| db.Model(&Order{}).Select("SUM(amount)").Scan(&total) | ctx.Orders.Sum(&Order{Amount: 0.0})✨ NEW! | 
| db.Model(&User{}).Select("AVG(age)").Scan(&avg) | ctx.Users.Average(&User{Age: 0})✨ NEW! | 
| Manual change tracking | Automatic change tracking ✨ | 
| Manual migrations | Code-first migrations ✨ | 
| Snake_case by default | Pascal case with PostgreSQL ✨ | 
GoNtext combines the best features:
- 🎯 EF Core: Change tracking, DbContext patterns, LINQ-style queries
- ⚡ GORM: Familiar syntax, struct-based queries, flexible operations
- 🐘 PostgreSQL: Automatic Pascal case with quoted identifiers
- 🚀 Performance: Zero runtime overhead, optimized SQL generation
- 📚 Complete API Reference - Full documentation of all GoNtext LINQ methods
- CRUD Operations Guide
- Migrations Setup Guide
- LINQ Queries Guide
# Clone and test
git clone https://github.com/shepherrrd/gontext
cd gontext/examples/01-crud
go mod tidy
createdb test_gontext
go run .