2016-01-10 4 views
1

Я использую поддокументы в моем проекте MEAN, чтобы обрабатывать заказы и элементы за заказ.MongoDB: как вставить поддоку?

Это мои (упрощенный) схемы:

var itemPerOrderSchema = new mongoose.Schema({ 
    itemId: String, 
    count: Number 
}); 
var OrderSchema = new mongoose.Schema({ 
    customerId: String, 
    date: String, 
    items: [ itemPerOrderSchema ] 
}); 

Для вставки элементов в itemPerOrderSchema массива я в настоящее время сделать:

var orderId = '123'; 
var item = { itemId: 'xyz', itemsCount: 7 }; 
Order.findOne({ id: orderId }, function(err, order) { 
    order.items.push(item); 
    order.save(); 
}); 

Проблема заключается в том, что я, очевидно, хочу один пункт за itemId , и таким образом я получаю много поддокументов на элемент ...
Одним из решений может быть цикл через все order.items, но это, конечно, не оптимально (order.items может мне много ...). Та же проблема может возникнуть при запросе order.items ...

Вопрос: Как вставить элементы в массиве itemPerOrderSchema без перебора всех элементов уже вставленных в порядке?

+0

itemPerOrder Schema имеет только элементы или предметы плюс идентификатор товара? Я использую аналогичный формат, и я напрямую вставляю элементы в массив, если вы хотите код, тогда я могу вставить его здесь. –

+0

У него тоже itemid ('var itemPerOrderSchema = new mongoose.Schema ({itemId: String, ...'). Если вы можете вставить свой код (в ответе, а не в комментарии, конечно), это может помогите, точно ... – MarcoS

+0

'modelname.findOneAndUpdate ({_ id: idname}, {$ addToSet: {items: {$ each: iems}}}, function (err, docs) {}' - Это то, что я делаю –

ответ

2

Если вы можете использовать объект вместо массива для элементов, возможно, вы можете немного изменить свою схему для обновления с одним запросом.

Что-то вроде этого:

{ 
    customerId: 123, 
    items: { 
    xyz: 14, 
    ds2: 7 
    } 
} 

Таким образом, каждый из Itemid является ключевым в объекте, не элемент массива.

let OrderSchema = new mongoose.Schema({ 
    customerId: String, 
    date: String, 
    items: mongoose.Schema.Types.Mixed 
}); 

Тогда обновление вашего заказа очень просто. Допустим, вы хотите добавить 3 из пунктов числа «хуг» клиенту 123.

db.orders.update({ 
    customerId: 123 
}, 
{ 
    $inc: { 
    'items.xyz': 3 
    } 
}, 
{ 
    upsert: true 
}); 

Passing upsert здесь, чтобы создать заказ, даже если клиент не имеет запись.

Недостатки этого:

  • это то, что если вы используете рамки агрегации, это либо невозможно перебрать ваши вопросы, или если у вас есть ограниченный, известный набор itemIds, то очень многословное , Вы можете решить эту проблему с помощью mapReduce, которая может быть немного медленнее, в зависимости от того, сколько из них у вас там, поэтому YMMB.

  • У вас нет чистого массива items на клиенте. Вы можете исправить это с помощью либо клиента, извлекающего эту информацию (простой let items = Object.keys(order.items).map(key => ({ key: order.items[key] })); или с виртуальным полем mongoose или schema.path(), но это, вероятно, еще один вопрос, на который уже ответил.

+0

Это именно то, что я искал ... Я не знал о Schema.Types.Mixed ... Спасибо! Я немного обеспокоен проблемами структуры агрегации, поскольку в настоящее время я использую его для запросов ... Хотя, вероятно, я смогу избежать итерации элементов на сервере, но только на клиенте (я проведу заказ .items.map(), чтобы получить элементы ...). – MarcoS

+0

Это 'Object.keys (order.items) .map();', но это круто. Запросы и агрегация тоже работают, и в зависимости от того, что вы хотите, это можно сделать. Но иногда это может быть немного многословным. – Zlatko

2

Прежде всего, вам, вероятно, необходимо добавить orderId в ваш itemPerOrderSchema, потому что комбинация orderId и itemId сделает запись уникальной.

Предполагая, что OrderId добавляется к itemPerOrderSchema, я хотел бы предложить следующую реализацию:

function addItemToOrder(orderId, newItem, callback) { 
    Order.findOne({ id: orderId }, function(err, order) { 
    if (err) { 
     return callback(err); 
    } 

    ItemPerOrder.findOne({ orderId: orderId, itemId: newItem.itemId }, function(err, existingItem) { 
     if (err) { 
     return callback(err); 
     } 

     if (!existingItem) { 
     // there is no such item for this order yet, adding a new one 
     order.items.push(newItem); 
     order.save(function(err) { 
      return callback(err); 
     }); 
     } 

     // there is already item with itemId for this order, updating itemsCount 
     itemPerOrder.update(
     { id: existingItem.id }, 
     { $inc: { itemsCount: newItem.itemsCount }}, function(err) { 
      return callback(err); 
     } 
    ); 
    }); 
    }); 
} 

addItemToOrder('123', { itemId: ‘1’, itemsCount: 7 }, function(err) { 
    if (err) { 
    console.log("Error", err); 
    } 
    console.log("Item successfully added to order"); 
}); 

Надеется, что это может помочь.

+0

Спасибо! Вероятно, это поможет ... Тем не менее, я действительно надеялся найти способ просто вызвать order.save, после того, как обновил подзапись заказа, просто нажав элемент (или изменив его) на массив javascript ... – MarcoS

+0

Возможно используйте $ inc вместо $ set для обновления 'existingItem' (при условии, что count integer) - если у вас есть параллельные обновления. – Zlatko

+0

Спасибо Златко, я отредактировал свой ответ. –

Смежные вопросы