Fields/FieldsEx字段过滤

  1. Fields  用于指定需要操作的表字段,包括查询字段、写入字段、更新字段等过滤;
  2. FieldsEx  用于例外的字段指定,可用于查询字段、写入字段、更新字段等过滤;

Fields示例

  1. 假如user表有4个字段uid,  nickname,  passport,  password
  2. 查询字段过滤

     // SELECT `uid`,`nickname` FROM `user` ORDER BY `uid` asc
     g.Model("user").Fields("uid, nickname").Order("uid asc").All()
  3. 写入字段过滤

    m := g.Map{
         "uid"      : 10000,
         "nickname" : "John Guo",
         "passport" : "john",
         "password" : "123456",
     }
     g.Model(table).Fields("nickname,passport,password").Data(m).Insert()
     // INSERT INTO `user`(`nickname`,`passport`,`password`) VALUES('John Guo','john','123456')

FieldsEx示例

  1. 假如user表有4个字段uid,  nickname,  passport,  password
  2. 查询字段排除

     // SELECT `uid`,`nickname` FROM `user`
     g.Model("user").FieldsEx("passport, password").All()
  3. 写入字段排除

     m := g.Map{
         "uid"      : 10000,
         "nickname" : "John Guo",
         "passport" : "john",
         "password" : "123456",
     }
     g.Model(table).FieldsEx("uid").Data(m).Insert()
     // INSERT INTO `user`(`nickname`,`passport`,`password`) VALUES('John Guo','john','123456')

OmitEmpty空值过滤

  map/struct  中存在空值如  nil,"",0  时,默认情况下,gdb将会将其当做正常的输入参数,因此这些参数也会被更新到数据表。OmitEmpty特性可以在将数据写入到数据库之前过滤空值数据的字段。

相关方法:

func (m *Model) OmitEmpty() *Model
func (m *Model) OmitEmptyWhere() *Model
func (m *Model) OmitEmptyData() *Model 

OmitEmpty方法会同时过滤WhereData中的空值数据,而通过OmitEmptyWhere/OmitEmptyData方法可以执行特定的字段过滤。

写入/更新操作

空值会影响于写入/更新操作方法,如Insert,  Replace,  Update,  Save操作。如以下操作(以map为例,struct同理):

// UPDATE `user` SET `name`='john',update_time=null WHERE `id`=1
g.Model("user").Data(g.Map{
    "name"        : "john",
    "update_time" : nil,
}).Where("id", 1).Update()

针对空值情况,我们可以通过OmitEmpty方法来过滤掉这些空值。例如,以上示例可以修改为:

// UPDATE `user` SET `name`='john' WHERE `id`=1
g.Model("user").OmitEmpty().Data(g.Map{
    "name"        : "john",
    "update_time" : nil,
}).Where("id", 1).Update()

对于struct数据参数,我们也可以进行空值过滤。操作示例:

type User struct {
    Id         int    `orm:"id"`
    Passport   string `orm:"passport"`
    Password   string `orm:"password"`
    NickName   string `orm:"nickname"`
    CreateTime string `orm:"create_time"`
    UpdateTime string `orm:"update_time"`
}
user := User{
    Id        : 1,
    NickName  : "john",
    UpdateTime: gtime.Now().String(),
}
g.Model("user").OmitEmpty().Data(user).Insert()
// INSERT INTO `user`(`id`,`nickname`,`update_time`) VALUES(1,'john','2019-10-01 12:00:00')

注意哟,批量写入/更新操作中OmitEmpty方法将会失效,因为在批量操作中,必须保证每个写入记录的字段是统一的。

关于omitempty标签与OmitEmpty方法:

  1. 针对于struct的空值过滤大家会想到omitempty的标签。该标签常用于json转换的空值过滤,也在某一些第三方的ORM库中用作struct到数据表字段的空值过滤,即当属性为空值时不做转换。
  2. omitempty标签与OmitEmpty方法所达到的效果是一样的。在ORM操作中,我们不建议对struct使用omitempty的标签来控制字段的空值过滤,而建议使用OmitEmpty方法来做控制。因为该标签一旦加上之后便绑定到了struct上,没有办法做灵活控制;而通过OmitEmpty方法使得开发者可以选择性地、根据业务场景对struct做空值过滤,操作更加灵活。

数据查询操作

空值也会影响数据查询操作,主要是影响where条件参数。我们可以通过OmitEmpty方法过滤条件参数中的空值。

使用示例:

// SELECT * FROM `user` WHERE `passport`='john' LIMIT 1
r, err := g.Model("user").Where(g.Map{
    "nickname" : "",
    "passport" : "john",
}).OmitEmpty().One()
type User struct {
    Id         int    `orm:"id"`
    Passport   string `orm:"passport"`
    Password   string `orm:"password"`
    NickName   string `orm:"nickname"`
    CreateTime string `orm:"create_time"`
    UpdateTime string `orm:"update_time"`
}
user := User{
    Passport : "john",
}
r, err := g.Model("user").OmitEmpty().Where(user).One()
// SELECT * FROM `user` WHERE `passport`='john' LIMIT 1

OmitNil空值过滤

基本介绍

  map/struct  中存在空值如  nil时,默认情况下,gdb将会将其当做正常的输入参数,因此这些参数也会被更新到数据表。OmitNil特性可以在将数据写入到数据库之前过滤空值数据的字段。与OmitEmpty特性的区别在于,OmitNil只会过滤值为nil的空值字段,其他空值如"",0并不会被过滤。

相关方法:

func (m *Model) OmitNil() *Model
func (m *Model) OmitNilWhere() *Model
func (m *Model) OmitNilData() *Model 

OmitEmpty方法会同时过滤WhereData中的空值数据,而通过OmitEmptyWhere/OmitEmptyData方法可以执行特定的字段过滤。

使用do对象进行字段过滤

如果使用GoFrame工程目录,通过gf gen dao或者make dao指令会自动根据配置的数据库生成对应的数据表dao/entity/do文件,如果在数据库操作中使用do对象,那么将会自动过滤未赋值的字段。例如:

生成的do对象结构体定义

// User is the golang structure of table user for DAO operations like Where/Data.
type User struct {
	g.Meta   `orm:"table:user, do:true"`
	Id       interface{} // User ID
	Passport interface{} // User Passport
	Password interface{} // User Password
	Nickname interface{} // User Nickname
	CreateAt *gtime.Time // Created Time
	UpdateAt *gtime.Time // Updated Time
}

数据写入:

dao.User.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
	_, err = dao.User.Ctx(ctx).Data(do.User{
		Passport: in.Passport,
		Password: in.Password,
		Nickname: in.Nickname,
	}).Insert()
	return err
})

数据查询:

var user *entity.User
err = dao.User.Ctx(ctx).Where(do.User{
	Passport: in.Passport,
	Password: in.Password,
}).Scan(&user)

Filter字段过滤(已内置)

gdb可以自动同步数据表结构到程序缓存中(缓存不过期,直至程序重启/重新部署),并且可以过滤提交参数中不符合表结构的数据项,该特性可以使用Filter方法实现。常用于新增/删除操作中输入map/struct/[]map/[]string参数类型的场景。

使用示例,假如user表有4个字段uid,  nickname,  passport,  password

r, err := g.Model("user").Filter().Data(g.Map{
    "id"          : 1,
    "uid"         : 1,
    "passport"    : "john",
    "password"    : "123456",
}).Insert()
// INSERT INTO user(uid,passport,password) VALUES(1, "john", "123456")

其中id为不存在的字段,在写入数据时将会被过滤掉,不至于被构造成写入SQL中产生执行错误。

数据库没有设计为Data方法做自动过滤,而是需要开发者调用Filter方法来手动指定过滤,目的是友好地提醒开发者可能误写/传递错误了字段名称。如果强制性的自动过滤可能会引起难以预料的业务逻辑异常,例如,由于字段名称拼写错误导致自动过滤了本来需要输入的字段,导致写入数据库的数据不完整。

GoFrame v1.15.7版本开始,根据社区整体反馈,为提高组件易用性,filter特性被设置为默认开启,不再需要显示调用,Filter方法已被标记废弃。



Content Menu

  • No labels

27 Comments

  1. kim

    发现

    OmitEmpty()方法对于save操作无效。调了此方法后,save一个有部分空值字段的结构体会报错。

    1. 具体什么报错信息?是数据表字段设置为NOT NULL了但是程序被过滤空值了吗?

      1. kim

        用Save()方法进行修改的时候:比如一个5个字段的结构体,实例化一个只有2个字段有值(其中一个是主键)的对象作为Save()方法的参数,如果对象另外三个空字段(对应在数据库表中是有值的)在数据库中设置了not null约束则会报没有默认值的异常,如果不设置not null就没问题。但是正常应该是不管设不设置not null都不受影响,因为OmitEmpty()方法已经把结构体对象中的空字段给屏蔽了,再接着调Save()方法应该不受结构体对象参数中的空属性影响。

        1. 这个其实数据库的模式有关,在就是not null的字段建议有默认值,不然哪天换数据服务器,就掉坑了

  2. 假设有这样的一个数据表:

    type User struct {
        ID   int64
        Name string
        Age  int64
    }

    前端想对其进行部分更新,发送Patch请求

    因为是部分更新,所以我将请求绑定到UserReq上 ,并且使用了OmitEmpty()函数

    type UserReq struct {
        Name *string
        Age  *int64
    }

    当前端的请求只包含Name的时候,Age 属性值就是nil,自然被过滤掉,这样就只更新Name

    但之后又发现了问题,如果前端想把Name更新为Null,这该怎么办?

    null绑定结构体后,是nil,会被过滤掉

    在业务场景中,也不能用空字符串代替nullName空字符串代表用户无名字,null代表用户名字未知,都是需要的

    请问在这种情形下,对于Patch请求,有什么好的数据绑定或者过滤方案吗?

    1. 我也有这样的疑惑,请问您解决了吗

      1. g.Map{"name":null} 不应该是写成 g.Map{"name":""} z这样吗?去掉OmitEmpty 这个方法吗?

        为啥要用null 这种,是不是在设计表的时候 就是空字符串吗?

        1. name字符串的话,null代表用户名字未知,空字符串代表用户无名字

          还有其他场景,比如合同金额int64,null代表未知,用户还没有填写合同金额,0代表合同金额为0元

          我不是很喜欢用map,结构体的话比较方便,可以提示属性,也方便加tag


      2. 还没有解决,和前端约定了某个值代表Null,暂时处理

  3. OmitEmpty 使用的时候非常方便,但是有一点麻烦的就是比如我有个字段 "备注"本来是有文字内容的 update的时候想更新为空白没有文字,这时候使用OmitEmpty就没办法更新这个字段了,能否考虑像,FieldsEx 那样加个OmitEmptyFieldsEx

    这样就可以排除指定字段不执行OmitEmpty了

    1. 直接使用Fields方法指定更新字段即可,试试看。

      1. 好的我去实验下,晚点过来反馈.

  4. dao.SysRole.LeftJoin("sys_role_menu role_menu", "sys_role.id=role_menu.role_id").Fields("sys_role.id,sys_role.name").Fields(1).Count() 这样的查询再1.16.5中可以正常,但是在1.16.6中就会报错。请问是做了什么改动吗?
  5. OmitEmpty有时候不起效果?

    dao.User.Ctx(ctx).OmitEmpty().Where(dao.User.C.CompanyId, req.CompanyId).WhereLike(dao.User.C.Username, req.Key)

    结果

    SELECT `id`,`username`,`company_id` FROM `admin_user` WHERE (`company_id`=0) AND (`username` LIKE '') LIMIT 0,20

  6. 如果OmitEmpty()全局设置很麻烦,建议参考php Yii2 ORM框架的

     whereFilter 在入参的时候就做过滤

  7. 如果要  过滤nil 跟 “”  但不过滤0  这种的要用哪个?

  8. OmitEmpty 或 OmitNull 可以加个入参  控制 哪些要过滤 会更灵活一点。。

  9. 文档应该不是2的吧,2的字段过滤不是可以直接传字段结构体了吗

    1. 是的,在工程项目中可以直接用do对象自动过滤,文档中还没有介绍。我更新一下。

  10. 我Fields结果的字段在mysql数据库里面是大写 在mssql里面是小写要怎么办,很多地方都有这个问题 有什么批量解决的办法嘛

  11. 在insert语句中,常常需要忽略主键字段,因为数据库采用自增或者序列自动设置的方式,id是需要忽略值的,但是偏偏使用model方式直接插入数据库会导致id int类型默认为0也会插入数据库,这会造成一个问题。

    我在想是否需要增加一个 OmitEmptyPK()方法来支持忽略 主键 是0或者空字符串的情况?

    1. 不用entity用do即可

      1. do太麻烦了,需要每个entity重新构造,不太适用于 使用基础基类 base类这种统一处理保存数据的通用场景。


        我还是自己改了源码加了OmitEmptyPK()方法来统一过滤int 0 和string ""类型 的主键 数据了,在Save、InsertOrUpdateOption 等场景里面自动过滤主键的赋值。

  12. 关于字段过滤,不知道是不是学的不够深入,我发现是把这个字段置为零值,而不是直接去掉这个字段:

    entity:

    type User struct {
    	Id       int    `json:"id"       ` //
    	Login    string `json:"login"    ` //
    	Password string `json:"password" ` //
    	Name     string `json:"name"     ` //
    	UserName string `json:"userName" ` //
    	Phone    string `json:"phone"    ` //
    	Email    string `json:"email"    ` //
    	Length   int    `json:"length"   ` //
    }

    logic:

    func (s *sUser) UserSearch(ctx context.Context) []*entity.User {
    	var res []*entity.User
    	userModel := dao.User.Ctx(ctx)
    	_ = userModel.FieldsEx(dao.User.Columns().Password).Scan(&res)
    	return res
    }

    返回数据:

    {
        "code": 0,
        "message": "",
        "data": {
            "userList": [
                {
                    "id": 1,
                    "login": "aa",
                    "password": "",
                    "name": "小明",
                    "userName": "xiaoming",
                    "phone": "111111111111",
                    "email": "xiaoming@mail.com",
                    "length": 100
                }
            ]
        }
    }

    只能自己手动处理删除字段吗?

  13. OmitEmptyData 对slice类型的data不生效
    dao.CustomerInternalTransRecord.Ctx(ctx).Data(g.Slice{g.Map{
    "customer_id": 1840,
    "trans_type": 2,
    "come": 0,
    "go": 8,
    "create_user_id": 2,
    }, g.Map{
    "customer_id": 1840,
    "trans_type": 2,
    "come": 0,
    "go": 8,
    "create_user_id": 2,
    }}).OmitEmptyData().Insert()

    INSERT INTO "customer_internal_trans_record"("customer_id","trans_type","come","go","create_user_id","create_at","update_at") VALUES(1840,2,0,8,2,'2024-04-11 11:23:07','2024-04-11 11:23:07'),(1840,2,0,8,2,'2024-04-11 11:23:07','2024-04-11 11:23:07')

    come为0 但是没有过滤掉