如何将子文档插入 mongo 集合?

Posted

技术标签:

【中文标题】如何将子文档插入 mongo 集合?【英文标题】:How to insert a subdocument into a mongo collection? 【发布时间】:2021-03-28 19:04:06 【问题描述】:

MERN 堆栈。 有一个主故事模型和一个事件模型,它是一个嵌入的子文档。我创建一个故事,然后向其中添加事件。我尝试了几种方法,搜索了将子文档插入现有 mongo 集合的方法,查看了 mongo 和 mongoose 文档(很少),我似乎无法插入。我之前能够做到,但在重构之后,代码不再工作了。我试着找出问题出在哪里,它似乎指向路线,尽管我不是 100% 的。

这是我的模型/模式(使用猫鼬):

const mongoose = require('mongoose')

    const GeoSchema = mongoose.Schema(
        type: 
            type: String,
            enum: ['Point', 'LineString', 'Polygon'],
            default: "Point"
        ,
        coordinates: 
            type: [Number],
            index: "2dsphere"
        
    )

    const EventSchema = mongoose.Schema(
        eventDate: 
            type: Date
        ,
        eventTitle: 
            type: String,
            required: true,
            minlength: 3
        ,
        eventDescription: 
            type: String,
            minlength: 3
        ,
        eventImageUrl: 
            type: String
        ,
        eventLink: 
            type: String
        ,
        eventAudio: 
            type: String
        ,
        eventLocation: GeoSchema,
    )

    const StorySchema = mongoose.Schema(
        storyTitle: 
            type: String,
            minlength: 5,
            required: true
        ,
        storySummary: 
            type: String
        ,
        storyImageUrl: 
            type: String,
            required: true
        ,
        storyStatus: 
            type: String,
            default: 'public',
            enum: ['public', 'private']
        ,
        storyCreator: 
            type: mongoose.Types.ObjectId,
            // required: true,
            ref: 'User'
        ,
        storyReferences: [String],
        storyTags: [String],
        storyMapStyle: 
            type: String,
            default: 'mapbox://styles/mapbox/light-v9',
        ,
        likes: [
            type: mongoose.Types.ObjectId,
            ref: 'User'
        ],
        event: [ EventSchema ]
    , timestamps: true)

    module.exports = mongoose.model('Story', StorySchema)
  

这是我的特快路线:

// Create a new event for a specific story, used by AddEvent.js
router.put('/:story_id/update', (req, res) => 
    const 
        eventDate,
        eventTitle,
        eventDescription,
        eventImageUrl,
        eventLink,
        eventAudio  = req.body

    console.log('eventDate',eventDate)

    Story.findByIdAndUpdate(
         _id: req.params.story_id,
        
            $push:
             event: 
                
                eventDate,
                eventTitle,
                eventDescription,
                eventImageUrl,
                eventLink,
                eventAudio
            
        )
)

这里是 React 代码以防万一:

import React, useEffect, useState from 'react';
import  useForm  from 'react-hook-form'
import  useHistory, useParams  from 'react-router-dom'
import * as yup from 'yup'
import  yupResolver  from "@hookform/resolvers/yup"
import axios from 'axios'
import clsx from 'clsx';

import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import Typography from '@material-ui/core/Typography';
import  makeStyles  from '@material-ui/core/styles';
import Container from '@material-ui/core/Container';
import Collapse from '@material-ui/core/Collapse';
import InfoIcon from '@material-ui/icons/Info';
import InputAdornment from '@material-ui/core/InputAdornment';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';

const schema = yup.object().shape(
    eventDate: yup
        .date(),
    eventTitle: yup
        .string()
        .required('Title is a required field.')
        .min(3),
    eventDescription: yup
        .string(),
    eventImageUrl: yup
        .string(),
    eventLink: yup
        .string(),
    eventAudio: yup
        .string(),
    // eventType: yup
    //     .string(),
    // eventLatitude: yup
    //     .number()
    //     .transform(cv => isNaN(cv) ? undefined : cv).positive()
    //     .nullable()
    //     .lessThan(90)
    //     .moreThan(-90)
    //     .notRequired() ,
    // eventLongitude: yup
    //     .number()
    //     .transform(cv => isNaN(cv) ? undefined : cv).positive()
    //     .nullable()
    //     .lessThan(180)
    //     .moreThan(-180)
    //     .notRequired() ,
)

const useStyles = makeStyles((theme) => (
    paper: 
        marginTop: theme.spacing(12),
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
    ,
    form: 
        width: '100%', // Fix IE 11 issue.
        marginTop: theme.spacing(1),
    ,
    submit: 
        margin: theme.spacing(2, 0, 0),
    ,
    formControl: 
        marginTop: '1rem',
    ,
));

export default function AddEvent(props) 
    const classes = useStyles();
    const history = useHistory()
    const  register, handleSubmit, errors  = useForm( 
        resolver: yupResolver(schema)
    )
    const  story_id  = useParams()
    
    const [data, setData] = useState('')
    const [expanded, setExpanded] = useState(false);
    
    const  
        eventDate,
        eventTitle,
        eventDescription,
        eventImageUrl,
        eventLink,
        eventAudio,
        eventLocation
     = data

    useEffect(() => 
            axios.put(`http://localhost:5000/story/$story_id/update`, 
                eventDate,
                eventTitle,
                eventDescription,
                eventImageUrl,
                eventLink,
                eventAudio,
                eventLocation
            )
            .then(() => history.goBack() )
            .catch(err => console.log(err))
    , [data])

    const handleExpandClick = () => 
        setExpanded(!expanded);
    ;

    const handleCancel = () => 
        history.goBack()
    ;

    const onSubmit = (data) => 
        console.log(data);
        setData(data) 
    


    return (
        <Container component="main" maxWidth="xs">
            <div className=classes.paper>
                <Typography>
                    Add New Event
                </Typography>
                <form className=classes.form noValidate onSubmit=handleSubmit(onSubmit)>
                    <TextField
                        variant="outlined"
                        margin="normal"
                        fullWidth
                        id="eventDate"
                        label="eventDate"
                        name="eventDate"
                        autoComplete="eventDate"
                        type="text"
                        autoFocus
                        inputRef=register
                        error=!!errors.eventDate
                        helperText=errors?.eventDate?.message
                    />
                    <TextField
                        variant="outlined"
                        margin="normal"
                        fullWidth
                        required
                        id="eventTitle"
                        label="eventTitle"
                        name="eventTitle"
                        autoComplete="eventTitle"
                        type="text"
                        inputRef=register
                        error=!!errors.eventTitle
                        helperText=errors?.eventTitle?.message
                    />
                    <TextField
                        variant="outlined"
                        margin="normal"
                        fullWidth
                        multiline
                        rows=4
                        name="eventDescription"
                        label="eventDescription"
                        type="text"
                        id="eventDescription"
                        inputRef=register
                        error=!!errors.eventDescription
                        helperText=errors?.eventDescription?.message
                    />
                    <TextField
                        variant="outlined"
                        margin="normal"
                        fullWidth
                        name="eventImageUrl"
                        label="eventImageUrl"
                        type="text"
                        id="eventImageUrl"
                        InputProps=
                            endAdornment: (
                                <InputAdornment position="end">
                                    <Button
                                        className=clsx(classes.expand, 
                                            [classes.expandOpen]: expanded,
                                        )
                                        onClick=handleExpandClick
                                        aria-expanded=expanded
                                        aria-label="show more"
                                    >
                                        <InfoIcon size='sm' />
                                    </Button>
                                </InputAdornment>
                            ),
                        
                        inputRef=register
                        error=!!errors.eventImageUrl
                        helperText=errors?.eventImageUrl?.message
                    /> 
                    <Collapse in=expanded timeout="auto" unmountOnExit>
                        <p>Pls paste either an image or video url link here.</p>
                        <p>If you are using a YouTube link: Navigate to the video you wish to embed. Click the Share link below the video, then click the Embed link. The embed link will be highlighted in blue. Copy and paste this link here.
                        </p>
                    </Collapse>
                    
                    <TextField
                        variant="outlined"
                        margin="normal"
                        fullWidth
                        name="eventLink"
                        label="eventLink"
                        type="text"
                        id="eventLink"
                        inputRef=register
                        error=!!errors.eventLink
                        helperText=errors?.eventLink?.message
                    />

                    <TextField
                        variant="outlined"
                        margin="normal"
                        fullWidth
                        name="eventAudio"
                        label="eventAudio"
                        type="text"
                        id="eventAudio"
                        inputRef=register
                        error=!!errors.eventAudio
                        helperText=errors?.eventAudio?.message
                    />
                    /* <FormControl fullWidth variant="outlined" className=classes.formControl>
                        <InputLabel id="demo-simple-select-outlined-label">eventType</InputLabel>
                        <Select
                            labelId="demo-simple-select-outlined-label"
                            id="demo-simple-select-outlined"
                            value=eventType
                            onChange=handleChange
                            defaultValue='Point'
                            label="eventType"
                            className=classes.selectEmpty
                        >
                            <MenuItem value='Point'>Point</MenuItem>
                            <MenuItem value='LineString'>LineString</MenuItem>
                            <MenuItem value='Polygon'>Polygon</MenuItem>
                        </Select>
                    </FormControl> */
                    <TextField
                        variant="outlined"
                        margin="normal"
                        fullWidth
                        name="eventLatitude"
                        label="eventLatitude"
                        type="number"
                        id="eventLatitude"
                        inputRef=register
                        error=!!errors.eventLatitude
                        helperText=errors?.eventLatitude?.message
                    />

                    <TextField
                        variant="outlined"
                        margin="normal"
                        fullWidth
                        name="eventLongitude"
                        label="eventLongitude"
                        type="number"
                        id="eventLongitude"
                        inputRef=register
                        error=!!errors.eventLongitude
                        helperText=errors?.eventLongitude?.message
                    />

                    <Button
                        type="submit"
                        fullWidth
                        variant="contained"
                        color="primary"
                        className=classes.submit
                    >
                        Submit
                    </Button>

                    <Button
                        fullWidth
                        color="default"
                        onClick=handleCancel
                        className=classes.submit
                    >
                        Cancel
                    </Button>
                </form>
            </div>
        </Container>
    );



【问题讨论】:

我收集的一些研究: - 要从集合中编辑嵌套文档,请使用位置和集合运算符 - 请记住,在 MongoDB 中,只有在保存父文档时才会保存子文档。 - 要将嵌套文档添加/插入到集合中,请使用推送操作符从集合中删除嵌套文档,使用拉取操作符。 ``` db.timelines.update( _id: 1, $push: event: title: "something", year: 1980 ) ``` 【参考方案1】:

在一个简单的答案中,您应该调用 Story.updateOne 而不是 Story.findByIdAndUpdate,但我会建议更多(将其视为代码审查的东西)...

利用多元化,因此该属性应称为events 而不是event,因为它会有多个 删除属性前缀,所以StoryTitle 将只是title(我们已经知道它是一个故事,因为它在一个故事集合中),而EventTitle 将是一个简单的“事件”(我们已经知道这是一个事件)因为它在“事件”数组中) 为什么将storyReferences设置为字符串,如果是字符串数组,应该更容易操作,storyTags也一样

我冒昧,只是为了帮助您开发 MongoDB API,在 GitHub 中使用非常简单的 REST 调用创建一个非常简单的 NodeJs 项目,您可以在其中进行调用以创建文档并查询他们...

GitHub 项目https://github.com/balexandre/so65351733

【讨论】:

Obrigado @balexandre!我将剖析您答案的每一行。至于属性前缀,主要是为了代码的可读性。所以,summary 属于 Story,description 属于 Event。我确实在某个地方读过,名字不冗长,越简单越好。我会尝试使用较短的名称。你是对的,storyReferences 和 storyTags 应该是数组。我暂时将它们转换为字符串,因为我遇到了数组类型的问题。明天我会看看你的码头集装箱。我可能还有更多问题。

以上是关于如何将子文档插入 mongo 集合?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 mongo 搜索集合并返回子文档列表(Spring-data-mongo)

mongo学习-固定集合

NodeJS/Mongo:通过各种集合循环查询

Mongo - 从对象更新集合的每个文档

如何使用Mongo Java驱动程序从集合中检索随机文档

mongo+mongoose+express