Använd GORM med go-mssqldb

GORM är en populär Go-ORM som stöder SQL Server via dialekten gorm.io/driver/sqlserver, som internt använder go-mssqldb. GORM hanterar schema-skillnader, frågegenerering och typmappning så att du kan arbeta med Go-structs istället för att skriva rå SQL.

Den här artikeln behandlar GORM-mönster som är specifika för SQL Server. För allmän användning av GORM, se GORM-dokumentationen.

Exempel i denna artikel körs mot AdventureWorks2025 :s exempeldatabas.

Installera GORM och SQL Server-dialekten

Skapa eller öppna en Go-modul, lägg sedan till GORM och SQL Server-dialekten:

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

Ansluta till SQL Server

Öppna en GORM-session genom att skicka ett sqlserver:// DSN till dialekten:

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")
}

Dialekten använder drivrutinen go-mssqldb i bakgrunden, så alla anslutningsalternativ från Anslutningssträngar och Anslutningsalternativ gäller även för DSN:en.

Kartmodeller till befintliga tabeller

GORM:s standardnamngivningskonvention använder små plural tabellnamn (till exempel products), vilket inte stämmer överens med SQL Server-konventionerna. Använd TableName() metoden för att binda en modell till en befintlig schema-kvalificerad tabell:

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

Definiera TableName() alltid när du arbetar med befintliga SQL Server-databaser. Att förlita sig på GORM:s automatiska pluralisering leder till fel av typen ”ogiltigt objektnamn” för tabeller som följer SQL Servers namngivningskonventioner.

Fråga efter data

Efter att du definierat en modell, använd GORM:s frågemetoder för att läsa data:

Hitta rader med en WHERE-klausul

Filtrera, sortera och begränsa resultat med hjälp av Where, Order, och 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)
}

Hämta en enda rad

Använd First med ett villkor för att ladda en post:

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)

Antal rader

Använd Count på modellens scope för att få antalet rader:

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

Lägg till, uppdatera och ta bort rader

Skrivoperationer använder samma modellstrukturer. Exemplen i det här avsnittet är inriktade på Production.ScrapReason så att de inte påverkar kritiska tabeller.

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"
}

Lägg in en rad

Skicka en ifylld struktur till Create för att infoga en rad och fyll i den automatiskt genererade primärnyckeln:

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)

Uppdatera en rad

Använd Model med Update för att ändra ett kolumnvärde:

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)

Ta bort en rad

Skicka en struktur med en befolkad primärnyckel till 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)

Satsvis infogning och gränsen på 2 100 parametrar

SQL Server begränsar sp_executesql till 2 100 parametrar per sats. GORM:s batchinsats genererar en flerradsklausul VALUES där varje kolumn använder en parameter, så en tabell med 10 kolumner når gränsen vid ungefär 210 rader per batch.

Ange CreateBatchSize för sessionen för att hålla dig inom gränsen:

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)
}

För tabeller med många kolumner, minska batchstorleken proportionellt. En säker formel: batch size = floor(2100 / number of inserted columns).

Transactions

Omslut flera operationer i en GORM-transaktion:

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")

Om callbacken returnerar ett felmeddelande rullas GORM automatiskt tillbaka.

Rå SQL-reservlösning

När GORM:s frågebyggare inte genererar den SQL Server-syntax du behöver, gå över till rå SQL medan du fortfarande använder GORM-sessionen:

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)
}

Råa frågor använder samma anslutningspool som hanteras av GORM, så poolinställningarna från db.DB() gäller fortfarande.

Konfigurera anslutningspoolen

Åtkomst till underliggande *sql.DB för att sätta poolparametrar:

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

För information om dimensionering av pooler, se Anslutningspooler.

Överväganden för AutoMigrate

AutoMigrate skapar eller ändrar tabeller för att matcha dina modelldefinitioner. Det fungerar med SQL Server, men var medveten om dessa beteenden:

  • AutoMigrate skapar tabeller i standardschemat (dbo) om du inte åsidosätter TableName() med ett schemaprefix.
  • Den lägger till saknade kolumner och skapar index men tar inte bort kolumner eller ändrar befintliga kolumntyper.
  • SQL Server har en gräns på 128 tecken för indexnamn. GORM:s genererade indexnamn kan överstiga denna gräns på tabeller med långa namn eller sammansatta index. Använd explicita taggar gorm:"index:idx_short_name" om du når denna gräns.

För produktionsdatabaser med hanterade scheman föredrar du migreringsverktyg framför AutoMigrate.