Utilisez GORM avec go-mssqldb

GORM est un ORM populaire pour Go qui prend en charge SQL Server via le dialecte gorm.io/driver/sqlserver, qui utilise go-mssqldb en interne. GORM gère les différences de schéma, la génération de requêtes et le mappage de types afin que vous puissiez travailler avec des structs Go au lieu d’écrire du SQL brut.

Cet article traite des schémas GORM spécifiques à SQL Server. Pour une utilisation générale de GORM, consultez la documentation GORM.

Les exemples de cet article s’exécutent sur la base de données d’exemple AdventureWorks2025.

Installez GORM et le dialecte SQL Server

Créez ou ouvrez un module Go, puis ajoutez GORM et le dialecte 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

Se connecter à SQL Server

Ouvrez une session GORM en transmettant un DSN sqlserver:// au dialecte :

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

Le dialecte s’appuie en interne sur le pilote go-mssqldb, donc toutes les options de connexion décrites dans Chaînes de connexion et Options de connexion s’appliquent au DSN.

Associer les modèles aux tables existantes

La convention de nommage par défaut de GORM utilise des noms de tables pluriels minuscules (par exemple, products), ce qui ne correspond pas aux conventions de SQL Server. Utilisez la TableName() méthode pour lier un modèle à une table existante qualifiée par le schéma :

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

Définissez TableName() toujours quand vous travaillez avec des bases de données SQL Server existantes. Se fier à l'auto-pluralisation de GORM entraîne des erreurs de « nom d'objet invalide » sur les tables suivant les conventions de nommage de SQL Server.

Rechercher des données

Après avoir défini un modèle, utilisez les méthodes de requête de GORM pour lire les données :

Trouvez des lignes avec une clause WHERE

Filtrez, triez et limitez les résultats en utilisant Where, Order, et 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)
}

Récupérer une seule ligne

À utiliser First avec une condition pour charger un enregistrement :

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)

Compter les lignes

Utilisez Count sur une portée de modèle pour obtenir le nombre de lignes :

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

Insérer, mettre à jour et supprimer les lignes

Les opérations d’écriture utilisent les mêmes structures de modèle. Les exemples de cette section ciblent Production.ScrapReason afin de ne pas modifier les tables critiques.

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

Insérer une ligne

Passez une structure remplie à Create pour insérer une ligne et remplissez la clé primaire générée automatiquement :

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)

Mettre à jour une ligne

Utilisez Model avec Update pour changer la valeur d’une colonne :

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)

Supprimer une ligne

Passons une structure avec une clé primaire remplie vers 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)

Insertion par lots et la limite de 2 100 paramètres

SQL Server limite sp_executesql à 2 100 paramètres par instruction. L’insert batch de GORM génère une clause multi-lignes VALUES où chaque colonne consomme un paramètre, de sorte qu’une table de 10 colonnes atteint la limite d’environ 210 lignes par lot.

Définissez CreateBatchSize pour la session afin de rester dans la limite autorisée :

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

Pour les tables avec plusieurs colonnes, réduisez proportionnellement la taille du lot. Une formule sûre : batch size = floor(2100 / number of inserted columns).

Transactions

Encapsulez plusieurs opérations dans une transaction 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")

Si le rappel renvoie une erreur, GORM revient automatiquement en arrière.

Secours SQL brut

Lorsque le constructeur de requêtes de GORM ne génère pas la syntaxe SQL Server dont vous avez besoin, passez à SQL brut tout en utilisant toujours la session 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)
}

Les requêtes brutes utilisent le même pool de connexions géré par GORM, donc les paramètres du pool de db.DB() s’appliquent toujours.

Configurez le pool de connexions

Accédez à la base *sql.DB pour définir les paramètres du pool :

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

Pour des conseils sur le dimensionnement du pool de connexions, voir pool de connexions.

Considérations relatives à AutoMigrate

AutoMigrate crée ou modifie des tables pour correspondre aux définitions de votre modèle. Il fonctionne avec SQL Server, mais soyez conscient de ces comportements :

  • AutoMigrate crée des tables dans le schéma par défaut (dbo), à moins de redéfinir TableName() en le préfixant par un schéma.
  • Il ajoute des colonnes manquantes et crée des indices mais ne supprime pas de colonnes ni ne modifie les types de colonnes existants.
  • SQL Server a une limite de 128 caractères pour les noms d’index. Les noms d’index générés par GORM peuvent dépasser cette limite sur les tables avec des noms longs ou des index composites. Utilisez des tags explicites gorm:"index:idx_short_name" si vous atteignez cette limite.

Pour les bases de données de production avec schémas gérés, il faut privilégier les outils de migration à AutoMigrate.