【gorm】无法将字段更新为空值

在使用gorm将一个字段更新为空的时候,发现并不生效,不了解具体什么原因,所以此时需要打开debug模式,查看原生SQL是如何执行的。

type Student struct {
	Model
    Email       string  `form:"email" json:"email"`
	Name 		string	`form:"name"  json:"name"`
}

func(c *Content) update(content Content) (err) {
    err = db.Model(&Content{}).Debug().Where("id = ?", 123).Update(&content).Error
}

查看日志便知,此时如果name为空字符串,那么update的sql语句中并不会set,后查阅,方知gorm对于空字符和0这种数据,认为是不需要处理的,所以。。。

遇到这个问题,有两种解决方案:

A.更新传值的时候通过map来指定;

B.修改gorm的源码包,让它支持自定义是否可以设置为空值;

上述两种方案,第一种比较简单,不过感觉比较low,所以我选择尝试第二种。

当然第二种,也有它的问题,比如被更新之后,得手动去调整回来。

下面重点讲解第二种方案的实施。

1、找到gorm包下的scope.go文件

func convertInterfaceToMap(values interface{}, withIgnoredField bool, db *DB) map[string]interface{} {
	var attrs = map[string]interface{}{}

	switch value := values.(type) {
	case map[string]interface{}:
		return value
	case []interface{}:
		for _, v := range value {
			for key, value := range convertInterfaceToMap(v, withIgnoredField, db) {
				attrs[key] = value
			}
		}
	case interface{}:
		reflectValue := reflect.ValueOf(values)

		switch reflectValue.Kind() {
		case reflect.Map:
			for _, key := range reflectValue.MapKeys() {
				attrs[ToColumnName(key.Interface().(string))] = reflectValue.MapIndex(key).Interface()
			}
		default:
			for _, field := range (&Scope{Value: values, db: db}).Fields() {
				if !field.IsBlank && (withIgnoredField || !field.IsIgnored) {
					attrs[field.DBName] = field.Field.Interface()
				}
			}
		}
	}
	return attrs
}

上面代码表示我们传递过来的数据会被转为map型,然后再进行数据库字段更新,这个代码很简单,就是把满足条件保存到map。

我们要解决的是空值能够更新,则和field.IsBlank相关联,接着找到下一个方法;

// Fields get value's fields
func (scope *Scope) Fields() []*Field {
	if scope.fields == nil {
		var (
			fields             []*Field
			indirectScopeValue = scope.IndirectValue()
			isStruct           = indirectScopeValue.Kind() == reflect.Struct
		)

		for _, structField := range scope.GetModelStruct().StructFields {
			if isStruct {
				fieldValue := indirectScopeValue
				for _, name := range structField.Names {
					if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
						fieldValue.Set(reflect.New(fieldValue.Type().Elem()))
					}
					fieldValue = reflect.Indirect(fieldValue).FieldByName(name)
				}
				//判断model中字段有没有force字段,此处使用Force,是因为解析tag时,统一转成了大写
				_, ok := structField.TagSettingsGet("FORCE")

				//如果字段为空值,且字段存在则设定为false
				fields = append(fields, &Field{StructField: structField, Field: fieldValue, IsBlank: isBlank(fieldValue) && !ok})
			} else {
				fields = append(fields, &Field{StructField: structField, IsBlank: true})
			}
		}
		scope.fields = &fields
	}

	return *scope.fields
}

注意上面带注释的两行,作用就不再赘述了。

2、修改我们的model

type Student struct {
	Model
    Email       string  `gorm:"force" form:"email" json:"email"`
	Name 		string	`gorm:"force" form:"name"  json:"name"`
}

func(c *Content) update(content Content) (err) {
    err = db.Model(&Content{}).Debug().Where("id = ?", 123).Update(&content).Error
}

此时执行则顺利完成,SQL语句也包含了所有字段。

3、你有没有感到奇怪,为什么scope里面校验的是FORCE,而我在model中则定义的是force?

这就要说到另一个文件 gorm包下的 model_struct.go

func parseTagSetting(tags reflect.StructTag) map[string]string {
	setting := map[string]string{}
	for _, str := range []string{tags.Get("sql"), tags.Get("gorm")} {
		if str == "" {
			continue
		}
		tags := strings.Split(str, ";")
		for _, value := range tags {
			v := strings.Split(value, ":")
			k := strings.TrimSpace(strings.ToUpper(v[0]))
			if len(v) >= 2 {
				setting[k] = strings.Join(v[1:], ":")
			} else {
				setting[k] = k
			}
		}
	}
	return setting
}

这个方法表示将orm中定义的tag标签,全部解析并转化为大写。

至此,讲完了。

你可能感兴趣的:(Golang,golang,go,mysql)