如何设计链式 Model.find() 函数?

Posted

技术标签:

【中文标题】如何设计链式 Model.find() 函数?【英文标题】:How to design a chained Model.find() function? 【发布时间】:2013-07-24 15:05:26 【问题描述】:

我正在尝试在 Node.js 中编写 ORM。我想声明一个名为 Model 的类,它将用于声明一个数据对象,例如:

Users = new Model(someModelRules);
newUser = new Users(userInfomation);

数据模型User 有一个名为find() 的函数。现在,我想将find() 链接起来,比如:

Users.find(" name = 'John' ")
.orderedBy("age").desc()
.limit(0,10)

或者只是一个简单的find:

Users.find(" name = 'John' ")

要编码这个find函数,我相信我必须先构建SQL,并在这个find链的末尾进行SQL查询。

我不知道怎么做,我能想到的就是添加一个像:doQuery()这样的函数,这样我就会知道在调用doQuery()函数时是时候进行SQL查询了,比如:

Users.find(" name = 'John' ")
.orderedBy("age").desc()
.limit(0,10)
.doQuery();

我知道这是一个简单的解决方案,但我不想要额外的 doQuery() 函数。 :(

那么,我应该如何设计呢?如果您能给我看一些带有 cmets 的示例代码,那就太好了。

谢谢! (对不起我的英语不好)

ps。我知道ORM2 有一个我想要的查找功能,但我想知道如何对其进行编码,而且我几乎无法理解 ORM2 中的代码,因为没有 cmets。 (我不会使用 orm2。)

==================================解决方案============= =================

受@bfavaretto 启发:

function User() 
    this.find = function(id, condition) 
        return new findChain(id, condition);
    



function findChain(id, condition) 
    this._id = id
    this._condition = condition
    this.queryTimerSet = false;

    this.scheduleQuery = function () 
        var self = this;
        if(!self.queryTimerSet) 
            console.log('[TEST CASE: ' + self._id + '] Insert query into eventLoop');
            setTimeout(function()
                console.log('[TEST CASE: ' + self._id + '] Start query: '+self._condition);
            , 0);
            self.queryTimerSet = true;
         else 
            console.log('[TEST CASE: ' + self._id + '] No need to insert another query');
        
    

    this.orderedBy = function(column) 
        console.log('[TEST CASE: ' + this._id + '] orderedBy was called');
        this._condition = this._condition + ' ORDER BY ' + column
        this.scheduleQuery();
        return this;
    

    this.desc = function() 
        // simply add DESC to the end of sql
        this._condition = this._condition + ' DESC'
    

    this.scheduleQuery();




var user = new User();
user.find(1,'SELECT * FROM test').orderedBy('NAME1').desc();
user.find(2,'SELECT * FROM test').orderedBy('NAME2');
user.find(3,'SELECT * FROM test');

运行这段代码,你会得到结果:

[TEST CASE: 1] Insert query into eventLoop
[TEST CASE: 1] orderedBy was called
[TEST CASE: 1] No need to insert another query
[TEST CASE: 2] Insert query into eventLoop
[TEST CASE: 2] orderedBy was called
[TEST CASE: 2] No need to insert another query
[TEST CASE: 3] Insert query into eventLoop
[TEST CASE: 1] Start query: SELECT * FROM test ORDER BY NAME1 DESC
[TEST CASE: 2] Start query: SELECT * FROM test ORDER BY NAME2
[TEST CASE: 3] Start query: SELECT * FROM test

我相信一定有更好的方法来实现这一点,但这是我目前能得到的最好的方法。 有cmets吗?

【问题讨论】:

【参考方案1】:

如果您安排doQuery 逻辑异步运行(但要尽快),则可以实现这一点。我正在考虑这样的事情:

function User() 

    // Flag to control whether a timer was already setup
    var queryTimerSet = false;

    // This will schedule the query execution to the next tick of the
    // event loop, if it wasn't already scheduled.
    // This function is available to your model methods via closure.
    function scheduleQuery() 
        if(!queryTimerSet) 
            setTimeout(function()
                // execute sql
                // from the query callback, set queryTimerSet back to false
            , 0);
            queryTimerSet = true;
        
    

    this.find = function() 
        // ... logic that builds the sql

        scheduleQuery();
        return this;
    

    this.orderedBy = function() 
        // ... logic that appends to the sql

        scheduleQuery();
        return this;
    

    // etc.


一种完全不同的方法是使用单一方法来构建 SQL,并在选项对象中传递 ORDER BY 和 LIMIT 参数。那么你的调用将如下所示:

user.find(
    what : " name = 'John' ",
    orderedBy : "age DESC",
    limit : "0,10"
);

这比您尝试做的更适合 SQL 查询。你所拥有的看起来像 MongoDB 这样的 noSQL 东西,其中获取记录和排序是单独的操作(我认为)。

【讨论】:

好的......但似乎每次调用(或几乎)函数时都会进行一次sql查询。如果我错了,请纠正我。谢谢,这很有趣,我会试着这样想。 queryTimerSet 标志旨在避免这种情况。如果前一个函数还没有这样做,您只会安排一个新的查询。 我明白了...所以 scheduleQuery() 将异步运行,所以代码不会等待它,而只是 return this ?但我认为scheduleQuery() 仍在运行,并且链中的每个函数都会调用scheduleQuery() 来运行,所以如果我写这样的代码:User.find("name='John'").orderedBy('age'),将会有2 个scheduleQuery() 在事件循环中运行。我对吗? (对不起,我是 Node.js 新手) @JeremyHou 实际上,scheduleQuery 是同步的。只有实际执行查询的部分(setTimeout 内部)是异步的。请参阅此示例运行:jsfiddle.net/Cp8BW。这种方法有一个主要缺点:您不能按顺序进行两个不同的find() 调用,除非您以某种方式实施一种方法来强制执行挂起的查询以开始新的查询。 我明白了!我想我可以声明另一个类(我们称之为 myQuery ),它将具有 orderedBy()limit()scheduleQuery() 函数,并且每次调用 find() 函数时,都会创建一个新的 myQuery 对象并find() 函数可以调用 newMyQuery.scheduleQuery(); return newMyQuery;。因此我可以制作多个find()。感谢您指出实现这一目标的方法!【参考方案2】:

你总是必须在链的末端有一个执行/doQuery 函数。 这是因为 doQuery 之前的所有其他函数都有助于构建需要在最后执行的查询。

【讨论】:

这将对性能产生巨大影响,因为它可能会多次查询。我宁愿走doQuery()的方式……我相信一定有办法做到这一点。谢谢。

以上是关于如何设计链式 Model.find() 函数?的主要内容,如果未能解决你的问题,请参考以下文章

如何在链式函数中将选定元素传递给“this”

如何使用 jest 模拟链式函数调用?

链式法则

如何在 Sinon 中模拟链式函数调用

设计模式 创建者模式 -- 建造者模式扩展: 链式编程(解决构造函数参数列表过长的可读性问题)

为链式函数使用 Swift 完成处理程序