Mongoose'da bir belgeyi nasıl güncelleyebilir / ekleyebilirim?


367

Belki de zamanı geldi, belki de benim seyrek belgelerde boğuluyorum ve Mongoose'da güncelleme kavramının etrafına dolamıyorum :)

İşte anlaşma:

Bir iletişim şeması ve modeli (kısaltılmış özellikleri) var:

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);

İstemciden, istediğim alanları içeren bir istek alıyorum ve modelimi bu şekilde kullanıyorum:

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

Ve şimdi soruna ulaşıyoruz:

  1. Aradığımda contact.save(function(err){...})aynı telefon numarasına sahip kişi zaten varsa bir hata alırım (beklendiği gibi - benzersiz)
  2. Ben arayamam update()bu yöntem, bir belge üzerinde varolmadığı, temas
  3. Modelde güncelleme çağırırsam:
    Contact.update({phone:request.phone}, contact, {upsert: true}, function(err{...})
    Mongoose güncelleme uygulaması açıkça ikinci parametre olarak bir nesne istemediğinden, bazı türlerden sonsuz bir döngüye girerim.
  4. Ben de aynısını yaparsam, ancak ikinci parametrede {status: request.status, phone: request.phone ...}çalışır özellik istek ilişkisel bir dizi geçmek - ama sonra belirli bir kişiyi referans var ve onun createdAtve updatedAtözelliklerini bulamıyorum .

Sonuç olarak, denediğim her şeyden sonra: bir belge verildiğinde contact, varsa nasıl güncelleyebilir veya yoksa ekleyebilirim?

Zaman ayırdığınız için teşekkürler.


Ne çengel hakkında preiçin save?
Shamoon

Yanıtlar:


427

Mongoose artık bunu findOneAndUpdate ile doğal olarak destekliyor (MongoDB findAndModify'ı çağırıyor ).

Upsert = true seçeneği, nesneyi yoksa oluşturur. varsayılanı false değerindedir .

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.');
});

Eski sürümlerde Mongoose bu kancaları bu yöntemle desteklemez:

  • varsayılan
  • belirleyiciler
  • doğrulayıcılar
  • ara katman

17
Bu güncel cevap olmalıdır. Diğer çoğu iki çağrı kullanır veya (sanırım) yerli mongodb sürücüsüne düşmek.
huggie

10
findOneAndUpdate ile ilgili sorun, ön kaydetmenin yürütülmemesidir.
a77icu5

2
Mongoose veya MongoDB'de böcek gibi mi görünüyor?
Pascalius

8
Dokümanlardan: "... findAndModify yardımcılarını kullanırken aşağıdakiler uygulanmaz: varsayılanlar, ayarlayıcılar, doğrulayıcılar, ara katman yazılımı" mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate
kellen

2
@JamieHutber Bu, varsayılan olarak ayarlanmamıştır, bu özel bir özelliktir
Pascalius

194

Aynı sorunu çözmeye çalışırken 3 saat boyunca yandım. Özellikle, varsa tüm belgeyi "değiştirmek" veya başka türlü eklemek istedim. İşte çözüm:

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{...});

Mongoose proje sayfasında , bununla ilgili bilgilerin dokümanlara eklenmesini isteyen bir sorun oluşturdum .


1
Belgeler şu anda zayıf görünüyor. API dokümanlarında bazı sayfalar var (sayfada "güncelleme" yi arayın. MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn);MyModel.update({ name: 'Tobi' }, { ferret: true }, { multi: true }, fn);
Şuna

vaka belgesi bulunamadığı için hangi _id kullanılır? Firavun faresi onu mı yoksa sorgulananı mı üretir?
Haider

91

Yakındın

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

ancak ikinci parametreniz bir değiştirme operatörüne sahip bir nesne olmalıdır

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

15
Bu {$set: ... }bölüme okumaya otomatik olarak ihtiyaç duyduğumu düşünmüyorum
CpILL

5
Evet, mongoose her şeyi $
set'e

1
Bu, yazıldığı sırada geçerliydi, artık MongoDB kullanmıyorum, bu yüzden son aylardaki değişikliklerle konuşamıyorum: D
chrixian

5
Eğer yerel sürücüyü zaman zaman kullanacaksanız $ set kullanmamak kötü bir alışkanlık olabilir.
UpTheCreek

Bir ekleme durumunda yalnızca belirli alanları ayarlamak için $ set ve $ setOnInsert kullanabilirsiniz
justin.m.chase

73

Yeterince bekledim ve cevap vermedim. Sonunda tüm güncelleme / destek yaklaşımından vazgeçti ve birlikte:

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);
            }
        });
    }
});

Çalışıyor mu? Evet. Bundan memnun muyum? Muhtemelen değil. Bir yerine 2 DB çağrısı.
Umarım gelecekteki bir Mongoose uygulaması bir Model.upsertişlev bulur .


2
Bu örneklerde, bir belge formundaki çoklu ve yukarı seçenekleri belirtmek için MongoDB 2.2'de eklenen arabirim kullanılmıştır. .. include :: /includes/fact-upsert-multi-options.rst Dokümantasyon bunu söylüyor, buradan nereye gideceğinizi bilmiyorum.
Donald Derek

1
Bu çalışmasına rağmen, artık yalnızca 1 (upert) gerektiğinde 2 işlem (bul, güncelle) çalıştırıyorsunuz. @chrixian bunu yapmanın doğru yolunu gösterir.
respectTheCode

12
Mongoose'un onaylayıcılarının devreye girmesine izin veren tek yanıtın bu olduğunu belirtmek gerekir. Dokümanlara göre , güncelleme çağırırsanız doğrulama gerçekleşmez.
Tom Spencer

@fiznool runValidators: true, güncelleme sırasında seçeneği manuel olarak geçirebileceğiniz gibi görünüyor : güncelleme belgeleri (ancak güncelleme doğrulayıcıları yalnızca çalışır $setve $unsetçalışır)
Danny

.upsert()Tüm modellerde bulunmanız gerekiyorsa cevabımı buna göre inceleyin . stackoverflow.com/a/50208331/1586406
spondbob

24

Promises zincirini kullanarak elde edebileceğiniz çok zarif bir çözüm:

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);
    });
});

Bu neden iptal edilmedi? Harika bir çözüm gibi görünüyor ve çok zarif
MadOgre

Parlak bir çözüm, aslında vaatlere nasıl yaklaştığımı yeniden düşünmemi sağladı.
lux

4
Hatta daha şık yeniden yazmak olacaktır (model) => { return model.save(); }olarak model => model.save(), hem de (err) => { res.send(err); }olduğu gibi err => res.send(err);)
Jeremy Thille

1
Daha fazla bilgiyi nereden alabilirim?
V0LT3RR4

17

Ben Mongoose'un koruyucusuyum. Bir dokümanı desteklemenin daha modern yolu Model.updateOne()işlevi kullanmaktır .

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

Eğer yenilenmiş dokümana ihtiyacınız varsa, Model.findOneAndUpdate()

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

Önemli paket, filterparametreye benzersiz özellikleri updateOne()veya parametresine findOneAndUpdate()ve diğer özellikleri parametreye koymanız gerektiğidir update.

İşte Mongoose ile belgeleri alt üst etme hakkında bir eğitim .


15

Bu soruyu cevaplamak için SADECE bir StackOverflow hesabı oluşturdum. İnterwebleri meyvesizce aradıktan sonra kendim bir şeyler yazdım. Ben böyle yaptım, böylece herhangi bir mongoose modele uygulanabilir. Ya bu işlevi içe aktarın ya da güncellemeyi yaptığınız yere doğrudan kodunuza ekleyin.

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;
}

Daha sonra bir tek renkli belgeyi desteklemek için aşağıdakileri yapın,

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

Bu çözüm, 2 DB çağrısı gerektirebilir, ancak

  • .Save () kullandığınız için modelinize göre şema doğrulaması
  • Güncelleme çağrınızda manuel olarak numaralandırılmadan derin yuvalanmış nesneleri yükseltebilirsiniz, böylece modeliniz değişirse kodunuzu güncelleme konusunda endişelenmenize gerek kalmaz

Kaynak mevcut bir değere sahip olsa bile, hedef nesnenin kaynağı her zaman geçersiz kılacağını unutmayın.

Ayrıca, diziler için, varolan nesnenin değiştirilenden daha uzun bir dizisi varsa, eski dizinin sonundaki değerler kalır. Tüm diziyi yükseltmenin kolay bir yolu, eski diziyi, yapmayı planladığınız şeyse, upsert'ten önce boş bir dizi olarak ayarlamaktır.

GÜNCELLEME - 01/16/2016 Bir dizi temel değer varsa, Mongoose dizinin "set" işlevini kullanmadan güncellenmiş olduğunu fark etmediği için fazladan bir koşul ekledim.


2
Sadece bunun için acc oluşturmak için +1: P Keşke sadece .save (), becaunse findOneAndUpate () kullanarak başka bir + 1 verebilir bize doğrulayıcılar ve pre, post, vb şeyler kullanamaz hale getirir. Teşekkürler, ben de kontrol edeceğim
user1576978

Üzgünüz, ama burada işe yaramadı :( Çağrı yığını boyutu aşıldı var
user1576978

Hangi lodash sürümünü kullanıyorsunuz? Lodash 2.4.1 sürümünü kullanıyorum Teşekkürler!
Aaron Mast

Ayrıca, nesneleri ne kadar karmaşık bir şekilde canlandırıyorsunuz? Çok büyükse, düğüm işlemi nesneleri birleştirmek için gereken özyinelemeli çağrıların sayısını işleyemeyebilir.
Aaron Mast

Bunu kullandım, ancak if(_.isObject(value) && _.keys(value).length !== 0) {yığın taşmasını durdurmak için koruma koşulunu eklemek zorunda kaldım . Lodash 4+ burada, nesne olmayan değerleri çağrıdaki nesnelere dönüştürüyor gibi görünüyor, keysböylece özyinelemeli koruma her zaman doğruydu. Belki daha iyi bir yol var, ama neredeyse benim için çalışıyor ...
Richard G

12

Bir belgeyi bir koleksiyona güncellemem / eklemem gerekiyordu, yaptığım şey böyle yeni bir nesne hazır bilgisi oluşturmaktı:

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

veritabanımdaki başka bir yerden aldığım verilerden oluşuyor ve sonra Modelde güncelleme çağırıyorum

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

Bu komut dosyasını ilk kez çalıştırdıktan sonra aldığım çıkış:

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

Ve ben komut dosyasını ikinci kez çalıştırdığımda çıktı:

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

Mongoose sürüm 3.6.16 kullanıyorum


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!' });
            });

        });
    });

Mongoose'taki güncelleme yöntemini çözmek için daha iyi bir yaklaşım, daha fazla bilgi için Scotch.io'yu kontrol edebilirsiniz . Bu kesinlikle benim için çalıştı !!!


5
Bunun MongoDB'nin güncellemesi ile aynı şeyi yaptığını düşünmek yanlıştır. Atomik değil.
Valentin Waeselynck

1
@ValentinWaeselynck cevabını yedeklemek istiyorum. Scotch'ın kodu temiz - ancak bir belge alıyorsunuz ve ardından güncelleniyorsunuz. Bu sürecin ortasında belge değiştirilebilirdi.
Nick Pineda

8

2.6'da tanıtılan bir hata var ve 2.7'yi de etkiliyor

Upsert 2.4 üzerinde düzgün çalışırdı

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

Bir göz atın, bazı önemli bilgiler içeriyor

GÜNCELLENMİŞ:

Bu upsert'in çalışmadığı anlamına gelmez. İşte nasıl kullanılacağına dair güzel bir örnek:

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

Kaydı bununla güncelleyebilir ve güncellenmiş verileri yanıt olarak alabilirsiniz

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

bu benim için çalıştı.

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"});
    });

});


Teşekkürler. Sonunda benim için çalışan bu oldu!
Luis Febro

4

İşte ara katman yazılımı ve doğrulayıcıları çağırırken oluşturmanın / güncellemenin en basit yolu.

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

Buraya gelen herkes için hala kanca desteği ile "upserting" için iyi bir çözüm arıyor, ben test ve çalışan budur. Yine de 2 DB aramaları gerektirir, ancak tek bir aramada denediğim her şeyden çok daha kararlı.

// 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

Jeneratörler mevcutsa daha da kolaylaşır:

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

Benim için başka hiçbir çözüm işe yaramadı. Ben bir gönderme isteği kullanıyorum ve başka eklemek varsa veri güncelleme, ayrıca _id kaldırılması gereken istek gövdesi ile gönderilir.

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 });
});

Bu kod snippet'i sorunu çözebilir, ancak soruyu neden veya nasıl yanıtladığını açıklamaz. Lütfen gönderinizin kalitesini artırmaya gerçekten yardımcı olduğu için kodunuz için bir açıklama ekleyin. Gelecekte okuyucular için soruyu cevapladığınızı ve bu kişilerin kod önerinizin nedenlerini bilmeyebileceğini unutmayın. İşaretçiler / yorumcular: Bunun gibi sadece kod yanıtları için, aşağı oy, silmeyin!
Patrick

2

Zaten harika olan Travelling Tech Guy'ın cevabını takiben , bir eklenti oluşturabilir ve başlattıktan sonra mongoose ekleyebiliriz, böylece .upsert()tüm modellerde kullanılabilir.

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

Sonra istediğiniz zaman User.upsert({ _id: 1 }, { foo: 'bar' })veya YouModel.upsert({ bar: 'foo' }, { value: 1 })istediğiniz gibi bir şey yapabilirsiniz .


2

Bir süre sonra bu konuya tekrar geldim ve Aaron Mast'ın cevabına dayanan bir eklenti yayınlamaya karar verdim.

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

Mongoose eklentisi olarak kullanın. Aktarılan nesneyi özyinelemeli olarak birleştirecek statik bir yöntem ayarlar.

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

0

Bu kahve, Düğüm ile benim için çalışıyor - hile, istemciden gönderilip iade edildiğinde _id get'in ObjectID sarmalayıcısından çıkarılması ve bu nedenle güncellemeler için değiştirilmesi gerekiyor (_id sağlanmadığında, kaydetme eklemek ve eklemek için geri dönecek bir).

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

Martin Kuzdowicz'in yukarıda paylaştığı şeyler üzerine inşa etmek. Ben mongoose ve json nesneleri derin birleştirme kullanarak bir güncelleştirme yapmak için aşağıdakileri kullanın. Mongoose'taki model.save () işleviyle birlikte, bu, mongoose'un json'daki diğer değerlere dayanan bir tane bile tam bir doğrulama yapmasına izin verir. https://www.npmjs.com/package/deepmerge adresindeki deepmerge paketini gerektirir . Ama bu çok hafif bir paket.

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
NoSQL enjeksiyonunureq.body test etmeden önce olduğu gibi kullanmaya dikkat ederim (bkz. Owasp.org/index.php/Testing_for_NoSQL_injection ).
Tech Tech Guy

1
@TravelingTechGuy Dikkat için teşekkürler, hala Düğüm ve Mongoose için yeniyim. Doğrulayıcılara sahip mongoose modelim bir enjeksiyon girişimi yakalamak için yeterli olmaz mı? model.save ()
Chris Deleo

-5

Yukarıdaki yazıları okuduktan sonra bu kodu kullanmaya karar verdim:

    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);
        }
    });

r boşsa, yeni öğe oluştururuz. Aksi takdirde, güncelleme yeni öğe oluşturmadığından güncellemede upsert komutunu kullanın.


Eğer iki Moğol çağrısı varsa, o kadar da sağlam değil mi?
huggie
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.