使用 Mongoose 查找或创建

Find one or create with Mongoose

我有

Page.findById(pageId).then(page => {
  const pageId = page.id;
   ..
});

我的问题是,如果没有给出页面 ID,它应该在给定某些条件的情况下只获取第一个可用页面,这是由

完成的
Page.findOne({}).then(page => {
  const pageId = page.id;
  ..
});

但是如果没有找到页面,它应该创建一个新页面并使用它,这是通过

完成的
Page.create({}).then(page => {
  const pageId = page.id;
  ..
});

但是我如何将所有这些合并到尽可能少的行中呢?

我内心有很多逻辑

page => { ... }

所以我非常想聪明地做这件事,这样我就可以避免这样做

if (pageId) {
  Page.findById(pageId).then(page => {
    const pageId = page.id;
     ..
  });
} else {
  Page.findOne({}).then(page => {
    if (page) {
      const pageId = page.id;
      ..
    } else {
      Page.create({}).then(page => {
        const pageId = page.id;
        ..
      });
    }
  });
}

我在想我也许可以用类似

的东西为模式分配一个静态
pageSchema.statics.findOneOrCreate = function (condition, doc, callback) {
  const self = this;
  self.findOne(condition).then(callback).catch((err, result) => {
    self.create(doc).then(callback);
  });
};

每个模式都可以为其模型定义实例和static methods。静态与方法几乎相同,但允许定义直接存在于模型中的函数

静态方法findOneOrCreate:

pageSchema.statics.findOneOrCreate = function findOneOrCreate(condition, doc, callback) {
  const self = this;
  self.findOne(condition, (err, result) => {
    return result 
      ? callback(err, result)
      : self.create(doc, (err, result) => {
        return callback(err, result);
      });
  });
};

现在,当您拥有 Page 的实例时,您可以调用 findOneOrCreate:

Page.findOneOrCreate({id: 'somePageId'}, (err, page) => {
  console.log(page);
});

如果您不想向模型添加静态方法,您可以尝试移动一些东西,至少不要让所有这些回调嵌套级别:

function getPageById (callback) {
  Page.findById(pageId).then(page => {
    return callback(null, page);
  });
}

function getFirstPage(callback) {
  Page.findOne({}).then(page => {
    if (page) {
      return callback(null, page);
    }

    return callback();
  });
}

let retrievePage = getFirstPage;
if (pageId) {
  retrievePage = getPageById;
}

retrievePage(function (err, page) {
  if (err) {
    // @todo: handle the error
  }

  if (page && page.id) {
    pageId = page.id;
  } else {
    Page.create({}).then(page => {
      pageId = page.id;
    });
  }
});

试试这个..

 var myfunc = function (pageId) {
  // check for pageId passed or not
 var newId = (typeof pageId == 'undefined') ? {} : {_id:pageId};

 Page.findOne(pageId).then(page => {
 if (page)
 const pageId = page.id;
 else {  // if record not found, create new

    Page.create({}).then(page => {
        const pageId = page.id;
    });
  }
});

 }

Yosvel Quintero 的 答案相关但对我不起作用:

pageSchema.statics.findOneOrCreate = function findOneOrCreate(condition, callback) {
    const self = this
    self.findOne(condition, (err, result) => {
        return result ? callback(err, result) : self.create(condition, (err, result) => { return callback(err, result) })
    })
}

然后像这样使用它:

Page.findOneOrCreate({ key: 'value' }, (err, page) => {
    // ... code
    console.log(page)
})

承诺async/await版本。

Page.static('findOneOrCreate', async function findOneOrCreate(condition, doc) {
  const one = await this.findOne(condition);

  return one || this.create(doc);
});

用法

Page.findOneOrCreate({ id: page.id }, page).then(...).catch(...)

或者

async () => {
  const yourPage = await Page.findOneOrCreate({  id: page.id }, page);
}

根据猫鼬 docs:

根据previous SO answer

Model.findByIdAndUpdate()

"Finds a matching document, updates it according to the update arg, passing any options, and returns the found document (if any) to the callback."

在选项中将 upsert 设置为 true:

upsert: bool - 如果对象不存在则创建它。默认为假。

Model.findByIdAndUpdate(id, { $set: { name: 'SOME_VALUE' }}, { upsert: true  }, callback)

async/await 的一行解决方案:

const page = Page.findOne({}).then(p => p || p.create({})

此处发布的解决方案忽略了当字段或字段组合上有唯一索引时,这种模式最常见。此解决方案正确考虑唯一索引违规错误:

mongoose.plugin((schema) => {
  schema.statics.findOrCreate = async function findOrCreate(attrs) {
    try {
      return await this.create(attrs);
    } catch (error) {
      const isDuplicate = error.code === 11000 && error.keyPattern;
      if (isDuplicate) {
        const doc = await this.findOne(error.keyValue);
        doc.set(attrs);
        return await doc.save();
      }
      throw error;
    }
  };
});