Mongoose Discriminators(鉴别器)是一种模式继承机制,允许你在同一个集合中存储不同类型的文档,同时保持各自独特的字段和验证规则。这对于处理具有共同基础但又有特定差异的数据模型非常有用。
基本概念
创建基础 Schema
javascriptconst eventSchema = new Schema({ name: { type: String, required: true }, date: { type: Date, required: true }, location: String }, { discriminatorKey: 'kind' // 用于区分不同类型的字段 }); const Event = mongoose.model('Event', eventSchema);
创建鉴别器
javascript// 创建会议类型的鉴别器 const conferenceSchema = new Schema({ speakers: [String], sponsors: [String] }); const Conference = Event.discriminator('Conference', conferenceSchema); // 创建聚会类型的鉴别器 const meetupSchema = new Schema({ attendees: Number, maxAttendees: Number }); const Meetup = Event.discriminator('Meetup', meetupSchema);
使用鉴别器
创建文档
javascript// 创建基础事件 const event = await Event.create({ name: 'General Event', date: new Date('2024-01-01'), location: 'New York' }); // 创建会议 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'] }); // 创建聚会 const meetup = await Meetup.create({ name: 'Developer Meetup', date: new Date('2024-03-01'), location: 'Boston', attendees: 50, maxAttendees: 100 });
查询文档
javascript// 查询所有事件 const allEvents = await Event.find(); // 查询特定类型的事件 const conferences = await Conference.find(); const meetups = await Meetup.find(); // 使用 discriminatorKey 查询 const conferences2 = await Event.find({ kind: 'Conference' });
嵌套鉴别器
在子文档中使用鉴别器
javascriptconst batchSchema = new Schema({ name: String, size: Number, product: { type: Schema.Types.ObjectId, ref: 'Product' } }, { discriminatorKey: 'kind' }); const orderSchema = new Schema({ customer: String, items: [batchSchema] }); // 创建产品类型的鉴别器 const productBatchSchema = new Schema({ quantity: Number, unit: String }); const productBatch = batchSchema.discriminator('ProductBatch', productBatchSchema); // 创建服务类型的鉴别器 const serviceBatchSchema = new Schema({ duration: Number, rate: Number }); const serviceBatch = batchSchema.discriminator('ServiceBatch', serviceBatchSchema);
鉴别器中间件
为鉴别器添加中间件
javascript// 为会议添加中间件 conferenceSchema.pre('save', function(next) { console.log('Saving conference:', this.name); next(); }); // 为聚会添加中间件 meetupSchema.pre('save', function(next) { if (this.attendees > this.maxAttendees) { return next(new Error('Attendees cannot exceed max')); } next(); });
基础 Schema 的中间件
javascript// 基础 Schema 的中间件会应用到所有鉴别器 eventSchema.pre('save', function(next) { console.log('Saving event:', this.name); next(); });
鉴别器方法
为鉴别器添加方法
javascript// 为会议添加方法 conferenceSchema.methods.getSpeakerCount = function() { return this.speakers.length; }; // 为聚会添加方法 meetupSchema.methods.getAvailableSpots = function() { return this.maxAttendees - this.attendees; }; // 使用方法 const conference = await Conference.findById(conferenceId); console.log(conference.getSpeakerCount()); const meetup = await Meetup.findById(meetupId); console.log(meetup.getAvailableSpots());
鉴别器验证
为鉴别器添加验证
javascriptconferenceSchema.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');
实际应用场景
1. 内容管理系统
javascriptconst contentSchema = new Schema({ title: { type: String, required: true }, author: { type: String, required: true }, publishedAt: Date }, { discriminatorKey: 'contentType' }); const Content = mongoose.model('Content', contentSchema); // 文章类型 const articleSchema = new Schema({ body: String, tags: [String] }); const Article = Content.discriminator('Article', articleSchema); // 视频类型 const videoSchema = new Schema({ url: String, duration: Number, thumbnail: String }); const Video = Content.discriminator('Video', videoSchema);
2. 用户角色系统
javascriptconst 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); // 管理员 const adminSchema = new Schema({ permissions: [String], department: String }); const Admin = User.discriminator('Admin', adminSchema); // 客户 const customerSchema = new Schema({ address: String, phone: String, loyaltyPoints: { type: Number, default: 0 } }); const Customer = User.discriminator('Customer', customerSchema);
3. 订单系统
javascriptconst 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); // 在线订单 const onlineOrderSchema = new Schema({ shippingAddress: String, trackingNumber: String }); const OnlineOrder = Order.discriminator('OnlineOrder', onlineOrderSchema); // 到店订单 const inStoreOrderSchema = new Schema({ pickupTime: Date, storeLocation: String }); const InStoreOrder = Order.discriminator('InStoreOrder', inStoreOrderSchema);
鉴别器 vs 嵌入文档
选择指南
使用鉴别器当:
- 需要在同一个集合中查询所有类型
- 不同类型有大量共同字段
- 需要统一的索引和查询
- 类型数量相对较少
使用嵌入文档当:
- 每种类型有完全不同的结构
- 不需要跨类型查询
- 需要更好的性能隔离
- 类型数量很多
最佳实践
- 合理设计基础 Schema:基础 Schema 应包含所有类型的共同字段
- 使用清晰的 discriminatorKey:选择有意义的字段名来区分类型
- 为鉴别器添加验证:确保每种类型的数据完整性
- 利用中间件:为不同类型添加特定的业务逻辑
- 考虑性能:鉴别器在同一个集合中,可能影响查询性能
- 文档清晰:为每个鉴别器添加清晰的注释
- 测试覆盖:为每种鉴别器编写测试