# Source: https://mikro-orm.io/docs/collections.md

# Collections

`OneToMany` and `ManyToMany` properties are stored in a `Collection` wrapper.

## Working with collections[​](#working-with-collections "Direct link to Working with collections")

The `Collection` class implements iterator, so we can use `for of` loop to iterate through it.

Another way to access collection items is to use bracket syntax like when we access array items. Keep in mind that this approach will not check if the collection is initialized, while using `get` method will throw error in this case.

> Note that array access in `Collection` is available only for reading already loaded items, we cannot add new items to `Collection` this way.

To get all entities stored in a `Collection`, we can use `getItems()` method. It will throw in case the `Collection` is not initialized. If we want to disable this validation, we can use `getItems(false)`. This will give us the entity instances managed by the identity map.

Alternatively we can use `toArray()` which will serialize the `Collection` to an array of DTOs. Modifying those will have no effect on the actual entity instances.

```
const author = em.findOne(Author, '...', { populate: ['books'] }); // populating books collection

// Or we could lazy load books collection later via `load()` method.
// Unlike `init()` it will check the state and do nothing if the collection is already initialized.
// await author.books.load();

for (const book of author.books) {
  console.log(book.title); // initialized
  console.log(book.author.isInitialized()); // true
  console.log(book.author.id);
  console.log(book.author.name); // Jon Snow
  console.log(book.publisher); // just reference
  console.log(book.publisher.isInitialized()); // false
  console.log(book.publisher.id);
  console.log(book.publisher.name); // undefined
}

// collection needs to be initialized before we can work with it
author.books.add(book);
console.log(author.books.contains(book)); // true
console.log(author.books.exists(item => item === book)); // true
console.log(author.books.find(item => item === book)); // book
console.log(author.books.map(item => item.title)); // array of book titles
console.log(author.books.filter(item => item.title.startsWith('Foo'))); // array of books matching the callback
author.books.remove(book);
console.log(author.books.contains(book)); // false
author.books.add(book);
console.log(author.books.count()); // 1
console.log(author.books.slice(0, 1)); // Book[]
console.log(author.books.slice()); // Book[]
console.log(author.books.slice().length); // 1
author.books.removeAll();
console.log(author.books.isEmpty()); // true
console.log(author.books.contains(book)); // false
console.log(author.books.count()); // 0
console.log(author.books.getItems()); // Book[]
console.log(author.books.getIdentifiers()); // array of string | number
console.log(author.books.getIdentifiers('_id')); // array of ObjectId

// array access works as well
console.log(author.books[1]); // Book
console.log(author.books[12345]); // undefined, even if the collection is not initialized

// getting array of the items
console.log(author.books.getItems()); // Book[]

// serializing the collection
console.log(author.books.toArray()); // EntityDTO<Book>[]

const author = em.findOne(Author, '...'); // books collection has not been populated
const count = await author.books.loadCount(); // gets the count of collection items from database instead of counting loaded items
console.log(author.books.getItems()); // throws because the collection has not been initialized
// initialize collection if not already loaded and return its items as array
console.log(await author.books.loadItems()); // Book[]
```

### Removing items from collection[​](#removing-items-from-collection "Direct link to Removing items from collection")

Removing items from a collection does not necessarily imply deleting the target entity, it means we are disconnecting the relation - removing items from collection, not removing entities from database - `Collection.remove()` is not the same as `em.remove()`. When you use `em.assign()` to update entities you can also remove/disconnect entities from a collection, they do not get automatically removed from the database. If we want to delete the entity by removing it from collection, we need to enable `orphanRemoval: true`, which tells the ORM we don't want orphaned entities to exist, so we know those should be removed. Also check the documentation on [Orphan Removal](https://mikro-orm.io/docs/cascading.md#orphan-removal)

## OneToMany Collections[​](#onetomany-collections "Direct link to OneToMany Collections")

`OneToMany` collections are inverse side of `ManyToOne` references, to which they need to point via `fk` attribute:

```
@Entity()
export class Book {

  @PrimaryKey()
  _id!: ObjectId;

  @ManyToOne()
  author!: Author;

}

@Entity()
export class Author {

  @PrimaryKey()
  _id!: ObjectId;

  @OneToMany(() => Book, book => book.author)
  books1 = new Collection<Book>(this);

  // or via options object
  @OneToMany({ entity: () => Book, mappedBy: 'author' })
  books2 = new Collection<Book>(this);

}
```

## ManyToMany Collections[​](#manytomany-collections "Direct link to ManyToMany Collections")

For ManyToMany, SQL drivers use pivot table that holds reference to both entities.

As opposed to them, with MongoDB we do not need to have join tables for `ManyToMany` relations. All references are stored as an array of `ObjectId`s on owning entity.

### Unidirectional[​](#unidirectional "Direct link to Unidirectional")

Unidirectional `ManyToMany` relations are defined only on one side, if we define only `entity` attribute, then it will be considered the owning side:

```
@ManyToMany(() => Book)
books1 = new Collection<Book>(this);

// or mark it as owner explicitly via options object
@ManyToMany({ entity: () => Book, owner: true })
books2 = new Collection<Book>(this);
```

### Bidirectional[​](#bidirectional "Direct link to Bidirectional")

Bidirectional `ManyToMany` relations are defined on both sides, while one is owning side (where references are store), marked by `inversedBy` attribute pointing to the inverse side:

```
@ManyToMany(() => BookTag, tag => tag.books, { owner: true })
tags = new Collection<BookTag>(this);

// or via options object
@ManyToMany({ entity: () => BookTag, inversedBy: 'books' })
tags = new Collection<BookTag>(this);
```

And on the inversed side we define it with `mappedBy` attribute pointing back to the owner:

```
@ManyToMany(() => Book, book => book.tags)
books = new Collection<Book>(this);

// or via options object
@ManyToMany({ entity: () => Book, mappedBy: 'tags' })
books = new Collection<Book>(this);
```

### Custom pivot table entity[​](#custom-pivot-table-entity "Direct link to Custom pivot table entity")

By default, a generated pivot table entity is used under the hood to represent the pivot table. Since v5.1 we can provide our own implementation via `pivotEntity` option.

The pivot table entity needs to have exactly two many-to-one properties, where first one needs to point to the owning entity and the second to the target entity of the many-to-many relation.

```
@Entity()
export class Order {

  @ManyToMany({ entity: () => Product, pivotEntity: () => OrderItem })
  products = new Collection<Product>(this);

}
```

For bidirectional M<!-- -->:N<!-- --> relations, it is enough to specify the `pivotEntity` option only on the owning side. We still need to link the sides via `inversedBy` or `mappedBy` option.

```
@Entity()
export class Product {

  @ManyToMany({ entity: () => Order, mappedBy: o => o.products })
  orders = new Collection<Order>(this);

}
```

If we want to add new items to such M<!-- -->:N<!-- --> collection, we need to have all non-FK properties to define a database level default value.

```
@Entity()
export class OrderItem {

  @ManyToOne({ primary: true })
  order: Order;

  @ManyToOne({ primary: true })
  product: Product;

  @Property({ default: 1 })
  amount!: number;

}
```

Alternatively, we can work with the pivot entity directly:

```
// create new item
const item = em.create(OrderItem, {
  order: 123,
  product: 321,
  amount: 999,
});
await em.persist(item).flush();

// or remove an item via delete query
const em.nativeDelete(OrderItem, { order: 123, product: 321 });
```

We can as well define the 1<!-- -->:m<!-- --> properties targeting the pivot entity as in the previous example, and use that for modifying the collection, while using the M<!-- -->:N<!-- --> property for easier reading and filtering purposes.

### Forcing fixed order of collection items[​](#forcing-fixed-order-of-collection-items "Direct link to Forcing fixed order of collection items")

> Since v3 many to many collections does not require having auto increment primary key, that was used to ensure fixed order of collection items.

To preserve fixed order of collections, we can use `fixedOrder: true` attribute, which will start ordering by `id` column. Schema generator will convert the pivot table to have auto increment primary key `id`. We can also change the order column name via `fixedOrderColumn: 'order'`.

We can also specify default ordering via `orderBy: { ... }` attribute. This will be used when we fully populate the collection including its items, as it orders by the referenced entity properties instead of pivot table columns (which `fixedOrderColumn` is). On the other hand, `fixedOrder` is used to maintain the insert order of items instead of ordering by some property.

## Populating references[​](#populating-references "Direct link to Populating references")

Sometimes we might want to know only what items are part of a collection, and we don't care about the values of those items. For this, we can populate the collection only with references:

```
const book1 = await em.findOne(Book, 1, { populate: ['tags:ref'] });
console.log(book1.tags.isInitialized()); // true
console.log(wrap(book1.tags[0]).isInitialized()); // false

// or alternatively use `init({ ref: true })`
const book2 = await em.findOne(Book, 1);
await book2.tags.init({ ref: true });
console.log(book2.tags.isInitialized()); // true
console.log(wrap(book2.tags[0]).isInitialized()); // false
```

## Propagation of Collection's add() and remove() operations[​](#propagation-of-collections-add-and-remove-operations "Direct link to Propagation of Collection's add() and remove() operations")

When we use one of `Collection.add()` method, the item is added to given collection, and this action is also propagated to its counterpart.

```
// one to many
const author = new Author(...);
const book = new Book(...);

author.books.add(book);
console.log(book.author); // author will be set thanks to the propagation
```

For M<!-- -->:N<!-- --> this works in both ways, either from owning side, or from inverse side.

```
// many to many works both from owning side and from inverse side
const book = new Book(...);
const tag = new BookTag(...);

book.tags.add(tag);
console.log(tag.books.contains(book)); // true

tag.books.add(book);
console.log(book.tags.contains(tag)); // true
```

> Since v5.2.2 propagation of adding new items to inverse side M<!-- -->:N<!-- --> relation also works if the owning collection is not initialized. For propagation of remove operation, both sides still have to be initialized.

> Although this propagation works also for M<!-- -->:N<!-- --> inverse side, we should always use owning side to manipulate the collection.

Same applies for `Collection.remove()`.

## Filtering and ordering of collection items[​](#filtering-and-ordering-of-collection-items "Direct link to Filtering and ordering of collection items")

When initializing collection items via `collection.init()`, you can filter the collection as well as order its items:

```
await book.tags.init({
  where: { active: true },
  orderBy: { name: QueryOrder.DESC },
});
```

> You should never modify partially loaded collections.

## Declarative partial loading[​](#declarative-partial-loading "Direct link to Declarative partial loading")

Collections can also represent only a subset of the target entities:

```
@Entity()
class Author {

  @OneToMany(() => Book, b => b.author)
  books = new Collection<Book>(this);

  @OneToMany(() => Book, b => b.author, { where: { favorite: true } })
  favoriteBooks = new Collection<Book>(this);

}
```

This works also for M<!-- -->:N<!-- --> relations. Note that if you want to declare more relations mapping to the same pivot table, you need to explicitly specify its name (or use the same pivot entity):

```
@Entity()
class Book {

  @ManyToMany(() => BookTag)
  tags = new Collection<BookTag>(this);

  @ManyToMany({
    entity: () => BookTag, 
    pivotTable: 'book_tags',
    where: { popular: true },
  })
  popularTags = new Collection<BookTag>(this);

}
```

## Filtering Collections[​](#filtering-collections "Direct link to Filtering Collections")

Collections have a `matching` method that allows to slice parts of data from a collection. By default, it will return the list of entities based on the query. We can use the `store` boolean parameter to save this list into the collection items - this will mark the collection as `readonly`, methods like `add` or `remove` will throw.

```
const a = await em.findOneOrFail(Author, 1);

// only loading the list of items
const books = await a.books.matching({ limit: 3, offset: 10, orderBy: { title: 'asc' } });
console.log(books); // [Book, Book, Book]
console.log(a.books.isInitialized()); // false

// storing the items in collection
const tags = await books[0].tags.matching({
  limit: 3,
  offset: 5,
  orderBy: { name: 'asc' },
  store: true,
});
console.log(tags); // [BookTag, BookTag, BookTag]
console.log(books[0].tags.isInitialized()); // true
console.log(books[0].tags.getItems()); // [BookTag, BookTag, BookTag]
```

## Mapping Collection items[​](#mapping-collection-items "Direct link to Mapping Collection items")

The `Collection` class offers several handy helper methods to filter, map, or convert the collection items.

### `indexBy`[​](#indexby "Direct link to indexby")

When you want to convert the collection to a simple key-value dictionary, use the `indexBy()` method:

```
// given `user.settings` is `Collection<Option>`
const settingsDictionary = user.settings.indexBy('key');
// `settingsDictionary` is `Record<string, Option>`
```

The second argument lets you map to property values instead of the target entity:

```
const settingsDictionary = user.settings.indexBy('key', 'value');
// `settingsDictionary` is `Record<string, string>`
```