如何使模型 属性 在环回 4 mongoDB 中唯一?

How do I make a model property unique in loopback 4 mongoDB?

我想让 name 字段在 loopback 4 模型中是唯一的。我正在使用 MongoDB 数据源。我尝试使用 index: { unique: true } 似乎不起作用。我阅读了有关 validatesUniquenessOf() 验证的环回文档,但我不清楚如何以及在何处使用它。

@model()
export class BillingAddress extends Entity {
  @property({
    type: 'string',
    id: true,
    mongodb: {dataType: 'ObjectId'}
  })
  _id: string;

  @property({
    type: 'string',
    required: true,
    index: {
        unique: true
    },
  })
  name: string;
  ...
  ...
  ...
  constructor(data?: Partial<BillingAddress>) {
    super(data);
  }
}

validatesUniquenessOf() 是有效的并且在旧的环回版本中可用 https://apidocs.strongloop.com/loopback-datasource-juggler/#validatable-validatesuniquenessof

在 Loopback 4 中,如文档中所述,有两种方法可以处理此类独特的约束,

1.在 ORM 层添加验证 - https://loopback.io/doc/en/lb4/Validation-ORM-layer.html

Schema constraints are enforced by specific databases, such as unique index

因此我们需要在 mongodb

的数据库级别添加唯一约束
> db.BillingAddress.createIndex({ "name": 1 }, { unique: true })
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}

创建唯一索引后,当我们尝试插入具有相同名称的帐单地址时,我们应该得到以下错误,

MongoError: E11000 duplicate key error collection: BillingAddress.BillingAddress index: name_1 dup key: { : "Bob" }

Request POST /billing-addresses failed with status code 500. 

2。在 Controller 层添加验证 - https://loopback.io/doc/en/lb4/Validation-controller-repo-service-layer.html

我。在控制器中验证功能:

// create a validateUniqueBillingAddressName function and call it here
if (!this.validateUniqueBillingAddressName(name))
  throw new HttpErrors.UnprocessableEntity('Name already exist');
return this.billingAddressRepository.create(billingAddress);

二。在拦截器处验证并将其注入控制器:

> lb4 interceptor
? Interceptor name: validateBillingAddressName
? Is it a global interceptor? No
   create src/interceptors/validate-billing-address-name.interceptor.ts
   update src/interceptors/index.ts

您可能需要在拦截器文件中编写验证逻辑 validate-billing-address-name.interceptor.ts 类似

import {
  injectable,
  Interceptor,
  InvocationContext,
  InvocationResult,
  Provider,
  ValueOrPromise
} from '@loopback/core';
import {repository} from '@loopback/repository';
import {HttpErrors} from '@loopback/rest';
import {BillingAddressRepository} from '../repositories';

/**
 * This class will be bound to the application as an `Interceptor` during
 * `boot`
 */
@injectable({tags: {key: ValidateBillingAddressNameInterceptor.BINDING_KEY}})
export class ValidateBillingAddressNameInterceptor implements Provider<Interceptor> {
  static readonly BINDING_KEY = `interceptors.${ValidateBillingAddressNameInterceptor.name}`;

  constructor(
    @repository(BillingAddressRepository)
    public billingAddressRepository: BillingAddressRepository
  ) { }

  /**
   * This method is used by LoopBack context to produce an interceptor function
   * for the binding.
   *
   * @returns An interceptor function
   */
  value() {
    return this.intercept.bind(this);
  }

  /**
   * The logic to intercept an invocation
   * @param invocationCtx - Invocation context
   * @param next - A function to invoke next interceptor or the target method
   */
  async intercept(
    invocationCtx: InvocationContext,
    next: () => ValueOrPromise<InvocationResult>,
  ) {
    try {
      // Add pre-invocation logic here
      if (invocationCtx.methodName === 'create') {
        const {name} = invocationCtx.args[0];
        const nameAlreadyExist = await this.billingAddressRepository.find({where: {name}})
        if (nameAlreadyExist.length) {
          throw new HttpErrors.UnprocessableEntity(
            'Name already exist',
          );
        }
      }
      const result = await next();
      // Add post-invocation logic here
      return result;
    } catch (err) {
      // Add error handling logic here
      throw err;
    }
  }
}

然后通过它的绑定键将这个拦截器注入到你的 billing-address.controller.ts 文件中,比如,

import {intercept} from '@loopback/context';
import {
  repository
} from '@loopback/repository';
import {
  getModelSchemaRef,
  post,

  requestBody
} from '@loopback/rest';
import {ValidateBillingAddressNameInterceptor} from '../interceptors';
import {BillingAddress} from '../models';
import {BillingAddressRepository} from '../repositories';

export class BillingAddressController {
  constructor(
    @repository(BillingAddressRepository)
    public billingAddressRepository: BillingAddressRepository,
  ) { }

  @intercept(ValidateBillingAddressNameInterceptor.BINDING_KEY)
  @post('/billing-addresses', {
    responses: {
      '200': {
        description: 'BillingAddress model instance',
        content: {'application/json': {schema: getModelSchemaRef(BillingAddress)}},
      },
    },
  })
  async create(
    @requestBody({
      content: {
        'application/json': {
          schema: getModelSchemaRef(BillingAddress, {
            title: 'NewBillingAddress',
            exclude: ['id'],
          }),
        },
      },
    })
    billingAddress: Omit<BillingAddress, 'id'>,
  ): Promise<BillingAddress> {
    return this.billingAddressRepository.create(billingAddress);
  }
}