From 250f9977b2b89833c1dc226bcf7db67cde0cb7d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sun, 17 Jan 2021 22:04:22 +0300 Subject: [PATCH] Repository document: Use GetQueryableAsync. --- docs/en/Repositories.md | 176 ++++++++++++++++++++++++++++++---------- 1 file changed, 135 insertions(+), 41 deletions(-) diff --git a/docs/en/Repositories.md b/docs/en/Repositories.md index 5ad3107ca67..a2eb385094b 100644 --- a/docs/en/Repositories.md +++ b/docs/en/Repositories.md @@ -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 _personRepository; +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; - public PersonAppService(IRepository personRepository) +namespace Demo +{ + public class PersonAppService : ApplicationService { - _personRepository = personRepository; - } + private readonly IRepository _personRepository; - public async Task Create(CreatePersonDto input) - { - var person = new Person { Name = input.Name, Age = input.Age }; + public PersonAppService(IRepository personRepository) + { + _personRepository = personRepository; + } - await _personRepository.InsertAsync(person); - } + public async Task CreateAsync(CreatePersonDto input) + { + var person = new Person(input.Name); - public List 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 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` 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`, 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` interface extends standard `IQueryable` 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` object. You can use this object to perform LINQ queries on the entities in the database. -ABP provides `IBasicRepository` and `IBasicRepository` 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 _personRepository; -### Read Only Repositories + public PersonAppService(IRepository personRepository) + { + _personRepository = personRepository; + } -There are also `IReadOnlyRepository` and `IReadOnlyBasicRepository` interfaces for who only want to depend on querying capabilities of the repositories. + public async Task> GetListAsync(string filter) + { + //Obtain the IQueryable + IQueryable 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` (or basic/readonly versions) defined above. In that case, you can inject and use `IRepository` for your entity. + //Execute the query to get list of people + var people = query.ToList(); -> `IRepository` 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` 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> GetListAsync(string filter) +{ + //Obtain the IQueryable + IQueryable 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` interface exposes the standard `IQueryable` 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` and `IBasicRepository` 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` and `IReadOnlyBasicRepository` 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` (or basic/readonly versions) defined above. In that case, you can inject and use `IRepository` for your entity. + +> `IRepository` 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` features to query entities by standard LINQ methods. ## Custom Repositories