簡體   English   中英

使用gorm創建記錄時如何驗證歸屬關系

[英]How to validate a belongs-to relationship when creating record with gorm

我有以下型號

type PrivateGormModel struct {
    ID        uint       `gorm:"primary_key" json:"id"`
    CreatedAt time.Time  `json:"-"`
    UpdatedAt time.Time  `json:"-"`
    DeletedAt *time.Time `json:"-"`
}

type Employee struct {
    PrivateGormModel
    Person          `gorm:"embedded" json:"person,omitempty"`
    Contact         `gorm:"embedded" json:"contact,omitempty"`
    Address         `gorm:"embedded" json:"address,omitempty"`
    AltContact      `gorm:"embedded" json:"privateContact,omitempty"`
    BankAccount     `gorm:"embedded" json:"bankAccount,omitempty"`
    EmployeeGroupID uint `json:"groupID"`
    EmployeeGroup   `json:"group"`
    EmployeeRoleID  uint `json:"roleID"`
    EmployeeRole    `json:"role"`
}

func (e Employee) Validate() error {
    return validation.ValidateStruct(&e,
        validation.Field(&e.Person, validation.Required),
        validation.Field(&e.Contact),
        validation.Field(&e.Address),
        validation.Field(&e.AltContact),
        validation.Field(&e.BankAccount),
        validation.Field(&e.EmployeeGroup),
        validation.Field(&e.EmployeeRole),
    )
}

type EmployeeGroup struct {
    PrivateGormModel
    Title string `json:"title" gorm:"primaryKey;unique"`
}

func (e EmployeeGroup) Validate() error {
    return validation.ValidateStruct(&e,
        validation.Field(&e.Title, validation.Required, validation.Length(1, 32), validation.Match(regexp.MustCompile(`^[a-zA-Z0-9_ ]*$`))),
    )
}

type EmployeeRole struct {
    PrivateGormModel
    Title string `json:"title" gorm:"primaryKey;unique"`
}

func (e EmployeeRole) Validate() error {
    return validation.ValidateStruct(&e,
        validation.Field(&e.Title, validation.Required, validation.Length(1, 32), validation.Match(regexp.MustCompile(`^[a-zA-Z0-9_ ]*$`))),
    )
}

我的員工組和員工角色都只是一個帶有 gorm model 和字符串類型標題的結構。 我在數據庫中有一個 ID 為 1 的角色,一個 ID 為 1 的組。這是創建員工的處理程序

func CreateEmployee(db *database.Database) fiber.Handler {
    return func(c *fiber.Ctx) error {
        employee := new(model.Employee)

        if err := c.BodyParser(employee); err != nil {
            fmt.Printf("%v", err)
            return c.JSON(myResponse.ParsingError())
        }

        // if err := employee.Validate(); err != nil {
        //  return c.JSON(myResponse.ValidationError(err))
        // }

        if result := db.Omit("EmployeeRole.*").Omit("EmployeeGroup.*").Create(&employee); result.Error != nil {
            return c.JSON(myResponse.RecordCreateError())
        }

        return c.JSON(myResponse.RecordCreateSuccess(employee))
    }
}

我將傳入的 JSON 解析為 model 看起來像這樣,我嘗試在數據庫中創建它

{
    "Person":{
        "Initials":"",
        "FirstName":"",
        "MiddleName":"",
        "LastName":"",
        "DateOfBirth":"",
        "Language":""
    },
    "Address":{
        "Country":"",
        "Zip":"",
        "Number":"0",
        "Addition":"",
        "Street":"",
        "State":"",
        "City":""
    },
    "Contact":{
        "Tel":"",
        "Mail":"",
        "URL":""
    },
    "BankAccount":{
        "Bank":"",
        "BIC":"",
        "IBAN":"",
        "AccountHolder":"",
        "Establishment":""
    },
    "EmployeeRoleID":1,
    "EmployeeRole":{
        "Title":"Test"
    },
    "EmployeeGroupID":1,
    "EmployeeGroup":{
        "Title":"Test"
    }
}

這給了我以下回應

{
    "data": {
        "id": 7,
        "person": {
            "initials": "",
            "firstName": "",
            "middleName": "",
            "lastName": "",
            "dateOfBirth": "2021-01-05T11:14:38+01:00",
            "language": ""
        },
        "contact": {
            "tel": "",
            "mail": "",
            "url": ""
        },
        "address": {
            "country": "",
            "zip": "",
            "number": "0",
            "addition": "",
            "street": "",
            "state": "",
            "city": ""
        },
        "privateContact": {
            "tel": "",
            "mail": "",
            "url": ""
        },
        "bankAccount": {
            "bank": "",
            "bic": "",
            "iban": "",
            "accountHolder": "",
            "establishment": ""
        },
        "groupID": 0,
        "group": {
            "id": 0,
            "title": ""
        },
        "roleID": 0,
        "role": {
            "id": 0,
            "title": ""
        }
    },
    "message": "record created successfully",
    "status": "success"
}

即使 ID 為 1 的角色記錄和 ID 為 1 的組記錄不存在,這也會給我以下響應。 如果角色或組不存在,它應該給我一個錯誤而不是創建記錄。

您如何建立關系並不明顯,因為您似乎對 EmployeeGroup 和 EmployeeRole 都使用了匿名嵌入式結構,而且您還沒有包含這些結構的代碼。 我將假設您已正確設置,並且 gorm 很樂意處理涉及匿名嵌入式結構的關系。

我還將假設您的意思是 BelongsTo 關系,否則您會將外鍵放在哪里以將 Employee 表與 Role 或 Group 表鏈接? 很明顯,外鍵不在最后兩個中。

因此,您獲得了一個 Employee 結構,當給定無效的 GroupID 或 RoleID 時,您有兩個選擇:要么在 ID 不存在時拒絕操作,要么使用給定的 ID 創建一個新的角色/組。 第一個是更理智和通常的處理方式,但 gorm 可以做到。

首先,如果您的數據庫中有外鍵檢查,您可以執行以下操作:

// First make sure that EmployeeRoleID and EmployeeGroupID are set 
err := db.Omit("EmployeeRole", "EmployeeGroup").Create(employee).Error

如果 EmployeeGroup.ID 或 EmployeeRole.ID 不存在,則會發生外鍵沖突並且您將收到錯誤消息。 您可以檢查錯誤並推斷它與外鍵有關,然后返回適當的 API 級錯誤。

根據您使用的數據庫,您可能會發現此錯誤檢查有點麻煩。 老實說,在保存實體之前觸發一堆額外的關系驗證查詢是非常常見的,所以在這種情況下不必對必須這樣做感到驚訝。

另一方面,如果你想每次都保存一個新的 Role 和 Group,你可以去掉 Omit 調用,確保每個 ID 都為零,然后調用 Create。 Gorm 將保存角色和組,給他們新的 ID,然后將這些新 ID 的鏈接保存在員工記錄中。


編輯

我嘗試運行您的代碼,並發現了一些問題:

  • 首先,您的輸入 JSON 具有所有 TitleCased 鍵名,但 model 結構預計其中很多都是小寫的。 您需要決定其中一個並堅持下去,否則編組和解組將不起作用。
  • 同樣適用於EmployeeRoleID / EmployeeGroupID vs groupID / roleID 在 JSON 中有一個版本的名稱,在 Go 結構中有一個版本(如果您刪除了json:"blah"結構,它們也可以是相同的 TitleCased 版本)。
  • Gorm 不喜歡為相關實體嵌入匿名結構,它不會為它們創建外鍵並在主表中創建無關字段,不要這樣做。
  • 您已將 Role 和 Group 的名稱用作復合主鍵的一部分,但這會破壞鍵控,因為您需要主結構中的兩個字段才能使其工作,例如EmployeeGroupIDEmployeeGroupName ,這違背了擁有的目的一個單獨的實體。 如果要強制名稱的唯一性,請改為添加唯一索引。
  • 原來Omit("Relation.*")僅適用於多對多關系。 對於belongs-to 要做的事情是填寫主結構中的RelationID字段,並通常用Omit("Relation")

這是您的 model 的簡化版本,它可以工作:

type Employee struct {
    PrivateGormModel
    Person  `gorm:"embedded" json:"Person"`
    // ...
    RoleID  uint
    Role    EmployeeRole `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}

type EmployeeRole struct {
    PrivateGormModel
    Title string `gorm:"uniqueIndex"`
}

這是一個測試用例,它顯示了它是如何工作的,假設有一個已配置的DB *gorm.DB

package main

import (
    "encoding/json"
    "testing"

    "github.com/stretchr/testify/require"
)

var inJSON = `{
    "Person": {
        "FirstName": "Test"
    },
    "RoleID": 1
}`

func TestGORM(t *testing.T) {
    require := require.New(t)
    require.NoError(DB.Migrator().DropTable(&Employee{}, &EmployeeRole{}))
    require.NoError(DB.Migrator().AutoMigrate(&Employee{}, &EmployeeRole{}))
    emp := Employee{}
    json.Unmarshal([]byte(inJSON), &emp)

    // create the role to simulate that it exists
    role := EmployeeRole{PrivateGormModel{ID: 1}, "Test"}
    require.NoError(DB.Create(&role).Error)

    // avoid re-saving emp.Role
    require.NoError(DB.Omit("Role").Create(&emp).Error)

    // if instead the RoleID doesn't exist
    emp.RoleID = 5
    require.Error(DB.Create(&emp).Error)
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM