Skip to content

Commit

Permalink
Merge pull request #7317 from abpframework/getqueryable-doc-update
Browse files Browse the repository at this point in the history
Repository document: Use GetQueryableAsync.
  • Loading branch information
hikalkan authored Jan 18, 2021
2 parents 0ac632f + 250f997 commit 0f5685a
Showing 1 changed file with 135 additions and 41 deletions.
176 changes: 135 additions & 41 deletions docs/en/Repositories.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,84 +13,178 @@ ABP can provide a **default generic repository** for each aggregate root or enti
**Example usage of a default generic repository:**

````C#
public class PersonAppService : ApplicationService
{
private readonly IRepository<Person, Guid> _personRepository;
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;

public PersonAppService(IRepository<Person, Guid> personRepository)
namespace Demo
{
public class PersonAppService : ApplicationService
{
_personRepository = personRepository;
}
private readonly IRepository<Person, Guid> _personRepository;

public async Task Create(CreatePersonDto input)
{
var person = new Person { Name = input.Name, Age = input.Age };
public PersonAppService(IRepository<Person, Guid> personRepository)
{
_personRepository = personRepository;
}

await _personRepository.InsertAsync(person);
}
public async Task CreateAsync(CreatePersonDto input)
{
var person = new Person(input.Name);

public List<PersonDto> GetList(string nameFilter)
{
var people = _personRepository
.Where(p => p.Name.Contains(nameFilter))
.ToList();
await _personRepository.InsertAsync(person);
}

return people
.Select(p => new PersonDto {Id = p.Id, Name = p.Name, Age = p.Age})
.ToList();
public async Task<int> GetCountAsync(string filter)
{
return await _personRepository.CountAsync(p => p.Name.Contains(filter));
}
}
}
````

> See the "*IQueryable & Async Operations*" section below to understand how you can use **async extension methods**, like `ToListAsync()` (which is strongly suggested) instead of `ToList()`.
In this example;

* `PersonAppService` simply injects `IRepository<Person, Guid>` in it's constructor.
* `Create` method uses `InsertAsync` to save a newly created entity.
* `GetList` method uses the standard LINQ `Where` and `ToList` methods to filter and get a list of people from the data source.
* `CreateAsync` method uses `InsertAsync` to save the new entity.
* `GetCountAsync` method gets a filtered count of all people in the database.

> The example above uses hand-made mapping between [entities](Entities.md) and [DTO](Data-Transfer-Objects.md)s. See [object to object mapping document](Object-To-Object-Mapping.md) for an automatic way of mapping.
### Standard Repository Methods

Generic Repositories provides some standard CRUD features out of the box:

* Provides `Insert` method to save a new entity.
* `GetAsync`: Returns a single entity by its `Id` or a predicate (lambda expression).
* Throws `EntityNotFoundException` if the requested entity was not found.
* Throws `InvalidOperationException` if there are multiple entities with given predicate.
* `FindAsync`: Returns a single entity by its `Id` or a predicate (lambda expression).
* Returns `null` if the requested entity was not found.
* Throws `InvalidOperationException` if there are multiple entities with given predicate.
* `InsertAsync`: Inserts a new entity to the database.
* `UpdateAsync`: Updates an existing entity in the database.
* `DeleteAsync`: Deletes the given entity from database.
* This method has an overload that takes a predicate (lambda expression) to delete multiple entities satisfies the given condition.
* `GetListAsync`: Returns the list of all entities in the database.
* `GetPagedListAsync`: Returns a limited list of entities. Gets `skipCount`, `maxResultCount` and `sorting` parameters.
* `GetCountAsync`: Gets count of all entities in the database.

There are overloads of these methods.

* Provides `Update` and `Delete` methods to update or delete an entity by entity object or it's id.
* Provides `Delete` method to delete multiple entities by a filter.
* Implements `IQueryable<TEntity>`, so you can use LINQ and extension methods like `FirstOrDefault`, `Where`, `OrderBy`, `ToList` and so on...

### Basic Repositories
### Querying / LINQ over the Repositories

Standard `IRepository<TEntity, TKey>` interface extends standard `IQueryable<TEntity>` and you can freely query using standard LINQ methods. However, some ORM providers or database systems may not support standard `IQueryable` interface.
Repositories provide the `GetQueryableAsync()` method that returns an `IQueryable<TEntity>` object. You can use this object to perform LINQ queries on the entities in the database.

ABP provides `IBasicRepository<TEntity, TPrimaryKey>` and `IBasicRepository<TEntity>` interfaces to support such scenarios. You can extend these interfaces (and optionally derive from `BasicRepositoryBase`) to create custom repositories for your entities.
**Example: Use LINQ with the repositories**

Depending on `IBasicRepository` but not depending on `IRepository` has an advantage to make possible to work with all data sources even if they don't support `IQueryable`. But major vendors, like Entity Framework, NHibernate or MongoDb already support `IQueryable`.
````csharp
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;

So, working with `IRepository` is the **suggested** way for typical applications. But reusable module developers may consider `IBasicRepository` to support a wider range of data sources.
namespace Demo
{
public class PersonAppService : ApplicationService
{
private readonly IRepository<Person, Guid> _personRepository;

### Read Only Repositories
public PersonAppService(IRepository<Person, Guid> personRepository)
{
_personRepository = personRepository;
}

There are also `IReadOnlyRepository<TEntity, TKey>` and `IReadOnlyBasicRepository<Tentity, TKey>` interfaces for who only want to depend on querying capabilities of the repositories.
public async Task<List<PersonDto>> GetListAsync(string filter)
{
//Obtain the IQueryable<Person>
IQueryable<Person> queryable = await _personRepository.GetQueryableAsync();

### Generic Repository without a Primary Key
//Create a query
var query = from person in queryable
where person.Name == filter
orderby person.Name
select person;

If your entity does not have an Id primary key (it may have a composite primary key for instance) then you cannot use the `IRepository<TEntity, TKey>` (or basic/readonly versions) defined above. In that case, you can inject and use `IRepository<TEntity>` for your entity.
//Execute the query to get list of people
var people = query.ToList();

> `IRepository<TEntity>` has a few missing methods those normally works with the `Id` property of an entity. Because of the entity has no `Id` property in that case, these methods are not available. One example is the `Get` method that gets an id and returns the entity with given id. However, you can still use `IQueryable<TEntity>` features to query entities by standard LINQ methods.
//Convert to DTO and return to the client
return people.Select(p => new PersonDto {Name = p.Name}).ToList();
}
}
}
````

You could also use the LINQ extension methods:

````csharp
public async Task<List<PersonDto>> GetListAsync(string filter)
{
//Obtain the IQueryable<Person>
IQueryable<Person> queryable = await _personRepository.GetQueryableAsync();

//Execute a query
var people = queryable
.Where(p => p.Name.Contains(filter))
.OrderBy(p => p.Name)
.ToList();

//Convert to DTO and return to the client
return people.Select(p => new PersonDto {Name = p.Name}).ToList();
}
````

Any standard LINQ method can be used over the `IQueryable` returned from the repository.

> This sample uses `ToList()` method, but it is **strongly suggested to use the asynchronous methods** to perform database queries, like `ToListAsync()` for this example.
>
> See the **IQueryable & Async Operations** section to learn how you can do it.
### Bulk Operations

There are some methods to perform bulk operations in the database;

* `InsertManyAsync`
* `UpdateManyAsync`
* `DeleteManyAsync`

These methods work with multiple entities and can take advantage of bulk operations if supported by the underlying database provider.

> Optimistic concurrency control may not be possible when you use `UpdateManyAsync` and `DeleteManyAsync` methods.
### Soft / Hard Delete

`DeleteAsync` method of the repository doesn't delete the entity if the entity is a **soft-delete** entity (that implements `ISoftDelete`). Soft-delete entities are marked as "deleted" in the database. Data Filter system ensures that the soft deleted entities are not retrieved from database normally.

If your entity is a soft-delete entity, you can use the `HardDeleteAsync` method to really delete the entity from database in case of you need it.
If your entity is a soft-delete entity, you can use the `HardDeleteAsync` method to physically delete the entity from database in case of you need it.

> See the [Data Filtering](Data-Filtering.md) documentation for more about soft-delete.
## Other Generic Repository Types

Standard `IRepository<TEntity, TKey>` interface exposes the standard `IQueryable<TEntity>` and you can freely query using the standard LINQ methods. This is fine for most of the applications. However, some ORM providers or database systems may not support standard `IQueryable` interface. If you want to use such providers, you can't rely on the `IQueryable`.

### Basic Repositories

ABP provides `IBasicRepository<TEntity, TPrimaryKey>` and `IBasicRepository<TEntity>` interfaces to support such scenarios. You can extend these interfaces (and optionally derive from `BasicRepositoryBase`) to create custom repositories for your entities.

Depending on `IBasicRepository` but not depending on `IRepository` has an advantage to make possible to work with all data sources even if they don't support `IQueryable`.

See the [Data Filtering](Data-Filtering.md) documentation for more about soft-delete.
Major vendors, like Entity Framework, NHibernate or MongoDB already support `IQueryable`. So, working with `IRepository` is the **suggested** way for typical applications. But reusable module developers may consider `IBasicRepository` to support a wider range of data sources.

## Bulk Operations
You can execute bulk operations with `InsertManyAsync`, `UpdateManyAsync`, `DeleteManyAsync` methods.
### Read Only Repositories

> **WARNING:** ConcurrencyStamp can't be checked at bulk operations!
There are also `IReadOnlyRepository<TEntity, TKey>` and `IReadOnlyBasicRepository<Tentity, TKey>` interfaces for who only want to depend on querying capabilities of the repositories.

### Generic Repository without a Primary Key

If your entity does not have an Id primary key (it may have a composite primary key for instance) then you cannot use the `IRepository<TEntity, TKey>` (or basic/readonly versions) defined above. In that case, you can inject and use `IRepository<TEntity>` for your entity.

> `IRepository<TEntity>` has a few missing methods those normally works with the `Id` property of an entity. Because of the entity has no `Id` property in that case, these methods are not available. One example is the `Get` method that gets an id and returns the entity with given id. However, you can still use `IQueryable<TEntity>` features to query entities by standard LINQ methods.
## Custom Repositories

Expand Down

0 comments on commit 0f5685a

Please sign in to comment.