mongoose多級嵌套操作


轉載請注明: TheViper http://www.cnblogs.com/TheViper 

模型就以貼吧為例

var themeModel = modelBase._mongoose.model('theme',modelBase._schema({
    theme_name:String,
    posts:[{
        post_id:String,
        title:String,
        content:String,
        time:Number,
        user:{type:objectId,ref:'user'},
        img_list:[String],
        suppost:Boolean,
        post_ref:String,
        comments:[{
            content:String,
            time:Number,
            from:{type:objectId,ref:'user'},
            to:{type:objectId,ref:'user'}
        }]
    }]
}),'theme');

這里模型故意弄的復雜點,其實就是多點嵌套。實際開發時請根據不同場景,性能,擴展等因素綜合考慮。

mongodb其實查詢性能是比不上常見的關系數據庫的,只是寫的性能要好些,另外,no sheme模型讓模型可以很靈活,就像上面那樣,可以一個collection就包含關系數據庫的幾個表.所以這里就着重用它的優點來看看。

下面解釋下上面的model:

最上面是主題theme,比如什么吧。然后是主題,一個theme有很多主題。接着是回復,也是一個主題有多個回復。最后是評論,又是一個回復有多個評論。

具體到上面的代碼,我為了偷懶,就把主題和回復和並在一起了,這就不嚴謹了,因為百度貼吧的回復是沒有title的,而主題是必須有的,這里就不管這么多了,反正只是個演示。

注意到有個字段是post_ref,如果post是主題,那post_ref就和post_id是一樣的值;如果是回復,那post_ref就是它的上級,也就是主題的post_id.suppost是true,表示是主題;false表示是主題的回復。另外還有個表是user(id,name)就不列了。

我胡亂弄了點數據

1.查詢主題

        return function(fn){
            themeModel.aggregate().unwind('posts').match({'posts.post_ref':post_id,'posts.user':suppost_user})
            .sort({"posts.time":1}).skip(skip).limit(limit).group({_id:"$_id",posts:{$push:"$posts"}}).exec()
            .then(function(theme){
                return themeModel.populate(theme,[{path:'posts.user',select:'name'},{path:'posts.comments.from',select:'name'},
                    {path:'posts.comments.to',select:'name'}]);
            }).then(function(theme){
                fn(null,theme);
            });
        };

可以看到這里用了mongodb的aggregation。關於aggregation,這里有一個sql to aggregation mapping chart

$unwind

可以看到unwind把最上級也就是主題的theme_name,_id分別添加到posts里面的每一個項.這個就是解決mongodb多級嵌套的神器,后面可以看到每個場景都用到了這個unwind.另外aggregate是嚴格按照順序執行,也就是unwind()在這里必須在最前面,否則沒有效果。

執行完unwind后就會發現后面的match({'posts.suppost':suppost}),sort({"posts.time":-1})就很簡單了,因為現在不是數組,而是對象了,直接用.運算符取就可以取到嵌套的字段了。

$group可以想成sql里面的group by,但是有些不一樣,$group實際上是讓你自定義聚合后需要返回哪些字段,因此$group里面至少要有_id,哪怕是group({_id:null})也可以。

后面的posts:{$push:"$posts"}是將posts每一項重新添加到新的posts數組,這里一定要寫出"$posts".

然后exec()返回Promise,再在then里面對字段里面與user集合有關聯的字段進行填充(populate),取出user集合里面的name.

這里是瀑布流

2.查詢回復

    this.getPostByPostId=function(post_id,skip,limit){
        return function(fn){
            themeModel.aggregate().unwind('posts').match({'posts.post_ref':post_id})
            .sort({"posts.time":1}).skip(skip).limit(limit).group({_id:"$_id",posts:{$push:"$posts"}}).exec()
            .then(function(theme){
                return themeModel.populate(theme,[{path:'posts.user',select:'name'},{path:'posts.comments.from',select:'name'},
                    {path:'posts.comments.to',select:'name'}]);
            }).then(function(theme){
                fn(null,theme);
            });
        };
    };

可以看到和第一個相比,查詢條件變成了'posts.post_id':post_id,其他的都差不多

3.獲取主題總數

    this.getSuppostCount=function(theme_name){
        return function(fn){
            themeModel.aggregate().unwind('posts').match({theme_name:theme_name,'posts.suppost':true})
            .group({_id:null,count:{$sum:1}}).exec()
            .then(function(theme){
                fn(null,theme);
            });
        };
    };

4.獲取回復總數

    this.getPostCount=function(theme_name,post_id){
        return function(fn){
            themeModel.aggregate().unwind('posts').match({'posts.post_ref':post_id})
            .group({_id:"$_id",count:{$sum:1}}).exec()
            .then(function(post){
                fn(null,post);
            });
        };
    };

5.插入評論

    this.insertComment=function(from,to,content,time,theme_name,post_id){
        return themeModel.update({theme_name:theme_name,'posts.post_id':post_id},{$push:{'posts.$.comments':{
            from:from,
            to:to,
            content:content,
            time:time
        }}});
    };

由於是多級嵌套,所以這里實際是update.注意,這里$push的對象是posts.$.comments,而不是posts.comments.$在這里是個占位符,官方文檔說的很詳細。

另外,$是個占位符意外着update的條件必須要能夠確定posts.$  ,所以在這里條件中必須要有'posts.post_id':post_id。

6.我發出的主題

    this.getSuppostByUser=function(id){
        return function(fn){
            themeModel.aggregate().unwind('posts').match({'posts.user':mongoose.Types.ObjectId(id),'posts.suppost':true})
            .group({_id:"$_id",posts:{$push:"$posts"}}).exec()
            .then(function(post){
                console.log(post)
                fn(null,post);
            });
        };
    };

user是外鍵,實際上存儲的是用戶的id,這里一定要寫成mongoose.Types.ObjectId(user),不能直接是user,另外也不要寫成建立schema時用的mongoose.Schema.Types.ObjectId,這個很容易搞混。

7.回復我的

和6差不多,條件改成'posts.suppost':false就可以了。

8.回復我的評論

查詢條件是"posts.comments.to":user,而posts.comments是數組,所以很自然的想到用$elemMatch匹配。

    this.reply_me=function(user){
        return function(fn){
            themeModel.aggregate()
            .unwind('posts')
            .match({'posts.comments':{$elemMatch:{to:modelBase._mongoose.Types.ObjectId(user)}}})
            .sort({"posts.comments.time":-1})
            .exec().then(function(theme){
                fn(null,theme);
            });
        };
    };

可以看到多出了兩個結果,正確的結果應該是to為后四位是2534的comment.

造成這種的結果的原因在於坑爹的$elemMatch.數組中有一個結果滿足條件,mongodb就會將這個數組里的元素一並取出。

解決方法就是用兩個unwind.

    this.reply_me=function(user){
        console.log('user:'+user)
        return function(fn){
            themeModel.aggregate()
            .project('posts.comments theme_name posts.title posts.post_id')
            .unwind('posts')
            .unwind('posts.comments')
            .match({'posts.comments.to':modelBase._mongoose.Types.ObjectId(user)})
            .sort({"posts.comments.time":-1})
            .exec().then(function(theme){
                fn(null,theme);
            });
        };
    };

具體的comments結果

9.修改最后更新時間

在post內的主題添加字段update_time,每次有新的主題回復,就更新這個主題的update_time。

    this.update_time=function(theme_name,post_id,time){
        return themeModel.update({theme_name:theme_name,'posts.post_id':post_id},{$set:{'posts.$.update_time':time}});
    };

這里也用到了$,原理和上面查詢comment回復一樣。

暫時更新這么多,以后如果有新的發現,會不定期更新此文。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM