乐闻世界logo
搜索文章和话题

Why does mongoose have both schemas and models

5 个月前提问
3 个月前修改
浏览次数31

5个答案

1
2
3
4
5

Mongoose 中的 SchemaModel 是 MongoDB 数据库操作的两个非常重要的概念,它们在设计和操作数据库时扮演着不同的角色。

Schema

Schema 是用于定义 MongoDB 集合中文档的结构的一种方式。它是一个对象,描述了数据的形状和类型,可以理解为数据的蓝图或者模板。通过 Schema,我们可以非常详细地指定文档中应该有哪些字段、字段类型是什么、是否必须、默认值是多少、验证规则等信息。

例如,如果我们有一个用户的集合,我们可能会定义一个这样的 Schema

javascript
const mongoose = require('mongoose'); const userSchema = new mongoose.Schema({ username: { type: String, required: true }, password: { type: String, required: true }, email: { type: String, required: true }, createdAt: { type: Date, default: Date.now } });

在这个例子中,userSchema 定义了用户应该有 usernamepasswordemail 以及 createdAt 这些字段,它们的类型分别是 StringDate,并且除了 createdAt 有默认值外,其他的都是必填的。

Model

Model 是基于 Schema 编译而成的构造函数,或者说类,它的实例就代表了数据库中的一个个文档。通过 Model,我们可以对数据库进行实际的 CRUD 操作(创建、读取、更新、删除)。

继续上面的例子,我们会这样创建一个 Model

javascript
const User = mongoose.model('User', userSchema);

这里,我们创建了一个叫做 UserModel,它关联到了 userSchema。这意味着我们现在可以创建新用户、查询用户、更新用户、删除用户等:

javascript
// 创建新用户 const newUser = new User({ username: 'johndoe', password: '123456', email: 'johndoe@example.com' }); newUser.save((err, savedUser) => { if (err) throw err; // savedUser 是存入数据库的用户文档 }); // 查询所有用户 User.find({}, (err, users) => { if (err) throw err; // users 是一个包含所有用户文档的数组 });

为什么既有 Schema 又有 Model?

SchemaModel 之所以分开,是因为它们各自承担了不同的职责。Schema 负责定义数据的结构和规则,而 Model 则是一个提供与数据库交互能力的接口。

将这两者分开,使得 Mongoose 的设计更加灵活和模块化。你可以在一个地方定义你的数据结构(Schema),然后在需要的地方创建一个或多个 Model 来处理数据。这种分离也便于维护和扩展,因为数据结构可能会频繁变化,而分开后,我们可以仅仅修改 Schema 而不需要触及到使用它的 Model

此外,Mongoose 还允许我们在 Schema 中定义实例方法、静态方法和虚拟属性,这样我们可以在 Model 的实例上调用这些方法,从而让数据操作更加方便和高效。

2024年6月29日 12:07 回复

EDIT: Although this has been useful for many people, as mentioned in the comments it answers the "how" rather than the why. Thankfully, the why of the question has been answered elsewhere also, with this answer to another question. This has been linked in the comments for some time but I realise that many may not get that far when reading.

Often the easiest way to answer this type of question is with an example. In this case, someone has already done it for me :)

Take a look here:

http://rawberg.com/blog/nodejs/mongoose-orm-nested-models/

EDIT: The original post (as mentioned in the comments) seems to no longer exist, so I am reproducing it below. Should it ever return, or if it has just moved, please let me know.

It gives a decent description of using schemas within models in mongoose and why you would want to do it, and also shows you how to push tasks via the model while the schema is all about the structure etc.

Original Post:

Let’s start with a simple example of embedding a schema inside a model.

shell
var TaskSchema = new Schema({ name: String, priority: Number }); TaskSchema.virtual('nameandpriority') .get( function () { return this.name + '(' + this.priority + ')'; }); TaskSchema.method('isHighPriority', function() { if(this.priority === 1) { return true; } else { return false; } }); var ListSchema = new Schema({ name: String, tasks: [TaskSchema] }); mongoose.model('List', ListSchema); var List = mongoose.model('List'); var sampleList = new List({name:'Sample List'});

I created a new TaskSchema object with basic info a task might have. A Mongoose virtual attribute is setup to conveniently combine the name and priority of the Task. I only specified a getter here but virtual setters are supported as well.

I also defined a simple task method called isHighPriority to demonstrate how methods work with this setup.

In the ListSchema definition you’ll notice how the tasks key is configured to hold an array of TaskSchema objects. The task key will become an instance of DocumentArray which provides special methods for dealing with embedded Mongo documents.

For now I only passed the ListSchema object into mongoose.model and left the TaskSchema out. Technically it's not necessary to turn the TaskSchema into a formal model since we won’t be saving it in it’s own collection. Later on I’ll show you how it doesn’t harm anything if you do and it can help to organize all your models in the same way especially when they start spanning multiple files.

With the List model setup let’s add a couple tasks to it and save them to Mongo.

shell
var List = mongoose.model('List'); var sampleList = new List({name:'Sample List'}); sampleList.tasks.push( {name:'task one', priority:1}, {name:'task two', priority:5} ); sampleList.save(function(err) { if (err) { console.log('error adding new list'); console.log(err); } else { console.log('new list successfully saved'); } });

The tasks attribute on the instance of our List model (sampleList) works like a regular JavaScript array and we can add new tasks to it using push. The important thing to notice is the tasks are added as regular JavaScript objects. It’s a subtle distinction that may not be immediately intuitive.

You can verify from the Mongo shell that the new list and tasks were saved to mongo.

shell
db.lists.find() { "tasks" : [ { "_id" : ObjectId("4dd1cbeed77909f507000002"), "priority" : 1, "name" : "task one" }, { "_id" : ObjectId("4dd1cbeed77909f507000003"), "priority" : 5, "name" : "task two" } ], "_id" : ObjectId("4dd1cbeed77909f507000001"), "name" : "Sample List" }

Now we can use the ObjectId to pull up the Sample List and iterate through its tasks.

shell
List.findById('4dd1cbeed77909f507000001', function(err, list) { console.log(list.name + ' retrieved'); list.tasks.forEach(function(task, index, array) { console.log(task.name); console.log(task.nameandpriority); console.log(task.isHighPriority()); }); });

If you run that last bit of code you’ll get an error saying the embedded document doesn’t have a method isHighPriority. In the current version of Mongoose you can’t access methods on embedded schemas directly. There’s an open ticket to fix it and after posing the question to the Mongoose Google Group, manimal45 posted a helpful work-around to use for now.

shell
List.findById('4dd1cbeed77909f507000001', function(err, list) { console.log(list.name + ' retrieved'); list.tasks.forEach(function(task, index, array) { console.log(task.name); console.log(task.nameandpriority); console.log(task._schema.methods.isHighPriority.apply(task)); }); });

If you run that code you should see the following output on the command line.

shell
Sample List retrieved task one task one (1) true task two task two (5) false

With that work-around in mind let’s turn the TaskSchema into a Mongoose model.

shell
mongoose.model('Task', TaskSchema); var Task = mongoose.model('Task'); var ListSchema = new Schema({ name: String, tasks: [Task.schema] }); mongoose.model('List', ListSchema); var List = mongoose.model('List');

The TaskSchema definition is the same as before so I left it out. Once its turned into a model we can still access it’s underlying Schema object using dot notation.

Let’s create a new list and embed two Task model instances within it.

shell
var demoList = new List({name:'Demo List'}); var taskThree = new Task({name:'task three', priority:10}); var taskFour = new Task({name:'task four', priority:11}); demoList.tasks.push(taskThree.toObject(), taskFour.toObject()); demoList.save(function(err) { if (err) { console.log('error adding new list'); console.log(err); } else { console.log('new list successfully saved'); } });

As we’re embedding the Task model instances into the List we’re calling toObject on them to convert their data into plain JavaScript objects that the List.tasks DocumentArray is expecting. When you save model instances this way your embedded documents will contain ObjectIds.

The complete code example is available as a gist. Hopefully these work-arounds help smooth things over as Mongoose continues to develop. I’m still pretty new to Mongoose and MongoDB so please feel free to share better solutions and tips in the comments. Happy data modeling!

2024年6月29日 12:07 回复

Schema is an object that defines the structure of any documents that will be stored in your MongoDB collection; it enables you to define types and validators for all of your data items.

Model is an object that gives you easy access to a named collection, allowing you to query the collection and use the Schema to validate any documents you save to that collection. It is created by combining a Schema, a Connection, and a collection name.

Originally phrased by Valeri Karpov, MongoDB Blog

2024年6月29日 12:07 回复

I don't think the accepted answer actually answers the question that was posed. The answer doesn't explain why Mongoose has decided to require a developer to provide both a Schema and a Model variable. An example of a framework where they have eliminated the need for the developer to define the data schema is django--a developer writes up their models in the models.py file, and leaves it to the framework to manage the schema. The first reason that comes to mind for why they do this, given my experience with django, is ease-of-use. Perhaps more importantly is the DRY (don't repeat yourself) principle--you don't have to remember to update the schema when you change the model--django will do it for you! Rails also manages the schema of the data for you--a developer doesn't edit the schema directly, but changes it by defining migrations that manipulate the schema.

One reason I could understand that Mongoose would separate the schema and the model is instances where you would want to build a model from two schemas. Such a scenario might introduce more complexity than is worth managing--if you have two schemas that are managed by one model, why aren't they one schema?

Perhaps the original question is more a relic of the traditional relational database system. In world NoSQL/Mongo world, perhaps the schema is a little more flexible than MySQL/PostgreSQL, and thus changing the schema is more common practice.

2024年6月29日 12:07 回复

To understand why? you have to understand what actually is Mongoose?

Well, the mongoose is an object data modeling library for MongoDB and Node JS, providing a higher level of abstraction. So it's a bit like the relationship between Express and Node, so Express is a layer of abstraction over regular Node, while Mongoose is a layer of abstraction over the regular MongoDB driver.

An object data modeling library is just a way for us to write Javascript code that will then interact with a database. So we could just use a regular MongoDB driver to access our database, it would work just fine.

But instead we use Mongoose because it gives us a lot more functionality out of the box, allowing for faster and simpler development of our applications.

So, some of the features Mongoose gives us schemas to model our data and relationship, easy data validation, a simple query API, middleware, and much more.

In Mongoose, a schema is where we model our data, where we describe the structure of the data, default values, and validation, then we take that schema and create a model out of it, a model is basically a wrapper around the schema, which allows us to actually interface with the database in order to create, delete, update, and read documents.

enter image description here

Let's create a model from a schema.

shell
const tourSchema = new mongoose.Schema({ name: { type: String, required: [true, 'A tour must have a name'], unique: true, }, rating: { type: Number, default: 4.5, }, price: { type: Number, required: [true, 'A tour must have a price'], }, }); //tour model const Tour = mongoose.model('Tour', tourSchema);

According to convetion first letter of a model name must be capitalized.

Let's create instance of our model that we created using mongoose and schema. also, interact with our database.

shell
const testTour = new Tour({ // instance of our model name: 'The Forest Hiker', rating: 4.7, price: 497, }); // saving testTour document into database testTour .save() .then((doc) => { console.log(doc); }) .catch((err) => { console.log(err); });

So having both schama and modle mongoose makes our life easier.

2024年6月29日 12:07 回复

你的答案