Usa o GORM com go-mssqldb

O GORM é um Go ORM popular que suporta o SQL Server através do gorm.io/driver/sqlserver dialeto, que utiliza go-mssqldb internamente. O GORM trata das diferenças de esquema, geração de consultas e mapeamento de tipos para que possas trabalhar com structs do Go em vez de escrever SQL bruto.

Este artigo aborda padrões GORM que são específicos do SQL Server. Para uso geral do GORM, consulte a documentação do GORM.

Os exemplos deste artigo são executados na base de dados de exemplo AdventureWorks2025.

Instalar o GORM e o dialeto do SQL Server

Crie ou abra um módulo Go, depois adicione o GORM e o dialeto do 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

Liga-te ao SQL Server

Abra uma sessão do GORM ao passar um sqlserver:// DSN ao dialeto:

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

O dialeto usa o go-mssqldb controlador internamente, pelo que todas as opções de ligação de Cadeias de ligação e Opções de ligação se aplicam ao DSN.

Mapear modelos para tabelas existentes

A convenção de nomes padrão do GORM usa nomes de tabelas plurais minúsculas (por exemplo, products), que não correspondem às convenções do SQL Server. Utilize o TableName() método para ligar um modelo a uma tabela já existente e qualificada pelo esquema:

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

Define TableName() sempre quando trabalhas com bases de dados SQL Server existentes. Depender da pluralização automática do GORM leva a erros de "nome de objeto inválido" em tabelas que seguem as convenções de nomenclatura do SQL Server.

Consultar dados

Depois de definir um modelo, use os métodos de consulta do GORM para ler dados:

Encontre linhas com uma cláusula WHERE

Filtre, ordene e limite os resultados 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)
}

Recuperar uma única linha

Use First com uma condição para carregar um registo:

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)

Contar linhas

Utilize Count no âmbito de um modelo para obter o número de linhas:

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

Inserir, atualizar e eliminar linhas

As operações de escrita usam as mesmas estruturas de modelo. Os exemplos nesta secção visam Production.ScrapReason para que não modifiquem tabelas críticas.

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

Inserir uma linha

Passe uma estrutura preenchida para Create para inserir uma linha e preencher a chave primária gerada 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)

Atualizar uma linha

Use Model com Update para alterar o valor de uma coluna:

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)

Eliminar uma linha

Passe uma struct com uma chave primária preenchida para 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)

Inserção em lote e o limite de 2.100 parâmetros

O SQL Server limita sp_executesql a 2.100 parâmetros por instrução. A inserção por lote do GORM gera uma cláusula de várias linhas VALUES onde cada coluna consome um parâmetro, de modo que uma tabela com 10 colunas atinge o limite de cerca de 210 linhas por lote.

Defina CreateBatchSize na sessão para permanecer dentro do 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)
}

Para tabelas com muitas colunas, reduza proporcionalmente o tamanho do lote. Uma fórmula segura: batch size = floor(2100 / number of inserted columns).

Transactions

Agrupe múltiplas operações numa transação 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 o callback devolver um erro, o GORM reverte automaticamente.

Reserva SQL bruta

Quando o construtor de consultas do GORM não gera a sintaxe do SQL Server que precisas, passa para SQL puro enquanto ainda usas a sessão do 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)
}

As consultas brutas usam o mesmo pool de ligação gerido pelo GORM, por isso as definições db.DB() do pool continuam a aplicar-se.

Configurar o pool de ligações

Acede ao subjacente *sql.DB para definir parâmetros do pool:

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

Para obter orientações sobre o dimensionamento do agrupamento de ligações, consulte agrupamento de ligações.

Considerações sobre o AutoMigrate

AutoMigrate cria ou altera tabelas para corresponder às definições do teu modelo. Funciona com SQL Server, mas esteja atento a estes comportamentos:

  • AutoMigrate Cria tabelas no esquema padrão (dbo), a menos que substituas TableName() com um prefixo de esquema.
  • Adiciona colunas em falta e cria índices, mas não elimina colunas nem altera tipos de colunas existentes.
  • O SQL Server tem um limite de 128 caracteres para nomes de índice. Os nomes de índices gerados pelo GORM podem exceder este limite em tabelas com nomes longos ou índices compostos. Usa etiquetas explícitas gorm:"index:idx_short_name" se atingires esse limite.

Para bases de dados de produção com esquemas geridos, prefira ferramentas de migração em vez de AutoMigrate.