databasegomapsprimary-keygo-gorm

Why do I get an error when creating records from a map with a PK field in GORM, and is this a valid use case?


I’ve been learning GORM from the official docs, and I’m running into some confusion when trying to create records from a map using the Create() function.

The official documentation shows this example for creating records from a map:

// Single insert
db.Model(&User{}).Create(map[string]interface{}{
  "Name": "jinzhu", "Age": 18,
})

// Batch insert
db.Model(&User{}).Create([]map[string]interface{}{
  {"Name": "jinzhu_1", "Age": 18},
  {"Name": "jinzhu_2", "Age": 20},
})

But here’s the thing — this works fine if there is no primary key (PK) defined for the table. However, if I define a PK field (like user_id), I get an error. From what I understand, every table must have a primary key for normalization, so why does this method work without a PK?

Is there any valid use case for creating records from a map if the table has a PK? Does GORM handle PKs automatically in this case? Or is there something I’m missing, and it really doesn’t work with PKs?

I’m also wondering if the example in the docs is actually well-written or if it needs a correction for handling tables with primary keys. Should I be using structs instead of maps when working with tables that have a PK?


Solution

  • Forgive me, but I strongly advise to read the manual.

    Gorm in general

    Assuming you have used composition to embed gorm.Model into User like this:

    type User struct {
        gorm.Model
        // your fields here 
    }
    

    your effective User looks like this

    type User struct {
      ID        uint           `gorm:"primaryKey"`
      CreatedAt time.Time
      UpdatedAt time.Time
      DeletedAt gorm.DeletedAt `gorm:"index"
    
      // your fields here 
    }
    

    So yes, per default gorm handles the primary key for you, and you can even access the value of it easily, as documented for Create:

    user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
    
    result := db.Create(&user) // pass pointer of data to Create
    
    user.ID             // returns inserted data's primary key
    result.Error        // returns error
    result.RowsAffected // returns inserted records count
    

    Your example

    It eludes me why you use a map[string]interface{}. There is no good reason for that. Again, as per docs for the batch insert:

    users := []*User{
      {Name: "Jinzhu", Age: 18, Birthday: time.Now()},
      {Name: "Jackson", Age: 19, Birthday: time.Now()},
    }
    
    result := db.Create(users)
    

    which would translate to

    
    result := db.Create([]*User{
      {Name: "Jinzhu", Age: 18, Birthday: time.Now()},
      {Name: "Jackson", Age: 19, Birthday: time.Now()},
    })
    

    One of the main advantages of Go is its type safety. With interface{}, you give up on that. As per go proverbs:

    interface{} says nothing

    I highly recommend going through all of them, as well as Effective Go