Added "Loading related entities" section to the EF Core document.

pull/6290/head
Halil İbrahim Kalkan 5 years ago
parent 66a3d3c492
commit 55ed8732ec

@ -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 class BookRepository
: EfCoreRepository<BookStoreDbContext, Book, Guid>, IBookRepository
{
public BookRepository(IDbContextProvider<BookStoreDbContext> dbContextProvider)
: base(dbContextProvider)
@ -272,6 +273,208 @@ 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 to learn all the details. This section will cover some topics related to the ABP Framework.
### 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.
**Example: Get an order with lines**
````csharp
using System;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;
namespace MyCrm
{
public class OrderManager : DomainService
{
private readonly IRepository<Order, Guid> _orderRepository;
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`**
````csharp
Configure<AbpEntityOptions>(options =>
{
options.Entity<Order>(orderOptions =>
{
orderOptions.DefaultWithDetailsFunc = query => query.Include(o => o.Lines);
});
});
````
> 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(Guid id)
{
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.
### Lazy Loading
TODO
## 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 +499,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.
## Extra Properties & Object Extension Manager

Loading…
Cancel
Save