Gebruik GORM met go-mssqldb

GORM is een populaire Go-ORM die SQL Server ondersteunt via het gorm.io/driver/sqlserver dialect, dat intern wordt gebruiktgo-mssqldb. GORM regelt schemaverschillen, querygeneratie en typemapping, zodat je met Go-structs kunt werken in plaats van ruwe SQL te schrijven.

Dit artikel behandelt GORM-patronen die specifiek zijn voor SQL Server. Voor algemeen gebruik van GORM, zie de GORM-documentatie.

In de voorbeelden in dit artikel wordt gebruikgemaakt van de AdventureWorks2025-voorbeelddatabase.

Installeer GORM en het SQL Server-dialect

Maak of open een Go-module, voeg dan GORM en het SQL Server-dialect toe:

mkdir go-gorm-sqlserver
cd go-gorm-sqlserver
go mod init example.com/go-gorm-sqlserver
go get gorm.io/gorm
go get gorm.io/driver/sqlserver

Verbinding maken met SQL Server

Open een GORM-sessie door een sqlserver:// DSN aan het dialect door te geven:

package main

import (
    "fmt"
    "log"

    "gorm.io/driver/sqlserver"
    "gorm.io/gorm"
)

func main() {
    // Replace <server>, <user>, and <password> with your own values.
    // In production, load credentials from environment variables or a secret store.
    dsn := "sqlserver://<user>:<password>@<server>.database.windows.net:1433?database=AdventureWorks2025&encrypt=true&TrustServerCertificate=false"
    db, err := gorm.Open(sqlserver.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal(err)
    }

    sqlDB, err := db.DB()
    if err != nil {
        log.Fatal(err)
    }
    defer sqlDB.Close()

    fmt.Println("Connected to SQL Server with GORM")
}

Het dialect gebruikt achter de schermen de go-mssqldb driver, dus alle verbindingsopties uit Verbindingsreeksen en Verbindingsopties zijn van toepassing op de DSN.

Koppel modellen aan bestaande tabellen

De standaard naamgevingsconventie van GORM gebruikt kleine meervoudige meervoudsnamen (bijvoorbeeld products), wat niet overeenkomt met de SQL Server-conventies. Gebruik de TableName() methode om een model te binden aan een bestaande schema-gekwalificeerde tabel:

type Product struct {
    ProductID   int32  `gorm:"column:ProductID;primaryKey"`
    Name        string `gorm:"column:Name"`
    ProductLine *string `gorm:"column:ProductLine"`
    ListPrice   float64 `gorm:"column:ListPrice"`
}

func (Product) TableName() string {
    return "Production.Product"
}

Tip

Definieer TableName() altijd wanneer je met bestaande SQL Server-databases werkt. Vertrouwen op de automatische meervoudsvorming van GORM leidt tot de foutmelding 'ongeldige objectnaam' bij tabellen die volgens de naamgevingsconventies van SQL Server zijn genoemd.

Gegevens opvragen

Nadat je een model hebt gedefinieerd, gebruik dan de querymethoden van GORM om data te lezen:

Zoek rijen met een WHERE-clausule

Filter, sorteer en beperk resultaten door gebruik te maken van Where, Order, en Limit:

var products []Product
result := db.Where("ListPrice > ?", 1000).
    Order("ListPrice DESC").
    Limit(5).
    Find(&products)
if result.Error != nil {
    log.Fatal(result.Error)
}

for _, p := range products {
    fmt.Printf("%-35s $%.2f\n", p.Name, p.ListPrice)
}

Haal een enkele rij op

Gebruik First met een voorwaarde om één record te laden:

var product Product
if err := db.First(&product, "ProductID = ?", 680).Error; err != nil {
    log.Fatal(err)
}
fmt.Printf("%s: $%.2f\n", product.Name, product.ListPrice)

Aantal rijen

Gebruik Count voor een model scope om het aantal rijen op te halen:

var count int64
db.Model(&Product{}).Where("ListPrice > ?", 0).Count(&count)
fmt.Printf("Products with a list price: %d\n", count)

Rijen invoegen, bijwerken en verwijderen

Schrijfoperaties gebruiken dezelfde modelstructs. De voorbeelden in deze sectie zijn gericht op Production.ScrapReason, zodat ze kritieke tabellen niet wijzigen.

type ScrapReason struct {
    ScrapReasonID int16     `gorm:"column:ScrapReasonID;primaryKey;autoIncrement"`
    Name          string    `gorm:"column:Name"`
    ModifiedDate  time.Time `gorm:"column:ModifiedDate"`
}

func (ScrapReason) TableName() string {
    return "Production.ScrapReason"
}

Voeg een rij in

Geef een ingevulde struct door aan Create om een rij in te voegen en de automatisch gegenereerde primaire sleutel in te vullen:

reason := ScrapReason{Name: "GORM test scrap", ModifiedDate: time.Now()}
if err := db.Create(&reason).Error; err != nil {
    log.Fatal(err)
}
fmt.Printf("Inserted ScrapReasonID: %d\n", reason.ScrapReasonID)

// Clean up
db.Delete(&reason)

Een rij bijwerken

Gebruik Model met Update om een kolomwaarde te wijzigen:

reason := ScrapReason{Name: "GORM update test", ModifiedDate: time.Now()}
db.Create(&reason)

db.Model(&reason).Update("Name", "GORM updated scrap")

var updated ScrapReason
db.First(&updated, reason.ScrapReasonID)
fmt.Printf("Updated name: %s\n", updated.Name)

// Clean up
db.Delete(&reason)

Een rij verwijderen

Geef een struct met een ingevulde primaire sleutel door naar Delete:

reason := ScrapReason{Name: "GORM delete test", ModifiedDate: time.Now()}
db.Create(&reason)
id := reason.ScrapReasonID

db.Delete(&reason)

var count int64
db.Model(&ScrapReason{}).Where("ScrapReasonID = ?", id).Count(&count)
fmt.Printf("Rows remaining with that ID: %d\n", count)

Batchinvoeging en de limiet van 2.100 parameters

SQL Server beperkt sp_executesql zich tot 2.100 parameters per statement. De batch-insert van ORM genereert een multi-row VALUES clausule waarbij elke kolom één parameter verbruikt, zodat een tabel met 10 kolommen de limiet bereikt bij ongeveer 210 rijen per batch.

Stel CreateBatchSize in voor de sessie om binnen de limiet te blijven:

reasons := make([]ScrapReason, 50)
for i := range reasons {
    reasons[i] = ScrapReason{
        Name:         fmt.Sprintf("Batch scrap %d", i+1),
        ModifiedDate: time.Now(),
    }
}

// 2 inserted columns per row (Name, ModifiedDate; the autoIncrement ID is excluded),
// so batches of 500 are well within the 2,100-parameter limit.
result := db.CreateInBatches(&reasons, 500)
if result.Error != nil {
    log.Fatal(result.Error)
}
fmt.Printf("Inserted %d rows in batches\n", result.RowsAffected)

// Clean up
for _, r := range reasons {
    db.Delete(&r)
}

Voor tabellen met veel kolommen verklein je de batchgrootte proportioneel. Een veilige formule: batch size = floor(2100 / number of inserted columns).

Transactions

Wikkel meerdere bewerkingen in een GORM-transactie:

err := db.Transaction(func(tx *gorm.DB) error {
    r1 := ScrapReason{Name: "TX scrap 1", ModifiedDate: time.Now()}
    if err := tx.Create(&r1).Error; err != nil {
        return err
    }

    r2 := ScrapReason{Name: "TX scrap 2", ModifiedDate: time.Now()}
    if err := tx.Create(&r2).Error; err != nil {
        return err
    }

    // Both inserts commit together.
    fmt.Printf("Inserted IDs: %d, %d\n", r1.ScrapReasonID, r2.ScrapReasonID)

    // Clean up inside the same transaction.
    tx.Delete(&r1)
    tx.Delete(&r2)

    return nil
})
if err != nil {
    log.Fatal(err)
}
fmt.Println("Transaction committed")

Als de callback een foutmelding teruggeeft, rolt GORM automatisch terug.

Raw SQL fallback

Wanneer de query builder van GORM niet de SQL Server-syntaxis genereert die je nodig hebt, ga dan over naar ruwe SQL terwijl je nog steeds de GROM-sessie gebruikt:

var results []struct {
    Name      string
    ListPrice float64
}
result := db.Raw("SELECT TOP 5 Name, ListPrice FROM Production.Product WHERE ListPrice > @price ORDER BY ListPrice DESC",
    sql.Named("price", 1000)).Scan(&results)
if result.Error != nil {
    log.Fatal(result.Error)
}

for _, r := range results {
    fmt.Printf("%-35s $%.2f\n", r.Name, r.ListPrice)
}

Ruwe queries gebruiken dezelfde verbindingspool die door GORM wordt beheerd, dus poolinstellingen blijven db.DB() van toepassing.

Configureer de verbindingspool

Toegang tot de onderliggende *sql.DB om poolparameters in te stellen:

sqlDB, err := db.DB()
if err != nil {
    log.Fatal(err)
}
sqlDB.SetMaxOpenConns(25)
sqlDB.SetMaxIdleConns(10)
sqlDB.SetConnMaxLifetime(5 * time.Minute)

Voor richtlijnen voor poolgrootte, zie Connection pooling.

Overwegingen over AutoMigrate

AutoMigrate maakt of wijzigt tabellen om aan te sluiten bij je modeldefinities. Het werkt met SQL Server, maar wees je bewust van deze gedragingen:

  • AutoMigrate maakt tabellen aan in het standaardschema (dbo) tenzij je overschrijft TableName() met een schema-prefix.
  • Het voegt ontbrekende kolommen toe en maakt indexen aan, maar laat kolommen niet vallen en verandert geen bestaande kolomtypes.
  • SQL Server heeft een limiet van 128 tekens voor indexnamen. De door VRM gegenereerde indexnamen kunnen deze limiet overschrijden op tabellen met lange namen of samengestelde indexen. Gebruik expliciete gorm:"index:idx_short_name" tags als je deze limiet bereikt.

Voor productiedatabases met beheerde schema's geef je de voorkeur aan migratietools boven AutoMigrate.