如何使用Bookshelf.js正确更新模型?

14
我相信我错过了一些东西,但我觉得对于我来说,书架API仍然是令人困惑的。这是我想做的事情:
  • 我有一个名为Radio的模型,其中包含一个应用分配的字符串主键,名为serial,并且为了本示例,包含两个名为example1example2的字段。我在模型定义中使用idAttribute: 'serial'指定了自定义ID。
  • 我正在尝试使用Bookshelf执行upsert(而不是直接使用Knex,在我的实际应用程序中,该查询变得相当复杂)。
  • 我已经使插入情况工作,但似乎无法使更新情况工作。
  • 为简单起见,我现在不关心事务或原子性。我满意于让一个简单的select → insert/update工作。

特别是在这个例子中:

  • 在插入时设置example1example2
  • 在更新时设置example1并保持example2不变。

我在书架模型中有这样一个类(即“静态”)方法,类似于以下内容(“info”具有“序列号”,“示例1”和“示例2”字段):

insertOrUpdate: function (info) {
    return new Radio({'serial':info.serial}).fetch().then(function (model) {
        if (model) {
            model.set('example1', info.example1);
            return model.save({}, {
                method: 'update',
                patch: true
            })
        } else {
            return new Radio({
                serial: info.serial,
                example1: info.example1,
                example2: info.example2
            }).save({}, {
                method: 'insert'
            })
        }
    }).then(function (model) {
        console.log("SUCCESS");
    }).catch(function (err) {
        console.log("ERROR", err);
    });
}

示例调用:

Radio.insertOrUpdate({
    serial: ...,
    example1: ...,
    example2: ...
})

我在这里遇到的问题是,虽然“插入”情况可以正常工作,但“更新”情况会失败并显示以下错误信息:
ERROR { Error: ER_PARSE_ERROR: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'where `serial` = '123223'' at line 1

当Knex调试打开时,显然查询语句缺少set子句。
update `radios` set  where `serial` = ?

现在,我正在关注fetchsave的书架文档,并且我想知道我是否走错了方向。
我知道我在错误地使用API,但我无法弄清楚。为了使其半工作状态,我注意到了一些奇怪的事情/必须要做的事情。
  • 我不理解save的第一个参数是什么意思。如果saveModel的一个静态方法,那么它对我来说就有意义了,但事实并非如此。它是一个实例方法,你已经可以在Model构造函数中传递属性(例如,new X(a:1).save({a:2})是什么意思?),并且你已经可以在保存之前使用set设置属性。所以我无法理解这个参数。我必须传递{}作为占位符,以便让我指定选项。

  • 有这个forge的东西,但我不确定它的目的是什么,因为你已经可以在Model构造函数中传递属性了(除非作者发现X.forge({a:1})new X({a:1})相比有一些好处...?)。

  • 我发现我必须明确指定保存方法,因为似乎存在一个Bookshelf怪癖:Bookshelf基于isNew()选择方法,但当你将id传递给模型构造函数时,isNew()总是true,在应用程序分配ID的情况下,你必须这样做。因此,对于应用程序分配的ID,Bookshelf将始终执行“插入”,因为它始终认为模型是“新的”。所以你必须强制方法为“更新”...这只会增加我的Bookshelf困惑。

无论如何,我该怎样才能做好这个?我该如何让这个插入和更新工作?

更重要的是,这在哪里有记录?我真诚地认为它在某个地方被清晰地记录下来了,只是我现在看不到树林了,所以比起直接回答,我更需要一些文档上的指引,因为我确实需要搞清楚这个。我已经花了很多时间在书架上,而不是真正的开发,以至于我几乎希望我一开始就坚持使用直接的SQL查询。


1
通过发送 .save({}, options),您明确地向 Bookshelf 发送了一个空的更新对象。您可以尝试使用 null 代替吗?即 .save(null, options) - Eric Ly
2个回答

9

这是一个有趣的问题,花了我一些时间才理解发生了什么。

正如您已经发现的那样,save() 方法的文档关于patch选项的说明如下:

只保存传递给 save 的参数中包含的属性。

因此,您只需要更改您的代码为

if (model) {
    model.set('example1', info.example1);
    return model.save();
}

并且set属性将被保存。

但是 但是 但是 但是

所有属性都将进入update语句,即使是id

这是ORM的常见行为,其理由是,如果我们从一个事务获取了数据,并从另一个事务进行保存(不好的做法!),则数据可能已经被其他客户端更改。因此,仅保存部分属性可能会导致不一致的状态。

但是patch属性的存在本身就违反了这个概念。因此,Bookshelf可以通过以下方式进行改进:

  • 仅仅废弃patch选项。(我比较喜欢这个)
  • 由于Bookshelf模型跟踪变更的属性,所以在这方面使更新变得更加智能应该是微不足道的。这种变化也可能导致patch选项的废弃。
  • 另一种方法是使patch语意与更改的属性相关,而不仅仅是在save()上提供的那些属性。但是这种变化可能不幸地破坏某些用例。
  • 最后,引入一个新的选项来作用于所有更改的属性。但是这感觉有点混乱。

4
经过多次猜测,我似乎已经让它正常工作了,但我不知道这是否正确,也不知道如何在没有猜测的情况下确定它的正确性。基本上,我能够通过修改"update"案例来使其正常运行: 将属性作为第一个参数传递给save,而不是用set设置它们。最终解决方案如下:
insertOrUpdate: function (info) {
    return new Radio({'serial':info.serial}).fetch().then(function (model) {
        if (model) {
            // pass params to save instead of set()
            var params = { 'example1' : info.example1 }
            return model.save(params, {
                method: 'update',
                patch: true
            })
        } else {
            return new Radio({
                serial: info.serial,
                example1: info.example1,
                example2: info.example2
            }).save({}, {
                method: 'insert'
            })
        }
    }).then(function (model) {
        console.log("SUCCESS");
    }).catch(function (err) {
        console.log("ERROR", err);
    });
}

我仍然不确定forge在这里的作用是什么,或者在“插入”情况下save的第一个参数应该是什么。

更重要的是,我现在并不完全确定set是用来干什么的。ORM框架的主要优点之一是使这种东西透明化(即“save”正确工作,同时让您使用模型而无需思考,而且在您“保存”时无需了解发生了什么变化 - 我应该能够预先设置未知的任意代码set,然后能够保存它而不知道发生了什么变化,但看起来我不能),所以我不确定我实际上从Bookshelf中获得了什么。必须有更好的方法。


我现在打算不接受这个方案,因为我仍然不确定它是否正确。我觉得“保存”应该能够正确地与“设置”配合使用。 - Jason C
2
这并不是回答你的问题,但我想插一句。我使用 objection.js 作为 mysql 的 ORM,它非常好用。和 bookshelf 一样,也是基于 knex 构建的。文档很好,而且还有一个创建 upsert 方法的示例。此外,编写和维护它的人会在 gitter 和 github 页面上快速而详细地回答问题。只是一个想法... - Jeff Kilbride
@JeffKilbride 谢谢,我非常感激。下周我将开始一个新项目,我正准备深入了解其他选项。能够得到另一个用户的实质性建议真是太好了。我会更仔细地研究一下Objection。 - Jason C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接