Usa GORM con go-mssqldb

GORM è un popolare ORM per Go che supporta SQL Server attraverso il dialetto gorm.io/driver/sqlserver, che utilizza internamente go-mssqldb. GORM gestisce differenze di schema, generazione di query e mappatura dei tipi, così puoi lavorare con le struct Go invece di scrivere SQL grezzo.

Questo articolo tratta i pattern GORM specifici di SQL Server. Per l'uso generale di GORM, consulta la documentazione GORM.

Gli esempi in questo articolo vengono confrontati con il database di esempio AdventureWorks2025 .

Installa GORM e il dialetto di SQL Server

Crea o apri un modulo Go, poi aggiungi GORM e il dialetto di SQL Server:

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

Connessione a SQL Server

Apri una sessione GORM passando un DSN sqlserver:// al dialetto:

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

Il dialetto usa il driver go-mssqldb dietro le quinte, quindi tutte le opzioni di connessione di Stringhe di connessione e Opzioni di connessione valgono anche per il DSN.

Mappare i modelli su tabelle esistenti

La convenzione di denominazione predefinita di GORM utilizza nomi di tabelle minuscole plurali (ad esempio, products), che non corrispondono alle convenzioni di SQL Server. Usa il TableName() metodo per associare un modello a una tabella già qualificata per lo schema:

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

Definisci TableName() sempre quando lavori con i database SQL Server esistenti. Affidarsi all'autopluralizzazione di GORM porta a errori di "nome oggetto non valido" contro tabelle che seguono le convenzioni di denominazione di SQL Server.

Dati di query

Dopo aver definito un modello, usa i metodi di query di GORM per leggere i dati:

Trova righe con una clausola WHERE

Filtrare, ordinare e limitare i risultati usando Where, Order, e 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)
}

Recupero di una singola riga

Usa First con una condizione per caricare un record:

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)

Contare righe

Usa Count su un telescopio modello per ottenere un conteggio delle righe:

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

Inserisci, aggiorna ed elimina righe

Le operazioni di scrittura utilizzano le stesse strutture del modello. Gli esempi in questa sezione sono destinati a Production.ScrapReason, quindi non modificano tabelle critiche.

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

Inserisci una riga

Passa una struct popolata a Create per inserire una riga e popolare la chiave primaria generata automaticamente:

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)

Aggiorna una riga

Usa Model con Update per cambiare il valore di una colonna:

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)

Elimina una riga

Passa una struct con una chiave primaria popolata a 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)

Inserimento batch e limite di 2.100 parametri

SQL Server limita sp_executesql a 2.100 parametri per istruzione. L'inserimento batch di GORM genera una clausola multi-riga VALUES in cui ogni colonna consuma un parametro, così una tabella con 10 colonne raggiunge il limite di circa 210 righe per lotto.

Imposta CreateBatchSize nella sessione per rimanere entro il limite:

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

Per tabelle con molte colonne, ridurre proporzionalmente la dimensione del batch. Una formula sicura: batch size = floor(2100 / number of inserted columns).

Transactions

Incapsula più operazioni in una transazione GORM:

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

Se il callback restituisce un errore, GORM torna automaticamente indietro.

SQL grezzo di riserva

Quando il query builder di GORM non genera la sintassi SQL Server di cui hai bisogno, passa a SQL grezzo continuando a usare la sessione GORM:

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

Le query grezze usano lo stesso pool di connessione gestito da GORM, quindi le impostazioni del db.DB() pool da si applicano ancora.

Configura il pool di connessione

Accedi al sottostante *sql.DB per impostare i parametri del pool:

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

Per indicazioni sul dimensionamento del pool, vedi pool di connessioni.

Considerazioni sull'AutoMigrate

AutoMigrate Crea o modifica tabelle per adattarle alle definizioni del tuo modello. Funziona con SQL Server, ma tieni presente questi comportamenti:

  • AutoMigrate Crea tabelle nello schema predefinito (dbo) a meno che tu non sovrascriva TableName() con un prefisso schema.
  • Aggiunge colonne mancanti e crea indici, ma non elimina colonne né cambia tipi di colonne esistenti.
  • SQL Server ha un limite di 128 caratteri per i nomi degli indici. I nomi degli indici generati da GORM possono superare questo limite su tabelle con nomi lunghi o indici compositi. Usa tag espliciti gorm:"index:idx_short_name" se raggiungi questo limite.

Per i database di produzione con schemi gestiti, preferisci gli strumenti di migrazione rispetto a AutoMigrate.