Bagaimana cara memperbarui / memasang dokumen dalam bahasa Mongoose?


368

Mungkin ini saatnya, mungkin ini saya tenggelam dalam dokumentasi yang jarang dan tidak dapat membungkus kepala saya di sekitar konsep memperbarui dalam Mongoose :)

Inilah kesepakatannya:

Saya memiliki skema dan model kontak (properti yang diperpendek):

var mongoose = require('mongoose'),
    Schema = mongoose.Schema;

var mongooseTypes = require("mongoose-types"),
    useTimestamps = mongooseTypes.useTimestamps;


var ContactSchema = new Schema({
    phone: {
        type: String,
        index: {
            unique: true,
            dropDups: true
        }
    },
    status: {
        type: String,
        lowercase: true,
        trim: true,
        default: 'on'
    }
});
ContactSchema.plugin(useTimestamps);
var Contact = mongoose.model('Contact', ContactSchema);

Saya menerima permintaan dari klien, berisi bidang yang saya butuhkan dan menggunakan model saya sebagai berikut:

mongoose.connect(connectionString);
var contact = new Contact({
    phone: request.phone,
    status: request.status
});

Dan sekarang kita mencapai masalah:

  1. Jika saya menelepon contact.save(function(err){...})saya akan menerima kesalahan jika kontak dengan nomor telepon yang sama sudah ada (seperti yang diharapkan - unik)
  2. Saya tidak dapat memanggil update()kontak, karena metode itu tidak ada pada dokumen
  3. Jika saya memanggil pembaruan pada model:
    Contact.update({phone:request.phone}, contact, {upsert: true}, function(err{...})
    Saya masuk ke loop tak terbatas dari beberapa jenis, karena implementasi pembaruan Mongoose jelas tidak ingin objek sebagai parameter kedua.
  4. Jika saya melakukan hal yang sama, tetapi pada parameter kedua saya melewati array asosiatif dari properti permintaan {status: request.status, phone: request.phone ...}itu berfungsi - tetapi kemudian saya tidak memiliki referensi ke kontak tertentu dan tidak dapat menemukan createdAtdan updatedAtsifat - sifatnya.

Jadi intinya, setelah semua saya mencoba: diberi dokumen contact, bagaimana cara memperbaruinya jika ada, atau menambahkannya jika tidak?

Terima kasih atas waktunya.


Bagaimana dengan mengaitkan preuntuk save?
Shamoon

Jawaban:


428

Mongoose sekarang mendukung ini secara native dengan findOneAndUpdate (panggilan MongoDB findAndModify ).

Opsi upsert = true membuat objek jika tidak ada. default menjadi false .

var query = {'username': req.user.username};
req.newData.username = req.user.username;

MyModel.findOneAndUpdate(query, req.newData, {upsert: true}, function(err, doc) {
    if (err) return res.send(500, {error: err});
    return res.send('Succesfully saved.');
});

Dalam versi yang lebih lama, luwak tidak mendukung kaitan ini dengan metode ini:

  • default
  • setter
  • validator
  • middleware

17
Ini harus menjadi jawaban terbaru. Sebagian besar menggunakan dua panggilan atau (saya percaya) kembali ke driver mongodb asli.
huggie

10
masalah dengan findOneAndUpdate adalah pra-simpan tidak akan dieksekusi.
a77icu5

2
Kedengarannya seperti bug di Mongoose atau MongoDB?
Pascalius

8
Dari dokumen: "... saat menggunakan findAndModify helpers, yang berikut ini tidak diterapkan: default, setter, validator, middleware" mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate
kellen

2
@JamieHutber Ini tidak disetel secara default, ini adalah properti khusus
Pascalius

194

Saya baru saja membakar 3 jam yang solid mencoba memecahkan masalah yang sama. Secara khusus, saya ingin "mengganti" seluruh dokumen jika ada, atau menyisipkannya sebaliknya. Inilah solusinya:

var contact = new Contact({
  phone: request.phone,
  status: request.status
});

// Convert the Model instance to a simple object using Model's 'toObject' function
// to prevent weirdness like infinite looping...
var upsertData = contact.toObject();

// Delete the _id property, otherwise Mongo will return a "Mod on _id not allowed" error
delete upsertData._id;

// Do the upsert, which works like this: If no Contact document exists with 
// _id = contact.id, then create a new doc using upsertData.
// Otherwise, update the existing doc with upsertData
Contact.update({_id: contact.id}, upsertData, {upsert: true}, function(err{...});

Saya membuat masalah pada halaman proyek Mongoose meminta info tentang ini ditambahkan ke dokumen.


1
Dokumentasi tampaknya buruk saat ini. Ada beberapa di dokumen API (cari "perbarui" di halaman. Sepertinya ini: MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn);danMyModel.update({ name: 'Tobi' }, { ferret: true }, { multi: true }, fn);
CpILL

untuk dokumen kasus tidak ditemukan, _id mana yang digunakan? Mongoose menghasilkannya atau yang dipertanyakan?
Haider

91

Anda dekat dengan

Contact.update({phone:request.phone}, contact, {upsert: true}, function(err){...})

tetapi parameter kedua Anda harus berupa objek dengan operator modifikasi misalnya

Contact.update({phone:request.phone}, {$set: { phone: request.phone }}, {upsert: true}, function(err){...})

15
Saya tidak berpikir Anda memerlukan {$set: ... }bagian di sini sebagai bentuk otomatis bacaan saya
CpILL

5
Ya, luwak mengatakan itu mengubah segalanya menjadi $ set
grantwparks

1
Ini berlaku pada saat penulisan ini, saya tidak menggunakan MongoDB lagi jadi saya tidak dapat berbicara dengan perubahan dalam beberapa bulan terakhir: D
chrixian

5
Tidak menggunakan $ set mungkin merupakan kebiasaan buruk untuk masuk jika Anda akan menggunakan driver asli dari waktu ke waktu.
UpTheCreek

Anda dapat menggunakan $ set dan $ setOnInsert untuk menetapkan hanya bidang tertentu dalam kasus penyisipan
justin.m.chase

73

Yah, saya menunggu cukup lama dan tidak ada jawaban. Akhirnya menyerah seluruh pendekatan pembaruan / upsert dan pergi dengan:

ContactSchema.findOne({phone: request.phone}, function(err, contact) {
    if(!err) {
        if(!contact) {
            contact = new ContactSchema();
            contact.phone = request.phone;
        }
        contact.status = request.status;
        contact.save(function(err) {
            if(!err) {
                console.log("contact " + contact.phone + " created at " + contact.createdAt + " updated at " + contact.updatedAt);
            }
            else {
                console.log("Error: could not save contact " + contact.phone);
            }
        });
    }
});

Apakah itu bekerja? Ya. Apakah saya senang dengan ini? Mungkin tidak. 2 panggilan DB, bukan satu.
Semoga implementasi Mongoose di masa depan akan muncul dengan Model.upsertfungsi.


2
Contoh-contoh ini menggunakan antarmuka yang ditambahkan dalam MongoDB 2.2 untuk menentukan opsi multi dan upsert dalam bentuk dokumen. .. include :: /includes/fact-upsert-multi-options.rst Dokumentasi ini menyatakan ini, tidak tahu harus ke mana dari sini.
Donald Derek

1
Meskipun ini harus bekerja, Anda sekarang menjalankan 2 operasi (temukan, perbarui) ketika hanya 1 (upert) yang diperlukan. @chrixian menunjukkan cara yang benar untuk melakukan ini.
respectTheCode

12
Perlu dicatat bahwa ini adalah satu-satunya jawaban yang memungkinkan validator Mongoose masuk. Sesuai dengan dokumen , validasi tidak terjadi jika Anda memanggil pembaruan.
Tom Spencer

@fiznool sepertinya Anda dapat memasukkan opsi secara manual runValidators: truesaat pembaruan: perbarui dokumen (namun, pembaruan validator hanya berjalan di $setdan $unsetoperasi)
Danny

Lihat jawaban saya berdasarkan yang ini jika Anda perlu .upsert()tersedia di semua model. stackoverflow.com/a/50208331/1586406
spondbob

24

Solusi yang sangat elegan yang dapat Anda capai dengan menggunakan rantai Janji:

app.put('url', (req, res) => {

    const modelId = req.body.model_id;
    const newName = req.body.name;

    MyModel.findById(modelId).then((model) => {
        return Object.assign(model, {name: newName});
    }).then((model) => {
        return model.save();
    }).then((updatedModel) => {
        res.json({
            msg: 'model updated',
            updatedModel
        });
    }).catch((err) => {
        res.send(err);
    });
});

Mengapa ini tidak dibenarkan? Tampak seperti solusi hebat dan sangat elegan
MadOgre

Solusi brilian, sebenarnya membuat saya memikirkan kembali bagaimana saya mendekati janji.
lux

4
Bahkan lebih elegan adalah menulis ulang (model) => { return model.save(); }sebagai model => model.save(), dan juga (err) => { res.send(err); }sebagai err => res.send(err);)
Jeremy Thille

1
Di mana saya dapat memperoleh informasi lebih lanjut?
V0LT3RR4

17

Saya adalah penjaga Mongoose. Cara yang lebih modern untuk menggunakan doc adalah dengan menggunakan Model.updateOne()fungsinya .

await Contact.updateOne({
    phone: request.phone
}, { status: request.status }, { upsert: true });

Jika Anda membutuhkan dokumen yang dipasang, Anda dapat menggunakannya Model.findOneAndUpdate()

const doc = await Contact.findOneAndUpdate({
    phone: request.phone
}, { status: request.status }, { upsert: true });

Kuncinya adalah Anda perlu meletakkan properti unik di filterparameter ke updateOne()atau findOneAndUpdate(), dan properti lainnya di updateparameter.

Berikut ini adalah tutorial tentang pemasangan dokumen dengan luwak .


15

Saya membuat akun StackOverflow HANYA untuk menjawab pertanyaan ini. Setelah sia-sia mencari jalinan, saya sendiri menulis sesuatu. Ini adalah bagaimana saya melakukannya sehingga dapat diterapkan pada model luwak manapun. Baik impor fungsi ini atau tambahkan langsung ke kode Anda di mana Anda melakukan pembaruan.

function upsertObject (src, dest) {

  function recursiveFunc (src, dest) {
    _.forOwn(src, function (value, key) {
      if(_.isObject(value) && _.keys(value).length !== 0) {
        dest[key] = dest[key] || {};
        recursiveFunc(src[key], dest[key])
      } else if (_.isArray(src) && !_.isObject(src[key])) {
          dest.set(key, value);
      } else {
        dest[key] = value;
      }
    });
  }

  recursiveFunc(src, dest);

  return dest;
}

Kemudian untuk memberikan dokumen luwak lakukan hal berikut,

YourModel.upsert = function (id, newData, callBack) {
  this.findById(id, function (err, oldData) {
    if(err) {
      callBack(err);
    } else {
      upsertObject(newData, oldData).save(callBack);
    }
  });
};

Solusi ini mungkin memerlukan 2 panggilan DB namun Anda mendapatkan manfaat dari,

  • Validasi skema terhadap model Anda karena Anda menggunakan .save ()
  • Anda dapat memasang objek yang sangat bersarang tanpa enumerasi manual dalam panggilan pembaruan Anda, jadi jika model Anda berubah, Anda tidak perlu khawatir untuk memperbarui kode Anda

Ingatlah bahwa objek tujuan akan selalu menimpa sumber bahkan jika sumber memiliki nilai yang ada

Juga, untuk array, jika objek yang ada memiliki array yang lebih panjang daripada yang menggantikannya maka nilai-nilai di akhir array lama akan tetap. Cara mudah untuk mematikan seluruh array adalah dengan mengatur array lama menjadi array kosong sebelum upsert jika itu yang ingin Anda lakukan.

UPDATE - 01/16/2016 Saya menambahkan kondisi tambahan karena jika ada array nilai primitif, Mongoose tidak menyadari array menjadi diperbarui tanpa menggunakan fungsi "set".


2
+1 untuk membuat acc hanya untuk ini: P Berharap saya bisa memberi +1 lainnya hanya untuk menggunakan .save (), karena findOneAndUpate () membuat kita tidak dapat menggunakan validator dan hal-hal pra, posting, dll. Terima kasih, saya akan periksa ini juga
user1576978

Maaf, tetapi tidak berhasil di sini :( Saya mendapat ukuran tumpukan panggilan terlampaui
user1576978

Versi Lodash apa yang Anda gunakan? Saya menggunakan lodash versi 2.4.1 Terima kasih!
Aaron Mast

Juga, seberapa kompleks objek yang Anda tegaskan? Jika terlalu besar proses node mungkin tidak dapat menangani jumlah panggilan rekursif yang diperlukan untuk menggabungkan objek.
Aaron Mast

Saya menggunakan ini, tetapi harus menambahkan if(_.isObject(value) && _.keys(value).length !== 0) {pada kondisi penjaga untuk menghentikan stack overflow. Lodash 4+ di sini, tampaknya mengubah nilai non objek menjadi objek dalam keyspanggilan sehingga pelindung rekursif selalu benar. Mungkin ada cara yang lebih baik, tetapi sekarang hampir berfungsi untuk saya ...
Richard G

12

Saya perlu memperbarui / memasang dokumen ke dalam satu koleksi, yang saya lakukan adalah membuat objek literal baru seperti ini:

notificationObject = {
    user_id: user.user_id,
    feed: {
        feed_id: feed.feed_id,
        channel_id: feed.channel_id,
        feed_title: ''
    }
};

terdiri dari data yang saya dapatkan dari tempat lain di basis data saya dan kemudian panggil pembaruan pada Model

Notification.update(notificationObject, notificationObject, {upsert: true}, function(err, num, n){
    if(err){
        throw err;
    }
    console.log(num, n);
});

inilah ouput yang saya dapatkan setelah menjalankan skrip untuk pertama kalinya:

1 { updatedExisting: false,
    upserted: 5289267a861b659b6a00c638,
    n: 1,
    connectionId: 11,
    err: null,
    ok: 1 }

Dan ini adalah output ketika saya menjalankan skrip untuk kedua kalinya:

1 { updatedExisting: true, n: 1, connectionId: 18, err: null, ok: 1 }

Saya menggunakan luwak versi 3.6.16


10
app.put('url', function(req, res) {

        // use our bear model to find the bear we want
        Bear.findById(req.params.bear_id, function(err, bear) {

            if (err)
                res.send(err);

            bear.name = req.body.name;  // update the bears info

            // save the bear
            bear.save(function(err) {
                if (err)
                    res.send(err);

                res.json({ message: 'Bear updated!' });
            });

        });
    });

Berikut ini adalah pendekatan yang lebih baik untuk menyelesaikan metode pembaruan di luwak, Anda dapat memeriksa Scotch.io untuk lebih jelasnya. Ini pasti bekerja untuk saya !!!


5
Adalah kesalahan untuk berpikir ini melakukan hal yang sama dengan pembaruan MongoDB. Itu bukan atom.
Valentin Waeselynck

1
Saya ingin mencadangkan jawaban @ValentinWaeselynck. Kode Scotch bersih - tetapi Anda mengambil dokumen dan kemudian memperbarui. Di tengah proses itu dokumen bisa diubah.
Nick Pineda

8

Ada bug yang diperkenalkan di 2.6, dan berdampak ke 2.7 juga

Upert yang digunakan untuk bekerja dengan benar pada 2.4

https://groups.google.com/forum/#!topic/mongodb-user/UcKvx4p4hnY https://jira.mongodb.org/browse/SERVER-13843

Coba lihat, ini berisi beberapa info penting

DIPERBARUI:

Itu tidak berarti upert tidak berfungsi. Berikut ini adalah contoh yang bagus tentang bagaimana menggunakannya:

User.findByIdAndUpdate(userId, {online: true, $setOnInsert: {username: username, friends: []}}, {upsert: true})
    .populate('friends')
    .exec(function (err, user) {
        if (err) throw err;
        console.log(user);

        // Emit load event

        socket.emit('load', user);
    });

7

Anda cukup memperbarui catatan dengan ini dan mendapatkan data yang diperbarui sebagai tanggapan

router.patch('/:id', (req, res, next) => {
    const id = req.params.id;
    Product.findByIdAndUpdate(id, req.body, {
            new: true
        },
        function(err, model) {
            if (!err) {
                res.status(201).json({
                    data: model
                });
            } else {
                res.status(500).json({
                    message: "not found any relative data"
                })
            }
        });
});

6

ini bekerja untuk saya.

app.put('/student/:id', (req, res) => {
    Student.findByIdAndUpdate(req.params.id, req.body, (err, user) => {
        if (err) {
            return res
                .status(500)
                .send({error: "unsuccessful"})
        };
        res.send({success: "success"});
    });

});


Terima kasih. Inilah yang akhirnya bekerja untuk saya!
Luis Febro

4

Inilah cara paling sederhana untuk membuat / memperbarui sambil juga memanggil middleware dan validator.

Contact.findOne({ phone: request.phone }, (err, doc) => {
    const contact = (doc) ? doc.set(request) : new Contact(request);

    contact.save((saveErr, savedContact) => {
        if (saveErr) throw saveErr;
        console.log(savedContact);
    });
})

3

Bagi siapa pun yang tiba di sini masih mencari solusi yang baik untuk "bersemangat" dengan dukungan kait, inilah yang telah saya uji dan bekerja. Masih membutuhkan 2 panggilan DB tetapi jauh lebih stabil daripada apa pun yang saya coba dalam satu panggilan.

// Create or update a Person by unique email.
// @param person - a new or existing Person
function savePerson(person, done) {
  var fieldsToUpdate = ['name', 'phone', 'address'];

  Person.findOne({
    email: person.email
  }, function(err, toUpdate) {
    if (err) {
      done(err);
    }

    if (toUpdate) {
      // Mongoose object have extra properties, we can either omit those props
      // or specify which ones we want to update.  I chose to update the ones I know exist
      // to avoid breaking things if Mongoose objects change in the future.
      _.merge(toUpdate, _.pick(person, fieldsToUpdate));
    } else {      
      toUpdate = person;
    }

    toUpdate.save(function(err, updated, numberAffected) {
      if (err) {
        done(err);
      }

      done(null, updated, numberAffected);
    });
  });
}

3

Jika generator tersedia, ini menjadi lebih mudah:

var query = {'username':this.req.user.username};
this.req.newData.username = this.req.user.username;
this.body = yield MyModel.findOneAndUpdate(query, this.req.newData).exec();

3

Tidak ada solusi lain yang bekerja untuk saya. Saya menggunakan permintaan pos dan memperbarui data jika ada yang memasukkannya, _id juga dikirim dengan badan permintaan yang perlu dihapus.

router.post('/user/createOrUpdate', function(req,res){
    var request_data = req.body;
    var userModel = new User(request_data);
    var upsertData = userModel.toObject();
    delete upsertData._id;

    var currentUserId;
    if (request_data._id || request_data._id !== '') {
        currentUserId = new mongoose.mongo.ObjectId(request_data._id);
    } else {
        currentUserId = new mongoose.mongo.ObjectId();
    }

    User.update({_id: currentUserId}, upsertData, {upsert: true},
        function (err) {
            if (err) throw err;
        }
    );
    res.redirect('/home');

});

2
//Here is my code to it... work like ninj

router.param('contractor', function(req, res, next, id) {
  var query = Contractors.findById(id);

  query.exec(function (err, contractor){
    if (err) { return next(err); }
    if (!contractor) { return next(new Error("can't find contractor")); }

    req.contractor = contractor;
    return next();
  });
});

router.get('/contractors/:contractor/save', function(req, res, next) {

    contractor = req.contractor ;
    contractor.update({'_id':contractor._id},{upsert: true},function(err,contractor){
       if(err){ 
            res.json(err);
            return next(); 
            }
    return res.json(contractor); 
  });
});


--

2
User.findByIdAndUpdate(req.param('userId'), req.body, (err, user) => {
    if(err) return res.json(err);

    res.json({ success: true });
});

Meskipun potongan kode ini dapat menyelesaikan masalah, itu tidak menjelaskan mengapa atau bagaimana ia menjawab pertanyaan. Harap sertakan penjelasan untuk kode Anda , karena itu sangat membantu untuk meningkatkan kualitas posting Anda. Ingatlah bahwa Anda menjawab pertanyaan untuk pembaca di masa depan, dan orang-orang itu mungkin tidak tahu alasan untuk saran kode Anda. Penanda / pengulas: Untuk jawaban hanya kode seperti ini, downvote, jangan hapus!
Patrick

2

Mengikuti jawaban Traveling Tech Guy , yang sudah luar biasa, kita dapat membuat plugin dan melampirkannya ke luwak setelah kita menginisialisasi sehingga .upsert()akan tersedia di semua model.

plugins.js

export default (schema, options) => {
  schema.statics.upsert = async function(query, data) {
    let record = await this.findOne(query)
    if (!record) {
      record = new this(data)
    } else {
      Object.keys(data).forEach(k => {
        record[k] = data[k]
      })
    }
    return await record.save()
  }
}

db.js

import mongoose from 'mongoose'

import Plugins from './plugins'

mongoose.connect({ ... })
mongoose.plugin(Plugins)

export default mongoose

Maka Anda dapat melakukan sesuatu seperti User.upsert({ _id: 1 }, { foo: 'bar' })atau YouModel.upsert({ bar: 'foo' }, { value: 1 })kapan pun Anda mau.


2

Saya baru saja kembali ke masalah ini setelah beberapa saat, dan memutuskan untuk menerbitkan plugin berdasarkan jawaban oleh Aaron Mast.

https://www.npmjs.com/package/mongoose-recursive-upsert

Gunakan itu sebagai plugin luwak. Ini menetapkan metode statis yang secara rekursif akan menggabungkan objek yang dilewatkan.

Model.upsert({unique: 'value'}, updateObject});

0

Script coffeescript ini berfungsi untuk saya dengan Node - caranya adalah agar get _id dicopot dari pembungkus ObjectID-nya ketika dikirim dan dikembalikan dari klien sehingga ini perlu diganti untuk pembaruan (ketika tidak ada _id yang disediakan, simpan akan kembali untuk menyisipkan dan menambahkan satu).

app.post '/new', (req, res) ->
    # post data becomes .query
    data = req.query
    coll = db.collection 'restos'
    data._id = ObjectID(data._id) if data._id

    coll.save data, {safe:true}, (err, result) ->
        console.log("error: "+err) if err
        return res.send 500, err if err

        console.log(result)
        return res.send 200, JSON.stringify result

0

untuk membangun apa yang diposting Martin Kuzdowicz di atas. Saya menggunakan berikut ini untuk melakukan pembaruan menggunakan luwak dan penggabungan objek json. Bersamaan dengan fungsi model.save () di luwak ini memungkinkan luwak untuk melakukan validasi penuh bahkan yang bergantung pada nilai-nilai lain di json. itu memang membutuhkan paket deepmerge https://www.npmjs.com/package/deepmerge . Tapi itu paket yang sangat ringan.

var merge = require('deepmerge');

app.put('url', (req, res) => {

    const modelId = req.body.model_id;

    MyModel.findById(modelId).then((model) => {
        return Object.assign(model, merge(model.toObject(), req.body));
    }).then((model) => {
        return model.save();
    }).then((updatedModel) => {
        res.json({
            msg: 'model updated',
            updatedModel
        });
    }).catch((err) => {
        res.send(err);
    });
});

1
Saya akan memperingatkan untuk tidak menggunakan req.bodyapa adanya, sebelum menguji injeksi NoSQL (lihat owasp.org/index.php/Testing_for_NoSQL_injection ).
Traveling Tech Guy

1
@TravelingTechGuy Terima kasih atas perhatiannya. Saya masih baru di Node dan Mongoose. Tidakkah model luwak saya dengan validator cukup untuk menangkap upaya injeksi? selama model.save ()
Chris Deleo

-5

Setelah membaca posting di atas, saya memutuskan untuk menggunakan kode ini:

    itemModel.findOne({'pid':obj.pid},function(e,r){
        if(r!=null)
        {
             itemModel.update({'pid':obj.pid},obj,{upsert:true},cb);
        }
        else
        {
            var item=new itemModel(obj);
            item.save(cb);
        }
    });

jika r adalah nol, kami membuat item baru. Kalau tidak, gunakan upsert dalam pembaruan karena pembaruan tidak membuat item baru.


Jika ini adalah dua panggilan ke Mongo, itu tidak terlalu menakutkan bukan?
huggie
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.