Skip to main content
Version: 6.4

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 中提供其 PK,以及首先将该实体加载到当前上下文中。

¥Since v5, assign allows updating deep entity graph by default. To update existing entity, we need to provide its PK in the data, as well as to 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 中没有实体 PK,我们也可以使用 updateByPrimaryKey: false

¥If we want to always update the entity, even without the entity PK being present in data, we 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 });

否则,没有 PK 的实体数据将被视为新实体,并将触发插入查询:

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

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

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

同样适用于我们不先将子实体加载到上下文中的情况,例如当我们尝试分配给未填充的关系时。即使我们提供其 PK,它也将被视为新对象并触发插入查询。

¥Same applies to the case when we do not load the child entity first into the context, e.g. when we try to assign to a relation that was not populated. Even if we provide its PK, it will be considered as 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, we can either pass complete array of all items, or just a single item - in such 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);

全局配置

¥Global configuration

从 v6.2 开始,你还可以配置 assign 助手的全局工作方式:

¥Since v6.2, you can also configure how the assign helper works globally:

await MikroORM.init({
// default values:
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);