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

How to use Mongoose discriminators?

2月22日 20:12

Mongoose Discriminators are a schema inheritance mechanism that allows you to store different types of documents in the same collection while maintaining their unique fields and validation rules. This is very useful for handling data models that share a common base but have specific differences.

Basic Concepts

Create Base Schema

javascript
const eventSchema = new Schema({ name: { type: String, required: true }, date: { type: Date, required: true }, location: String }, { discriminatorKey: 'kind' // Field to distinguish different types }); const Event = mongoose.model('Event', eventSchema);

Create Discriminators

javascript
// Create conference type discriminator const conferenceSchema = new Schema({ speakers: [String], sponsors: [String] }); const Conference = Event.discriminator('Conference', conferenceSchema); // Create meetup type discriminator const meetupSchema = new Schema({ attendees: Number, maxAttendees: Number }); const Meetup = Event.discriminator('Meetup', meetupSchema);

Using Discriminators

Creating Documents

javascript
// Create base event const event = await Event.create({ name: 'General Event', date: new Date('2024-01-01'), location: 'New York' }); // Create conference const conference = await Conference.create({ name: 'Tech Conference', date: new Date('2024-02-01'), location: 'San Francisco', speakers: ['Alice', 'Bob'], sponsors: ['Company A', 'Company B'] }); // Create meetup const meetup = await Meetup.create({ name: 'Developer Meetup', date: new Date('2024-03-01'), location: 'Boston', attendees: 50, maxAttendees: 100 });

Querying Documents

javascript
// Query all events const allEvents = await Event.find(); // Query specific type events const conferences = await Conference.find(); const meetups = await Meetup.find(); // Use discriminatorKey to query const conferences2 = await Event.find({ kind: 'Conference' });

Nested Discriminators

Using Discriminators in Subdocuments

javascript
const batchSchema = new Schema({ name: String, size: Number, product: { type: Schema.Types.ObjectId, ref: 'Product' } }, { discriminatorKey: 'kind' }); const orderSchema = new Schema({ customer: String, items: [batchSchema] }); // Create product type discriminator const productBatchSchema = new Schema({ quantity: Number, unit: String }); const productBatch = batchSchema.discriminator('ProductBatch', productBatchSchema); // Create service type discriminator const serviceBatchSchema = new Schema({ duration: Number, rate: Number }); const serviceBatch = batchSchema.discriminator('ServiceBatch', serviceBatchSchema);

Discriminator Middleware

Adding Middleware to Discriminators

javascript
// Add middleware for conference conferenceSchema.pre('save', function(next) { console.log('Saving conference:', this.name); next(); }); // Add middleware for meetup meetupSchema.pre('save', function(next) { if (this.attendees > this.maxAttendees) { return next(new Error('Attendees cannot exceed max')); } next(); });

Base Schema Middleware

javascript
// Base schema middleware applies to all discriminators eventSchema.pre('save', function(next) { console.log('Saving event:', this.name); next(); });

Discriminator Methods

Adding Methods to Discriminators

javascript
// Add method for conference conferenceSchema.methods.getSpeakerCount = function() { return this.speakers.length; }; // Add method for meetup meetupSchema.methods.getAvailableSpots = function() { return this.maxAttendees - this.attendees; }; // Use methods const conference = await Conference.findById(conferenceId); console.log(conference.getSpeakerCount()); const meetup = await Meetup.findById(meetupId); console.log(meetup.getAvailableSpots());

Discriminator Validation

Adding Validation to Discriminators

javascript
conferenceSchema.path('speakers').validate(function(speakers) { return speakers.length > 0; }, 'Conference must have at least one speaker'); meetupSchema.path('attendees').validate(function(attendees) { return attendees >= 0; }, 'Attendees cannot be negative');

Practical Use Cases

1. Content Management System

javascript
const contentSchema = new Schema({ title: { type: String, required: true }, author: { type: String, required: true }, publishedAt: Date }, { discriminatorKey: 'contentType' }); const Content = mongoose.model('Content', contentSchema); // Article type const articleSchema = new Schema({ body: String, tags: [String] }); const Article = Content.discriminator('Article', articleSchema); // Video type const videoSchema = new Schema({ url: String, duration: Number, thumbnail: String }); const Video = Content.discriminator('Video', videoSchema);

2. User Role System

javascript
const userSchema = new Schema({ name: { type: String, required: true }, email: { type: String, required: true, unique: true }, password: { type: String, required: true } }, { discriminatorKey: 'role' }); const User = mongoose.model('User', userSchema); // Admin const adminSchema = new Schema({ permissions: [String], department: String }); const Admin = User.discriminator('Admin', adminSchema); // Customer const customerSchema = new Schema({ address: String, phone: String, loyaltyPoints: { type: Number, default: 0 } }); const Customer = User.discriminator('Customer', customerSchema);

3. Order System

javascript
const orderSchema = new Schema({ orderNumber: { type: String, required: true }, customer: { type: Schema.Types.ObjectId, ref: 'User' }, total: Number, status: String }, { discriminatorKey: 'orderType' }); const Order = mongoose.model('Order', orderSchema); // Online order const onlineOrderSchema = new Schema({ shippingAddress: String, trackingNumber: String }); const OnlineOrder = Order.discriminator('OnlineOrder', onlineOrderSchema); // In-store order const inStoreOrderSchema = new Schema({ pickupTime: Date, storeLocation: String }); const InStoreOrder = Order.discriminator('InStoreOrder', inStoreOrderSchema);

Discriminators vs Embedded Documents

Selection Guide

Use discriminators when:

  • Need to query all types in the same collection
  • Different types have many common fields
  • Need unified indexes and queries
  • Number of types is relatively small

Use embedded documents when:

  • Each type has completely different structure
  • Don't need cross-type queries
  • Need better performance isolation
  • Many types

Best Practices

  1. Design base schema properly: Base schema should include fields common to all types
  2. Use clear discriminatorKey: Choose meaningful field names to distinguish types
  3. Add validation to discriminators: Ensure data integrity for each type
  4. Leverage middleware: Add specific business logic for different types
  5. Consider performance: Discriminators are in the same collection, may affect query performance
  6. Clear documentation: Add clear comments for each discriminator
  7. Test coverage: Write tests for each discriminator type
标签:Mongoose