added more docs

This commit is contained in:
Umed Khudoiberdiev 2016-04-12 13:55:52 +05:00
parent e813d8d7d4
commit 1163df7c44
9 changed files with 426 additions and 31 deletions

View File

@ -1,3 +1,70 @@
## Entity Manager
TBD
EntityManager provides functionality to work with all your entities in a single connection.
Its like a Repository, but works with all entities.
There are several useful methods of the EntityManager:
* `getRepository(entity: Entity): Repository<Entity>`
Gets the repository of the given entity.
* `hasId(entity: Entity): boolean`
Sometimes you want to check if your entity already has id or not. This maybe useful in situations when you want to
check if your object is new or not.
* `create(entityClass: Function, plainJsObject?: Object): Entity`
Creates a new empty instance of entity. If `plainJsObject` is given then new entity will be created and all properties
from the `plainJsObject` that can be mapped to this entity will be copied to the new entity.
* `createMany(entityClass: Function, plainJsObjects: Object[]): Entity[]`
Creates multiple new entities based on array of plain javascript objects. Properties for each plain javascript object
will be copied to newly created entities if they should exist there.
* `initialize(entityClass: Function, object: Object): Promise<Entity>`
Creates a new entity from the given plan javascript object. If entity already exist in the database, then
it loads it (and everything related to it), replaces all values with the new ones from the given object
and returns this new entity. This new entity is actually a loaded from the db entity with all properties
replaced from the new object.
* `persist(entity: Entity): Promise<Entity>`
Persists (saves) a given entity in the database. If entity does not exist in the database then it inserts it,
else if entity already exist in the database then it updates it.
* `remove(entity: Entity): Promise<Entity>`
Removes a given entity from the database.
* `find(entityClass: Function, conditions?: Object, options?: FindOptions): Promise<Entity[]>`
Finds entities that match given conditions or given find options.
* `findOne(entityClass: Function, conditions?: Object, options?: FindOptions): Promise<Entity>`
Finds the first entity that match given conditions or given find options.
* `findById(entityClass: Function, id: any, options?: FindOptions): Promise<Entity>`
Finds entity with a given entity id.
* `findAndCount(entityClass: Function, conditions?: Object, options?: FindOptions): Promise<{ items: Entity[], count: number }>`
Finds entities that match given conditions or given find options plus gets a overall count of this items (for
pagination purposes).
* `createQueryBuilder(entityClass: Function, alias: string): QueryBuilder<Entity>`
Creates a new query builder that can be used to build a sql query and get the results of the executed query. You can
learn more about query builder [here](docs/query-builder.md).
* `query(sql: string): Promise<any>`
Executes raw SQL query.
* `transaction(runInTransaction: () => Promise<any>): Promise<any>`
Executes everything in the given function in a single transaction.

View File

@ -1,3 +1,53 @@
## Naming strategies
TBD
NamingStrategy is an interface that defines how auto-generated names for such things like table name, or table column
will be named.
#### Interface
By default `DefaultNamingStrategy` is used.
You can implement your own strategies by creating a new class and implementing `NamingStrategy` interface.
There are three methods you need to implement:
* `tableName(className: string): string`
Gets table name from the class name. `DefaultNamingStrategy` transforms className to a sneak-case.
* `columnName(className: string): string`
Gets column name from the class property. `DefaultNamingStrategy` transforms className to a camelCase.
* `relationName(className: string): string`
Gets relation name from the class property. `DefaultNamingStrategy` transforms className to a camelCase.
#### Example
Lets create a simple naming strategy that will add a "_" before names of tables, columns and relations:
```typescript
export class MyNamingStrategy implements NamingStrategy {
tableName(className: string) {
return "_" + className;
}
columnName(propertyName: string) {
return "_" + propertyName;
}
relationName(propertyName: string) {
return "_" + propertyName;
}
}
```
We also need to specify our new naming strategy on connection manager creation:
```typescript
let connectionManager = new ConnectionManager(new MyNamingStrategy());
```
Now try to run and generate your database schema. New naming strategy should be used.

View File

@ -1,3 +1,148 @@
## Query Builder
TBD
Query Builder allows to build a SQL queries and loads your entities from the database based on the built
query.
#### create a query builder
To create a query builder use [EntityManager](entity-manager.md) or [Repository](repository.md):
```typescript
let repository = connection.getRepository(Photo);
let queryBuilder = repository.createQueryBuilder("photo");
```
First argument of the `createQueryBuilder` is an **alias** of the object you are selecting. Basically its the same
alias as **sql aliases**. You'll need it to make further selections.
#### build a query
There are bunch of methods to help to create a query using QueryBuilder:
* `queryBuilder.select(selection: string)`
* `queryBuilder.select(selection: string[])`
* `queryBuilder.select(...selection: string[])`
* `queryBuilder.addSelect(selection: string)`
* `queryBuilder.addSelect(selection: string[])`
* `queryBuilder.addSelect(...selection: string[])`
Sets / adds given selections to the sql's `SELECT` group. Here you usually select aliases.
* `queryBuilder.where(condition: string, parameters?: { [key: string]: any })`
* `queryBuilder.andWhere(condition: string, parameters?: { [key: string]: any })`
* `queryBuilder.orWhere(condition: string, parameters?: { [key: string]: any })`
Adds sql's `WHERE` condition. For `andWhere` method it will also add "AND" before the condition and for the
`orWhere` method it will also add "OR" before the condition. `parameters` is the array of parameters to be escaped,
just a convenient shortcut for the `QueryBuilder#addParameters` method.
* `queryBuilder.having(condition: string, parameters?: { [key: string]: any })`
* `queryBuilder.andHaving(condition: string, parameters?: { [key: string]: any })`
* `queryBuilder.orHaving(condition: string, parameters?: { [key: string]: any })`
Adds sql's `HAVING` condition. For `andHaving` method it will also add "AND" before the condition and for the
`orHaving` method it will also add "OR" before the condition. `parameters` is the array of parameters to be escaped,
just a convenient shortcut for the `QueryBuilder#addParameters` method.
* `queryBuilder.orderBy(sort: string, order: "ASC"|"DESC" = "ASC")`
* `queryBuilder.addOrderBy(sort: string, order: "ASC"|"DESC" = "ASC")`
Sets / adds sql's `ORDER BY` condition.
* `queryBuilder.groupBy(groupBy: string)`
* `queryBuilder.addGroupBy(groupBy: string)`
Sets / adds sql's `GROUP BY` condition.
* `queryBuilder.setLimit(limit: number)`
Set's sql's `LIMIT`. If you are implementing pagination, LIMIT is not what you want in most of cases, because
of how it works. It can work in some trivial queries, but in complex queries it fails. If you want to use pagination
then use `QueryBuilder#setMaxResults` instead.
* `queryBuilder.setOffset(limit: number)`
Set's sql's `OFFSET`. If you are implementing pagination, OFFSET is not what you want in most of cases, because
of how it works. It can work in some trivial queries, but in complex queries it fails. If you want to use pagination
then use `QueryBuilder#setFirstResult` instead.
* `queryBuilder.setFirstResult(firstResult: number)`
Real "LIMIT" to use in queries for the pagination purposes. Use it if you want pagination.
* `queryBuilder.setMaxResults(maxResults: number)`
Real "LIMIT" to use in queries for the pagination purposes. Use it if you want pagination.
* `setParameter(key: string, value: any)`
* `setParameters(parameters: Object)`
* `addParameters(parameters: Object)`
Sets a single parameter value / set an object of parameters / add all parameters from the object.
Parameters are escaped values to be used in your query, like in WHERE or HAVING expressions.
* `innerJoin(property: string, alias: string, conditionType?: "on"|"with", condition?: string)`
Adds sql's `INNER JOIN` selection on a given **property** of the object, and gives an **alias** to this selection.
You can also specify a SQL JOIN condition type and condition.
* `leftJoin(property: string, alias: string, conditionType?: "on"|"with", condition?: string)`
Adds sql's `LEFT JOIN` selection on a given **property** of the object, and gives an **alias** to this selection.
You can also specify a SQL JOIN condition type and condition.
* `innerJoinAndSelect(property: string, alias: string, conditionType?: "on"|"with", condition?: string)`
Adds sql's `INNER JOIN` selection on a given **property** of the object, and gives an **alias** to this selection.
You can also specify a SQL JOIN condition type and condition. Also adds an alias into `SELECT`.
* `leftJoinAndSelect(property: string, alias: string, conditionType?: "on"|"with", condition?: string)`
Adds sql's `LEFT JOIN` selection on a given **property** of the object, and gives an **alias** to this selection.
You can also specify a SQL JOIN condition type and condition. Also adds an alias into `SELECT`.
These were methods to help you to create a query. Here is example how to use some of them:
```typescript
let photoRepository = connection.getRepository(Photo);
photoRepository
.createQueryBuilder("photo") // first argument is an alias. Alias is what you are selecting - photos. You must specify it.
.innerJoinAndSelect("photo.metadata")
.leftJoinAndSelect("photo.albums")
.where("photo.isPublished=true")
.andWhere("photo.name=:photoName OR photo.name=:bearName")
.orderBy("photo.id", "DESC")
.setFirstResult(5)
.setMaxResults(10)
.setParameters({ photoName: "My", beaName: "Mishka" });
```
There is also a `getSql()` method that can be used to take a look what query QueryBuilder built for you
and debug your queries.
#### get results
There are several methods to help you to get results:
* `getResults()`
Gets all results of the query and transforms results to the your entities.
* `getSingleResult()`
Gets the first result of the query and transforms it to the entity.
* `getScalarResults()`
Gets all results of the query in a raw - it does not transform them to entities.
* `getSingleScalarResult()`
Gets the first result of the query in a raw - it does not transform it to entity.
* `getCount()`
Gets the count - number of rows returned by this selection. Can be used for pagination.
There is also a `execute()` method that simply executes your query and returns you a plain result returned
by the database driver.

View File

@ -4,11 +4,11 @@ One of the best sides of TypeORM is relations support. You can build relations b
easily without thinking of database schema and foreign keys. Relations are created using special decorators
on specific fields of entity objects.
* [@OneToOne and @OneToOneInverse decorators](#@OneToOne-and-@OneToOneInverse-decorators)
* [@OneToMany and @ManyToOne decorators](#@OneToMany-and-@ManyToOne-decorators)
* [@ManyToMany and @ManyToManyInverse decorators](#@ManyToMany-and-@ManyToManyInverse-decorators)
* [@OneToOne and @OneToOneInverse decorators](#onetoone-and-onetooneinverse-decorators)
* [@OneToMany and @ManyToOne decorators](#onetomany-and-manytoone-decorators)
* [@ManyToMany and @ManyToManyInverse decorators](#manytomany-and-manytomanyinverse-decorators)
* [Self referencing](#self-referencing)
* [Relational decorators options](#RelationOptions)
* [Relational decorators options](#relational-decorators-options)
### @OneToOne and @OneToOneInverse decorators

View File

@ -1,6 +1,8 @@
## Repository
For each entity you have there is a Repository for it. Repository provides functionality to work with your entity.
For each entity you have there is a Repository for it.
Repository provides functionality to work with your entity.
Repository works the same way as EntityManager, but is more specific for concrete entity class.
There are several useful methods of the Repository:
* `hasId(entity: Entity): boolean`
@ -34,21 +36,11 @@ else if entity already exist in the database then it updates it.
Removes a given entity from the database.
* ```typescript
find(): Promise<Entity[]>
find(conditions: Object): Promise<Entity[]>;
find(options: FindOptions): Promise<Entity[]>;
find(conditions: Object, options: FindOptions): Promise<Entity[]>;
```
* `find(conditions?: Object, options?: FindOptions): Promise<Entity[]>`
Finds entities that match given conditions or given find options.
* ```typescript
findOne(): Promise<Entity>;
findOne(conditions: Object): Promise<Entity>;
findOne(options: FindOptions): Promise<Entity>;
findOne(conditions: Object, options: FindOptions): Promise<Entity>;
```
* `findOne(conditions?: Object, options?: FindOptions): Promise<Entity>`
Finds the first entity that match given conditions or given find options.

View File

@ -1,3 +1,136 @@
## Subscribers and Entity Listeners
TBD
You can listen to events in the ORM. There two concepts you can use:
* [Subscribers](#subscribers)
* [Entity Listeners](#entity-listeners)
### Subscribers
First you need to create a new subscriber class and implement `OrmSubscriber` interface:
```typescript
import {OrmEventSubscriber} from "typeorm/decorator/listeners"
import {OrmSubscriber} from "typeorm/subscriber/OrmSubscriber";
import {UpdateEvent} from "typeorm/subscriber/event/UpdateEvent";
import {RemoveEvent} from "typeorm/subscriber/event/RemoveEvent";
import {InsertEvent} from "typeorm/subscriber/event/InsertEvent";
@OrmEventSubscriber()
export class MySubscriber implements OrmSubscriber<any> {
/**
* Called after entity insertion.
*/
beforeInsert(event: InsertEvent<any>) {
console.log(`BEFORE ENTITY INSERTED: `, event.entity);
}
/**
* Called after entity insertion.
*/
beforeUpdate(event: UpdateEvent<any>) {
console.log(`BEFORE ENTITY UPDATED: `, event.entity);
}
/**
* Called after entity insertion.
*/
beforeRemove(event: RemoveEvent<any>) {
console.log(`BEFORE ENTITY WITH ID ${event.entityId} REMOVED: `, event.entity);
}
/**
* Called after entity insertion.
*/
afterInsert(event: InsertEvent<any>) {
console.log(`AFTER ENTITY INSERTED: `, event.entity);
}
/**
* Called after entity insertion.
*/
afterUpdate(event: UpdateEvent<any>) {
console.log(`AFTER ENTITY UPDATED: `, event.entity);
}
/**
* Called after entity insertion.
*/
afterRemove(event: RemoveEvent<any>) {
console.log(`AFTER ENTITY WITH ID ${event.entityId} REMOVED: `, event.entity);
}
/**
* Called after entity is loaded.
*/
afterLoad(entity: any) {
console.log(`AFTER ENTITY LOADED: `, entity);
}
}
```
To register a subscriber you need to register it:
```typescript
connectionManager.importSubscribers([MySubscriber]);
```
### Entity Listeners
You can also use listeners in your entities. Such listeners can be convenient for a trivial operations.
```typescript
import {Table} from "typeorm/decorator/tables";
import {AfterLoad} from "typeorm/decorator/listeners/AfterLoad";
import {AfterInsert} from "typeorm/decorator/listeners/AfterInsert";
import {BeforeInsert} from "typeorm/decorator/listeners/BeforeInsert";
import {BeforeUpdate} from "typeorm/decorator/listeners/BeforeUpdate";
import {AfterUpdate} from "typeorm/decorator/listeners/AfterUpdate";
import {BeforeRemove} from "typeorm/decorator/listeners/BeforeRemove";
import {AfterRemove} from "typeorm/decorator/listeners/AfterRemove";
@Table("posts")
export class Post {
// ... columns ...
@AfterLoad()
generateRandomNumbers() {
console.log(`event: Post entity has been loaded and callback executed`);
}
@BeforeInsert()
doSomethingBeforeInsertion() {
console.log("event: Post entity will be inserted so soon...");
}
@AfterInsert()
doSomethingAfterInsertion() {
console.log("event: Post entity has been inserted and callback executed");
}
@BeforeUpdate()
doSomethingBeforeUpdate() {
console.log("event: Post entity will be updated so soon...");
}
@AfterUpdate()
doSomethingAfterUpdate() {
console.log("event: Post entity has been updated and callback executed");
}
@BeforeRemove()
doSomethingBeforeRemove() {
console.log("event: Post entity will be removed so soon...");
}
@AfterRemove()
doSomethingAfterRemove() {
console.log("event: Post entity has been removed and callback executed");
}
}
```

View File

@ -6,6 +6,7 @@ import {ConnectionNotFoundError} from "./error/ConnectionNotFoundError";
import {EntityMetadataBuilder} from "../metadata-builder/EntityMetadataBuilder";
import {importClassesFromDirectories} from "../util/DirectoryExportedClassesLoader";
import {ConnectionOptions} from "tls";
import {NamingStrategy} from "../naming-strategy/NamingStrategy";
/**
* Connection manager holds all connections made to the databases and providers helper management functions
@ -27,8 +28,11 @@ export class ConnectionManager {
// Constructor
// -------------------------------------------------------------------------
constructor() {
this.entityMetadataBuilder = new EntityMetadataBuilder(defaultMetadataStorage, new DefaultNamingStrategy());
constructor(namingStrategy?: NamingStrategy) {
if (!namingStrategy)
namingStrategy = new DefaultNamingStrategy();
this.entityMetadataBuilder = new EntityMetadataBuilder(defaultMetadataStorage, namingStrategy);
}
// -------------------------------------------------------------------------

View File

@ -125,7 +125,7 @@ export class QueryBuilder<Entity> {
}
from(tableName: string, alias: string): this;
from(entity: Function, alias?: string): this;
from(entity: Function, alias: string): this;
from(entityOrTableName: Function|string, alias: string): this {
if (entityOrTableName instanceof Function) {
const aliasObj = new Alias(alias);
@ -213,18 +213,21 @@ export class QueryBuilder<Entity> {
return this;
}
having(having: string): this {
having(having: string, parameters?: { [key: string]: any }): this {
this.havings.push({ type: "simple", condition: having });
if (parameters) this.addParameters(parameters);
return this;
}
andHaving(having: string): this {
andHaving(having: string, parameters?: { [key: string]: any }): this {
this.havings.push({ type: "and", condition: having });
if (parameters) this.addParameters(parameters);
return this;
}
orHaving(having: string): this {
orHaving(having: string, parameters?: { [key: string]: any }): this {
this.havings.push({ type: "or", condition: having });
if (parameters) this.addParameters(parameters);
return this;
}
@ -264,7 +267,8 @@ export class QueryBuilder<Entity> {
}
setParameters(parameters: { [key: string]: any }): this {
this.parameters = parameters;
this.parameters = {};
Object.keys(parameters).forEach(key => this.parameters[key] = parameters[key]);
return this;
}

View File

@ -32,7 +32,7 @@ export class EntityManager {
* Checks if entity has an id.
*/
hasId(entity: Function): boolean {
return this.getRepository(entity).hasId(entity);
return this.getRepository(entity.constructor).hasId(entity);
}
/**
@ -78,14 +78,14 @@ export class EntityManager {
* Persists (saves) a given entity in the database.
*/
persist<Entity>(entity: Entity): Promise<Entity> {
return this.getRepository(<any> entity).persist(entity);
return this.getRepository(<any> entity.constructor).persist(entity);
}
/**
* Removes a given entity from the database.
*/
remove<Entity>(entity: Entity) {
return this.getRepository(<any> entity).remove(entity);
return this.getRepository(<any> entity.constructor).remove(entity);
}
/**