Skip to main content
Version: 6.6

EntityHelper 和装饰实体

使用 assign() 更新实体值

¥Updating Entity Values with assign()

当你想要根据用户输入更新实体时,通常只需将实体关系的纯字符串 ID 作为用户输入。通常,你需要先使用 em.getReference() 从每个 id 创建引用,然后使用这些引用来更新实体关系:

¥When you want to update entity based on user input, you will usually have just plain string IDs of entity relations as user input. Normally you would need to use em.getReference() to create references from each id first, and then use those references to update entity relations:

const jon = new Author('Jon Snow', 'snow@wall.st');
const book = new Book('Book', jon);
book.author = em.getReference(Author, '...id...');

使用 assign() 可以轻松实现相同的结果:

¥Same result can be easily achieved with assign():

import { wrap } from '@mikro-orm/core';

wrap(book).assign({
title: 'Better Book 1',
author: '...id...',
});
console.log(book.title); // 'Better Book 1'
console.log(book.author); // instance of Author with id: '...id...'
console.log(book.author.id); // '...id...'

要在非托管实体上使用 assign(),你需要明确提供 EntityManager 实例:

¥To use assign() on not managed entities, you need to provide EntityManager instance explicitly:

import { wrap } from '@mikro-orm/core';

const book = new Book();
wrap(book).assign({
title: 'Better Book 1',
author: '...id...',
}, { em });

默认情况下,entity.assign(data) 的行为类似于 Object.assign(entity, data),例如它不会递归合并事物。要启用对象属性(不是引用的实体)的深度合并,请使用第二个参数启用 mergeObjectProperties 标志:

¥By default, entity.assign(data) behaves similar to Object.assign(entity, data), e.g. it does not merge things recursively. To enable deep merging of object properties (not referenced entities), use second parameter to enable mergeObjectProperties flag:

import { wrap } from '@mikro-orm/core';

book.meta = { foo: 1, bar: 2 };

wrap(book).assign({ meta: { foo: 3 } }, { mergeObjectProperties: true });
console.log(book.meta); // { foo: 3, bar: 2 }

wrap(book).assign({ meta: { foo: 4 } });
console.log(book.meta); // { foo: 4 }

此规则的一个例外是分配给嵌入属性。默认情况下,它们以递归方式与数据合并。你可以通过 mergeEmbeddedProperties 标志(默认为 true)选择退出。

¥One exception to this rule is assigning to embedded properties. Those are by default merged with the data recursively. You can opt out of that via mergeEmbeddedProperties flag (which defaults to true).

更新深度实体图

¥Updating deep entity graph

自 v5 以来,使用 assign 3.0, 选项已被 取代。要更新现有实体,你需要在 data 中提供其主键,并首先将该实体加载到当前上下文中。

¥Since v5, assign allows updating deep entity graph by default. To update existing entity, you need to provide its primary key in the data, as well as load that entity first into current context.

const book = await em.findOneOrFail(Book, 1, { populate: ['author'] });

// update existing book's author's name
wrap(book).assign({
author: {
id: book.author.id,
name: 'New name...',
},
});

如果你希望始终更新实体,即使 data 中不存在实体主键,也可以使用 updateByPrimaryKey: false

¥If you want to always update the entity, even without the entity PK being present in data, you can use updateByPrimaryKey: false:

const book = await em.findOneOrFail(Book, 1, { populate: ['author'] });

// update existing book's author's name
wrap(book).assign({
author: {
name: 'New name...',
},
}, { updateByPrimaryKey: false });

否则,没有主键的实体数据将被视为新实体,并将触发 INSERT 查询:

¥Otherwise, the entity data without PK are considered as new entity, and will trigger an INSERT query:

const book = await em.findOneOrFail(Book, 1, { populate: ['author'] });

// creating new author for given book
wrap(book).assign({
author: {
name: 'New name...',
},
});

同样适用于你未先将子实体加载到上下文中的情况,例如,当你尝试将子实体赋值给一个未填充的关系时。即使你提供了它的主键,它也会被视为一个新对象并触发 INSERT 查询。

¥Same applies to the case when you do not load the child entity first into the context, e.g. when you try to assign to a relation that was not populated. Even if you provide its primary key, it will be considered as a new object and trigger an INSERT query.

const book = await em.findOneOrFail(Book, 1); // author is not populated

// creating new author for given book
wrap(book).assign({
author: {
id: book.author.id,
name: 'New name...',
},
});

更新集合时,你可以传递包含所有项的完整数组,也可以仅传递单个项。 - 在这种情况下,新项目将附加到现有项目之后。传递一个全新的项目数组将替换现有项目。以前存在的项目将从集合中断开/删除。还请检查 收藏页面 中关于从集合中删除实体的影响。

¥When updating collections, you can either pass a complete array of all items, or just a single item - in such a case, the new item will be appended to the existing items. Passing a completely new array of items will replace the existing items. Previously existing items will be disconnected/removed from the collection. Also check the Collection page on the effects of removing entities from collections.

// resets the addresses collection to a single item
wrap(user).assign({ addresses: [new Address(...)] });

// adds new address to the collection
wrap(user).assign({ addresses: new Address(...) });

使用基于类的数​​据

¥Using class-based data

在分配给关系属性时,重要的是仅使用纯 JavaScript 对象 (POJO)。你可以扩展 @mikro-orm/core 包提供的 PlainObject 类,让 ORM 知道某些类应该被视为 POJO。

¥When assigning to relation properties, it is important to use only plain JavaScript objects (POJO). You can extend the PlainObject class provided by the @mikro-orm/core package to let the ORM know some class should be considered as POJO.

如果你想使用像 class-transformer 这样的包来验证 DTO,这很方便。

¥This is handy if you want to use packages like class-transformer for validation of the DTO.

import { PlainObject } from '@mikro-orm/core';

class UpdateAuthorDTO extends PlainObject {

@IsString()
@IsNotEmpty()
name!: string;

@ValidateNested()
@Type(() => UpdateBookDto)
books!: UpdateBookDto[];

}

// dto is an instance of UpdateAuthorDto
em.assign(user, dto);

assign 选项

¥assign options

你可以通过以下选项(在第二个参数中传递)配置 assign 助手的工作方式:

¥You can configure how the assign helper works via the following options (passed in the second argument):

updateNestedEntities

允许禁用嵌套关系的处理。禁用此选项后,使用对象负载代替关系始终会导致 INSERT 查询。要分配关系的值,请使用外键而不是对象。默认为 true

¥Allows disabling processing of nested relations. When disabled, an object payload in place of a relation always results in an INSERT query. To assign a value of the relation, use the foreign key instead of an object. Defaults to true.

updateByPrimaryKey

赋值给具有对象有效负载且启用了 updateNestedEntities(默认)的关系属性时,你可以控制如何处理没有主键的有效负载。默认情况下,它会被视为一个新对象,从而触发 INSERT 查询。使用 updateByPrimaryKey: false 允许将数据赋值给现有关系。默认为 true

¥When assigning to a relation property with object payload and updateNestedEntities enabled (default), you can control how a payload without a primary key is handled. By default, it is considered as a new object, resulting in an INSERT query. Use updateByPrimaryKey: false to allow assigning the data on an existing relation instead. Defaults to true.

ignoreUndefined

启用 ignoreUndefined 后,将跳过有效负载中传递的 undefined 属性。默认为 false

¥With ignoreUndefined enabled, undefined properties passed in the payload are skipped. Defaults to false.

onlyProperties

当负载中包含某些实体属性映射未表示的属性时,可以通过 onlyProperties: true 跳过这些未知属性。默认为 false

¥When you have some properties in the payload that are not represented by an entity property mapping, you can skip such unknown properties via onlyProperties: true. Defaults to false.

onlyOwnProperties

启用 onlyOwnProperties 后,将跳过多对多关系的反向属性,并将其他关系的有效负载转换为外键。默认为 false

¥With onlyOwnProperties enabled, inverse sides of to-many relations are skipped, and payloads of other relations are converted to foreign keys. Defaults to false.

convertCustomTypes

assign 会排除使用自定义类型的属性的运行时值。为了能够分配原始数据库值,你可以启用 convertCustomTypes 选项。默认为 false

¥assign excepts runtime values for properties using custom types. To be able to assign raw database values, you can enable the convertCustomTypes option. Defaults to false.

mergeObjectProperties

赋值给 JSON 属性时,值会被替换。使用 mergeObjectProperties: true 启用有效负载与现有值的深度合并。默认为 false

¥When assigning to a JSON property, the value is replaced. Use mergeObjectProperties: true to enable deep merging of the payload with the existing value. Defaults to false.

mergeEmbeddedProperties

赋给嵌入属性时,值将与现有数据深度合并。使用 mergeEmbeddedProperties: false 替换它们。默认为 true

¥When assigning to an embedded property, the values are deeply merged with the existing data. Use mergeEmbeddedProperties: false to replace them instead. Defaults to true.

merge

赋值给关系属性时,如果值为 POJO 且启用了 updateByPrimaryKey,我们会根据目标的主键检查其是否存在于身份映射中,并递归调用 assign。如果没有提供主键,或者实体不在上下文中,则该实体将被视为新实体(导致 INSERT 查询),并通过 em.create() 创建。你可以使用 merge: true 来代替 em.merge(),这意味着将不会有任何用于持久化关系的查询。默认为 false

¥When assigning to a relation property, if the value is a POJO and updateByPrimaryKey is enabled, we check if the target exists in the identity map based on its primary key and call assign on it recursively. If there is no primary key provided, or the entity is not present in the context, such an entity is considered as new (resulting in INSERT query), created via em.create(). You can use merge: true to use em.merge() instead, which means there won't be any query used for persisting the relation. Defaults to false.

schema

在启用 updateNestedEntitiesupdateByPrimaryKey(默认)的情况下,将值赋给一对多关系属性 (Collection) 时,可以使用此选项覆盖关系模式。仅在尝试在当前上下文中查找实体引用时使用这样做。如果未找到,我们将使用目标实体模式创建关系实体。系统会自动从目标实体推断其值。

¥When assigning to a to-many relation properties (Collection) with updateNestedEntities and updateByPrimaryKey enabled (default), you can use this option to override the relation schema. This is used only when trying to find the entity reference in the current context. If it is not found, we create the relation entity using the target entity schema. The value is automatically inferred from the target entity.

em

使用静态 assign() 辅助函数时,可以通过 em 选项显式传递 EntityManager 实例。仅在尝试分配关系属性时才需要这样做。当目标实体被管理或使用 em.assign() 时,系统会自动从目标实体推断其值。

¥When using the static assign() helper, you can pass the EntityManager instance explicitly via the em option. This is only needed when you try to assign a relation property. The value is automatically inferred from the target entity when it is managed, or when you use em.assign() instead.

全局配置

¥Global configuration

自 v6.2 版本起,所有 assign 选项均可全局配置:

¥Since v6.2, all of the assign options can be configured globally too:

await MikroORM.init({
// default values:
assign: {
updateNestedEntities: true,
updateByPrimaryKey: true,
mergeObjectProperties: false,
mergeEmbeddedProperties: true,
},
});

WrappedEntitywrap() 助手

¥WrappedEntity and wrap() helper

IWrappedEntity 是一个定义 ORM 提供的公共辅助方法的接口:

¥IWrappedEntity is an interface that defines public helper methods provided by the ORM:

interface IWrappedEntity<Entity> {
isInitialized(): boolean;
isTouched(): boolean;
isManaged(): boolean;
populated(populated?: boolean): void;
populate<Hint extends string = never>(
populate: AutoPath<Entity, Hint>[] | boolean,
options?: EntityLoaderOptions<Entity, Hint>,
): Promise<Loaded<Entity, Hint>>;
init<Hint extends string = never>(
populated?: boolean,
populate?: Populate<Entity, Hint>,
lockMode?: LockMode,
connectionType?: ConnectionType,
): Promise<Loaded<Entity, Hint>>;
toReference(): Ref<Entity> & LoadedReference<Loaded<Entity, AddEager<Entity>>>;
toObject(): EntityDTO<Entity>;
toObject(ignoreFields: never[]): EntityDTO<Entity>;
toObject<Ignored extends EntityKey<Entity>>(ignoreFields: Ignored[]): Omit<EntityDTO<Entity>, Ignored>;
toJSON(...args: any[]): EntityDTO<Entity>;
toPOJO(): EntityDTO<Entity>;
serialize<
Hint extends string = never,
Exclude extends string = never,
>(options?: SerializeOptions<Entity, Hint, Exclude>): EntityDTO<Loaded<Entity, Hint>>;
assign<
Naked extends FromEntityType<Entity> = FromEntityType<Entity>,
Data extends EntityData<Naked> | Partial<EntityDTO<Naked>> = EntityData<Naked> | Partial<EntityDTO<Naked>>,
>(data: Data & IsSubset<EntityData<Naked>, Data>, options?: AssignOptions): MergeSelected<Entity, Naked, keyof Data & string>;
getSchema(): string | undefined;
setSchema(schema?: string): void;
}

有两种方法可以访问这些方法。你可以扩展定义这些方法的 BaseEntity(从 @mikro-orm/core 导出),或者使用 wrap() 助手访问存在这些方法的 WrappedEntity 实例。

¥There are two ways to access those methods. You can either extend BaseEntity (exported from @mikro-orm/core), that defines those methods, or use the wrap() helper to access WrappedEntity instance, where those methods exist.

用户可以选择是否可以使用这些附加方法污染实体接口,或者他们希望保持接口清洁并使用 wrap(entity) 辅助方法来访问它们。

¥Users can choose whether they are fine with polluting the entity interface with those additional methods, or they want to keep the interface clean and use the wrap(entity) helper method instead to access them.

自 v4 以来,你应该将 wrap(entity) 用于 SQL 函数,因为带有字符串值的 WrappedEntity 将自动加引号。它仅包含公共方法(initassignisInitialized,...),如果你想访问内部属性(如 __meta__em),则需要通过 wrap(entity, true) 明确请求助手。

¥Since v4, wrap(entity) no longer returns the entity, now the WrappedEntity instance is being returned. It contains only public methods (init, assign, isInitialized, ...), if you want to access internal properties like __meta or __em, you need to explicitly ask for the helper via wrap(entity, true).

import { BaseEntity } from '@mikro-orm/core';

@Entity()
export class Book extends BaseEntity { ... }

然后你可以直接使用这些方法:

¥Then you can work with those methods directly:

book.meta = { foo: 1, bar: 2 };
book.assign({ meta: { foo: 3 } }, { mergeObjectProperties: true });
console.log(book.meta); // { foo: 3, bar: 2 }

访问内部前缀属性

¥Accessing internal prefixed properties

以前可以从 wrap() 助手访问内部属性,如 __meta__em。现在要访问它们,你需要使用 wrap 的第二个参数:

¥Previously it was possible to access internal properties like __meta or __em from the wrap() helper. Now to access them, you need to use second parameter of wrap:

@Entity()
export class Author { ... }

console.log(wrap(author, true).__meta);