V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
wangweifeng
V2EX  ›  Node.js

mongoose 的关联操作

  •  
  •   wangweifeng · 2017-09-20 11:19:46 +08:00 · 3015 次点击
    这是一个创建于 2654 天前的主题,其中的信息可能已经有所发展或是发生改变。

    mongoose-ref

    实现代码 github 最近在做一个项目涉及到 mongoose 的关联查询等等,之前做的 mysql,postgresql 比较多,而 mongoose 用的都是比较简单的存储数据,简单查询等等。 刚开始涉及 ref 还是有点小晕的,查询了相关资源,也可以模模糊糊做出来,但是会有各种报错。痛下决心研究透彻点,晚上熬夜到 2 点终于有点小眉目了。

    • node v8.5.0
    • mongodb
    • 结合一个接口理解
    • 结合 promise-async-await
    • 一般采用 mvc 模式,本文就直接在 express 里直接写了

    1. 首先建立了一个 mongoose-ref 项目

    直接使用了 express -e mongoose-ref

    2.在 routes/index 里连接 mongodb 数据库

    const mongoose = require('mongoose');
    mongoose.connect('mongodb://localhost:27017/ref');
    

    3.建立 4 个模型,用户:User,城市:City,省份:State,国家:Country

    同时建立关联 user->city->state->country

      const Schema = mongoose.Schema;
      const ObjectId = Schema.Types.ObjectId;
      const UserSchema = new Schema({
        username: { type: String },
        userpwd: { type: String },
        userage: { type: Number },
        city: { type: Schema.Types.ObjectId, ref: 'City' },
      });
      const CitySchema = new Schema({
        name: { type: String },
        state: { type: Schema.Types.ObjectId, ref: 'State' }
      });
      const StateSchema = new Schema({
        name: { type: String },
        country: { type: Schema.Types.ObjectId, ref: 'Country' }
      });
      const CountrySchema = new Schema({
        name: { type: String }
      });
      const User = mongoose.model('User', UserSchema);
      const City = mongoose.model('City', CitySchema);
      const State = mongoose.model('State', StateSchema);
      const Country = mongoose.model('Country', CountrySchema);
    

    4.主要采用 promise-async-async 进行逻辑处理

    首先创建一个 user_getCountryList 函数,如下代码

      const user_getCountryList = async function (req, res) {
        console.log("/v1/ref start -->" + JSON.stringify(req.body));
        try {
          const respondData = {
            status: res.statusCode,
            data: {},
            error: {}
          };
          const username = req.body.username;
          const userpwd = req.body.userpwd;
          const userage = req.body.userage;
          const usercityname = req.body.usercityname;
          const userstatename = req.body.userstatename;
          const usercountryname = req.body.usercountryname;
          const userInfoCountry = await findUserCountry({ name: usercountryname }, usercountryname);//查看国家
          const userInfoState = await findUserState({ name: userstatename }, userstatename);//查看州
          const userInfoCity = await findUserCity({ name: usercityname }, usercityname);//查看城市
          const userInfo = await findUser({ username: username, }, username,userpwd,userage);//查看用户信息
          const updateInfoUser = await updateUser({ _id: userInfo },userInfoCity);//更新用户信息
          const updateInfoCity = await updateCity({ _id: userInfoCity }, userInfoState);//更新城市信息
          const updateInfoState = await updateState({ _id: userInfoState }, userInfoCountry);//更新州信息
          return res.json(respondData);
        }
        catch (error) {
          //错误处理
          console.log("userCity error -->" + JSON.stringify(error));
          respondData.error = error;
          return res.json(respondData);
        }
      }
    

    首先查看传入的国家在 country 中有没有,加入有,返回_id,没有就创建传入的国家名,并返回_id,查看 findUserCountry 函数对应的逻辑

      const findUserCountry = async function (cnd, country) {
        console.log("findUserCountry start --> " + JSON.stringify(cnd));
        return new Promise(function (resolve, reject) {
          Country.findOne(cnd, function (error, data) {
            console.log("findUserCountry findOne  data --> " + JSON.stringify(data));
            if (error) {
              return reject(error);
            }
            if (data) {
              return resolve(data._id);
            } else {
              const userCountry = new Country({
                name: country
              });
              userCountry.save(function (err, data) {
                if (err) {
                  console.log("userCountry.save err-->" + JSON.stringify(err));
                  return reject(err);
                }
                console.log("userCountry-->" + JSON.stringify(data));
                return resolve(data._id);
              });
            }
          });
        })
      }
    

    同理传入的州,城市,用户信息以同样的方式返回_id

    接下来就要进行关联 user->city->state->country

    通俗的说就是在 User 表中 city 保存 City 表中所需要的_id;也就是之前返回的_id 这时就可以用到,可以参考 updateUser 函数

       const updateUser = async function (cnd, cityid) {
        console.log("updateUser start --> " + JSON.stringify(cnd));
        return new Promise(function (resolve, reject) {
          User.update(cnd, { $set: { city: cityid } }, function (error, data) {
            console.log("updateUser findOne  data --> " + JSON.stringify(data));
            if (error) {
              return reject(error);
            }
            return resolve(data);
          });
        })
      }
    

    可以使用 postman 模拟数据,如图: Postman 模拟接口请求 这时就把 City 对应的_id 写进了 User 表中,可以查看表,如图: User 表中数据 City 表中数据 同理 user->city->state->country 数据都可以写进不同的表中。

    5.使用 populate 关联查询

    当传入 username 时,使用 populate 关联查询,可以查询出这个人的所以信息

     User.find({ username: user_name })
        .populate('city')
        .exec(function (err, docs) {
          City.find({ _id: docs[0].city._id })
            .populate('state')
            .exec(function (err, doc) {
              State.find({ _id: doc[0].state._id })
                .populate('country')
                .exec(function (err, result) {
                  const userInfo = {};
                  userInfo.username = docs[0].username;
                  userInfo.userpwd = docs[0].userpwd;
                  userInfo.userage = docs[0].userage;
                  userInfo.usercity = doc[0].name;
                  userInfo.userstate = result[0].name;
                  userInfo.usercountry = result[0].country.name;
                  respondData.data.push(userInfo);
                  return res.json(respondData);
                })
            })
        });
    

    使用 postman 模拟接口如下 Postman 模拟接口关联查询

    当然这个关联查询也可以使用 promise-async-await 不过有时候看着这回调,层层包含还挺好看,或者这也是 js 的一大美感呢

    3 条回复    2017-09-22 12:44:53 +08:00
    wangweifeng
        1
    wangweifeng  
    OP
       2017-09-20 11:22:20 +08:00
    为什么 Markdown 在这里看起来那么怪呢
    qiuyk
        2
    qiuyk  
       2017-09-22 11:05:49 +08:00
    我记得 mongoose 如果 exec 不传 cb 就返回 promise 的吧 既然都用上了 async/await 了 就彻底一点吧
    wangweifeng
        3
    wangweifeng  
    OP
       2017-09-22 12:44:53 +08:00
    @qiuyk 可以彻底的,最后一句话说了,层层包含还挺好看,或者这也是 js 的一大美感呢,哈哈
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5357 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 09:11 · PVG 17:11 · LAX 01:11 · JFK 04:11
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.