> `ISoftDelete` filter is enabled by default and you can not get deleted entities from database unless you explicitly disable it. See the `IDataFilter` service below.
> Soft-delete entities can be hard-deleted when you use `HardDeleteAsync` method on the repositories.
### IMultiTenant
[Multi-tenancy](Multi-Tenancy.md) is an efficient way of creating SaaS applications. Once you create a multi-tenant application, you typically want to isolate data between tenants. Implement `IMultiTenant` interface to make your entity "multi-tenant aware".
@ -174,7 +174,7 @@ public class Book : AggregateRoot<Guid>
}
```
(`BookType` is a simple enum here) And you want to create a new `Book` entity in a [domain service](Domain-Services.md):
(`BookType` is a simple `enum` here and not important) And you want to create a new `Book` entity in a [domain service](Domain-Services.md):
````csharp
public class BookManager : DomainService
@ -221,12 +221,13 @@ public interface IBookRepository : IRepository<Book, Guid>
}
````
You generally want to derive from the `IRepository` to inherit standard repository methods. However, you don't have to. Repository interfaces are defined in the domain layer of a layered application. They are implemented in the data/infrastructure layer (`EntityFrameworkCore` project in a [startup template](https://abp.io/Templates)).
You generally want to derive from the `IRepository` to inherit standard repository methods (while, you don't have to do). Repository interfaces are defined in the domain layer of a layered application. They are implemented in the data/infrastructure layer (`EntityFrameworkCore` project in a [startup template](https://abp.io/Templates)).
Example implementation of the `IBookRepository` interface:
````csharp
public class BookRepository : EfCoreRepository<BookStoreDbContext,Book,Guid>, IBookRepository
public BookRepository(IDbContextProvider<BookStoreDbContext> dbContextProvider)
: base(dbContextProvider)
@ -272,6 +273,278 @@ public async override Task DeleteAsync(
}
````
## Loading Related Entities
Assume that you've an `Order` with a collection of `OrderLine`s and the `OrderLine` has a navigation property to the `Order`:
````csharp
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
namespace MyCrm
{
public class Order : AggregateRoot<Guid>, IHasCreationTime
{
public Guid CustomerId { get; set; }
public DateTime CreationTime { get; set; }
public ICollection<OrderLine> Lines { get; set; } //Sub collection
public Order()
{
Lines = new Collection<OrderLine>();
}
}
public class OrderLine : Entity<Guid>
{
public Order Order { get; set; } //Navigation property
public Guid OrderId { get; set; }
public Guid ProductId { get; set; }
public int Count { get; set; }
public double UnitPrice { get; set; }
}
}
````
And defined the database mapping as shown below:
````csharp
builder.Entity<Order>(b =>
{
b.ToTable("Orders");
b.ConfigureByConvention();
//Define the relation
b.HasMany(x => x.Lines)
.WithOne(x => x.Order)
.HasForeignKey(x => x.OrderId)
.IsRequired();
});
builder.Entity<OrderLine>(b =>
{
b.ToTable("OrderLines");
b.ConfigureByConvention();
});
````
When you query an `Order`, you may want to **include** all the `OrderLine`s in a single query or you may want to **load them later** on demand.
> Actually these are not directly related to the ABP Framework. You can follow the [EF Core documentation](https://docs.microsoft.com/en-us/ef/core/querying/related-data/) to learn all the details. This section will cover some topics related to the ABP Framework.
### Eager Loading / Load With Details
You have different options when you want to load the related entities while querying an entity.
#### Repository.WithDetails
`IRepository.WithDetails(...)` can be used to include one relation collection/property to the query.
public OrderManager(IRepository<Order,Guid> orderRepository)
{
_orderRepository = orderRepository;
}
public async Task TestWithDetails(Guid id)
{
var query = _orderRepository
.WithDetails(x => x.Lines)
.Where(x => x.Id == id);
var order = await AsyncExecuter.FirstOrDefaultAsync(query);
}
}
}
````
> `AsyncExecuter` is used to execute async LINQ extensions without depending on the EF Core. If you add EF Core NuGet package reference to your project, then you can directly use `await _orderRepository.WithDetails(x => x.Lines).FirstOrDefaultAsync()`. But, this time you depend on the EF Core in your domain layer. See the [repository document](Repositories.md) to learn more.
**Example: Get a list of orders with their lines**
````csharp
public async Task TestWithDetails()
{
var query = _orderRepository
.WithDetails(x => x.Lines);
var orders = await AsyncExecuter.ToListAsync(query);
}
````
> `WithDetails` method can get more than one expression parameter if you need to include more than one navigation property or collection.
#### DefaultWithDetailsFunc
If you don't pass any expression to the `WithDetails` method, then it includes all the details using the `DefaultWithDetailsFunc` option you provide.
You can configure `DefaultWithDetailsFunc` for an entity in the `ConfigureServices` method of your [module](Module-Development-Basics.md) in your `EntityFrameworkCore` project.
**Example: Include `Lines` while querying an `Order`**
> You can fully use the EF Core API here since this is located in the EF Core integration project.
Then you can use the `WithDetails` without any parameter:
````csharp
public async Task TestWithDetails()
{
var query = _orderRepository.WithDetails();
var orders = await AsyncExecuter.ToListAsync(query);
}
````
`WithDetails()` executes the expression you've setup as the `DefaultWithDetailsFunc`.
#### Repository Get/Find Methods
Some of the standard [Repository](Repositories.md) methods have optional `includeDetails` parameters;
* `GetAsync` and `FindAsync` gets `includeDetails` with default value is `true`.
* `GetListAsync` and `GetPagedListAsync` gets `includeDetails` with default value is `false`.
That means, the methods return a **single entity includes details** by default while list returning methods don't include details by default. You can explicitly pass `includeDetails` to change the behavior.
> These methods use the `DefaultWithDetailsFunc` option that is explained above.
**Example: Get an order with details**
````csharp
public async Task TestWithDetails(Guid id)
{
var order = await _orderRepository.GetAsync(id);
}
````
**Example: Get an order without details**
````csharp
public async Task TestWithoutDetails(Guid id)
{
var order = await _orderRepository.GetAsync(id, includeDetails: false);
}
````
**Example: Get list of entities with details**
````csharp
public async Task TestWithDetails()
{
var orders = await _orderRepository.GetListAsync(includeDetails: true);
}
````
#### Alternatives
The repository patters tries to encapsulate the EF Core, so your options are limited. If you need an advanced scenario, you can follow one of the options;
* Create a custom repository method and use the complete EF Core API.
* Reference to the `Volo.Abp.EntityFrameworkCore` package from your project. In this way, you can directly use `Include` and `ThenInclude` in your code.
See also [eager loading document](https://docs.microsoft.com/en-us/ef/core/querying/related-data/eager) of the EF Core.
### Explicit / Lazy Loading
If you don't include relations while querying an entity and later need to access to a navigation property or collection, you have different options.
Repositories provide `EnsurePropertyLoadedAsync` and `EnsureCollectionLoadedAsync` extension methods to **explicitly load** a navigation property or sub collection.
**Example: Load Lines of an Order when needed**
````csharp
public async Task TestWithDetails(Guid id)
{
var order = await _orderRepository.GetAsync(id, includeDetails: false);
//order.Lines is empty on this stage
await _orderRepository.EnsureCollectionLoadedAsync(order, x => x.Lines);
//order.Lines is filled now
}
````
`EnsurePropertyLoadedAsync` and `EnsureCollectionLoadedAsync` methods do nothing if the property or collection was already loaded. So, calling multiple times has no problem.
See also [explicit loading document](https://docs.microsoft.com/en-us/ef/core/querying/related-data/explicit) of the EF Core.
#### Lazy Loading with Proxies
Explicit loading may not be possible in some cases, especially when you don't have a reference to the `Repository` or `DbContext`. Lazy Loading is a feature of the EF Core that loads the related properties / collections when you first access to it.
To enable lazy loading;
1. Install the [Microsoft.EntityFrameworkCore.Proxies](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Proxies/) package into your project (typically to the EF Core integration project)
2. Configure `UseLazyLoadingProxies` for your `DbContext` (in the `ConfigureServices` method of your module in your EF Core project). Example:
3. Make your navigation properties and collections `virtual`. Examples:
````csharp
public virtual ICollection<OrderLine> Lines { get; set; } //virtual collection
public virtual Order Order { get; set; } //virtual navigation property
````
Once you enable lazy loading and arrange your entities, you can freely access to the navigation properties and collections:
````csharp
public async Task TestWithDetails(Guid id)
{
var order = await _orderRepository.GetAsync(id);
//order.Lines is empty on this stage
var lines = order.Lines;
//order.Lines is filled (lazy loaded)
}
````
Whenever you access to a property/collection, EF Core automatically performs an additional query to load the property/collection from the database.
> Lazy loading should be carefully used since it may cause performance problems in some specific cases.
See also [lazy loading document](https://docs.microsoft.com/en-us/ef/core/querying/related-data/lazy) of the EF Core.
## Access to the EF Core API
In most cases, you want to hide EF Core APIs behind a repository (this is the main purpose of the repository pattern). However, if you want to access the `DbContext` instance over the repository, you can use `GetDbContext()` or `GetDbSet()` extension methods. Example:
@ -296,7 +569,7 @@ public class BookService
* `GetDbContext` returns a `DbContext` reference instead of `BookStoreDbContext`. You can cast it, however in most cases you don't need it.
> Important: You must reference to the `Volo.Abp.EntityFrameworkCore` package from the project you want to access to the DbContext. This breaks encapsulation, but this is what you want in that case.
> Important: You must reference to the `Volo.Abp.EntityFrameworkCore` package from the project you want to access to the `DbContext`. This breaks encapsulation, but this is what you want in that case.
In this example, `OtherDbContext` implements `IBookStoreDbContext`. This feature allows you to have multiple DbContext (one per module) on development, but single DbContext (implements all interfaces of all DbContexts) on runtime.
### Enabling split queries globally by default
### Split Queries
Abp enables split queries globally by default for better performance. You can also change it as needed, such as:
ABP enables [split queries](https://docs.microsoft.com/en-us/ef/core/querying/single-split-queries) globally by default for better performance. You can change it as needed.
@ -79,6 +79,14 @@ If your entity does not have an Id primary key (it may have a composite primary
> `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.
### 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.
See the [Data Filtering](Data-Filtering.md) documentation for more about soft-delete.
## Custom Repositories
Default generic repositories will be sufficient for most cases. However, you may need to create a custom repository class for your entity.