Skip to content

Commit 796e614

Browse files
Merge pull request #9247 from mentianyi/patch-1
Update Entity-Framework-Core.md
2 parents 161ae52 + 7869256 commit 796e614

File tree

1 file changed

+281
-2
lines changed

1 file changed

+281
-2
lines changed

docs/zh-Hans/Entity-Framework-Core.md

Lines changed: 281 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ namespace MyCompany.MyProject
3030
3131
### 数据库管理系统选择
3232

33-
EF Core支持多种数据库管理系统([查看全部](https://docs.microsoft.com/en-us/ef/core/providers/)). ABP框架和本文档不依赖于任何特定的DBMS.
33+
EF Core支持多种数据库管理系统([查看全部](https://docs.microsoft.com/zh-cn/ef/core/providers/)). ABP框架和本文档不依赖于任何特定的DBMS.
3434

3535
如果要创建一个可重用的[应用程序模块](Modules/Index.md),应避免依赖于特定的DBMS包.但在最终的应用程序中,始终会选择一个DBMS.
3636

@@ -60,7 +60,7 @@ namespace MyCompany.MyProject
6060

6161
### 关于EF Core Fluent Mapping
6262

63-
[应用程序启动模板](Startup-Templates/Application.md)已配置使用[EF Core fluent configuration API](https://docs.microsoft.com/en-us/ef/core/modeling/)映射你的实体到数据库表.
63+
[应用程序启动模板](Startup-Templates/Application.md)已配置使用[EF Core fluent configuration API](https://docs.microsoft.com/zh-cn/ef/core/modeling/)映射你的实体到数据库表.
6464

6565
你依然为你的实体属性使用**data annotation attributes**(像`[Required]`),而ABP文档通常遵循**fluent mapping API** approach方法. 如何使用取决与你.
6666

@@ -272,6 +272,285 @@ public async override Task DeleteAsync(
272272
}
273273
````
274274

275+
## 加载关联实体
276+
277+
假设你拥有带有`OrderLine`集合的`Order`,并且`OrderLine`具有`Order`的导航属性:
278+
279+
````csharp
280+
using System;
281+
using System.Collections.Generic;
282+
using System.Collections.ObjectModel;
283+
using Volo.Abp.Auditing;
284+
using Volo.Abp.Domain.Entities;
285+
286+
namespace MyCrm
287+
{
288+
public class Order : AggregateRoot<Guid>, IHasCreationTime
289+
{
290+
public Guid CustomerId { get; set; }
291+
public DateTime CreationTime { get; set; }
292+
293+
public ICollection<OrderLine> Lines { get; set; } //子集合
294+
295+
public Order()
296+
{
297+
Lines = new Collection<OrderLine>();
298+
}
299+
}
300+
301+
public class OrderLine : Entity<Guid>
302+
{
303+
public Order Order { get; set; } //导航属性
304+
public Guid OrderId { get; set; }
305+
306+
public Guid ProductId { get; set; }
307+
public int Count { get; set; }
308+
public double UnitPrice { get; set; }
309+
}
310+
}
311+
312+
````
313+
314+
然后象下面显示的这样定义数据库映射:
315+
316+
````csharp
317+
builder.Entity<Order>(b =>
318+
{
319+
b.ToTable("Orders");
320+
b.ConfigureByConvention();
321+
322+
//定义关系
323+
b.HasMany(x => x.Lines)
324+
.WithOne(x => x.Order)
325+
.HasForeignKey(x => x.OrderId)
326+
.IsRequired();
327+
});
328+
329+
builder.Entity<OrderLine>(b =>
330+
{
331+
b.ToTable("OrderLines");
332+
b.ConfigureByConvention();
333+
});
334+
````
335+
336+
当你查询一个 `Order`, 你可能想要在单个查询中**包含**所有的 `OrderLine`s 或根据需要在**以后加载它们**.
337+
338+
> 实际上这与ABP框架没有直接关系. 你可以按照 [EF Core 文档](https://docs.microsoft.com/zh-cn/ef/core/querying/related-data/) 了解全部细节. 本节将涵盖与 ABP 框架相关的一些主题.
339+
340+
### 预先加载 / 包含子对象的加载
341+
342+
当你想加载一个带有关联实体的实体时,可以使用不同的选项.
343+
344+
#### Repository.WithDetails
345+
346+
`IRepository.WithDetailsAsync(...)` 可以通过包含一个关系收集/属性来获得 `IQueryable<T>` .
347+
348+
**示例: 获取一个带有 `lines``order` 对象**
349+
350+
````csharp
351+
using System;
352+
using System.Linq;
353+
using System.Threading.Tasks;
354+
using Volo.Abp.Domain.Repositories;
355+
using Volo.Abp.Domain.Services;
356+
357+
namespace AbpDemo.Orders
358+
{
359+
public class OrderManager : DomainService
360+
{
361+
private readonly IRepository<Order, Guid> _orderRepository;
362+
363+
public OrderManager(IRepository<Order, Guid> orderRepository)
364+
{
365+
_orderRepository = orderRepository;
366+
}
367+
368+
public async Task TestWithDetails(Guid id)
369+
{
370+
//通过包含子集合获取一个 IQueryable<T>
371+
var queryable = await _orderRepository.WithDetailsAsync(x => x.Lines);
372+
373+
//应用其他的 LINQ 扩展方法
374+
var query = queryable.Where(x => x.Id == id);
375+
376+
//执行此查询并获取结果
377+
var order = await AsyncExecuter.FirstOrDefaultAsync(query);
378+
}
379+
}
380+
}
381+
````
382+
383+
> `AsyncExecuter` 用于执行异步 LINQ 扩展,而无需依赖 EF Core. 如果你将 EF Core NuGet 包引用添加到你的项目中,则可以直接使用 `await query.FirstOrDefaultAsync()`. 但是, 这次你依赖于域层中的 EF 核心.请参阅. 请参阅 [仓储文档](Repositories.md) 以了解更多.
384+
385+
**示例: 获取一个包含 `lines``orders` 列表**
386+
387+
````csharp
388+
public async Task TestWithDetails()
389+
{
390+
//通过包含一个子集合获取一个 IQueryable<T>
391+
var queryable = await _orderRepository.WithDetailsAsync(x => x.Lines);
392+
393+
//执行此查询并获取结果
394+
var orders = await AsyncExecuter.ToListAsync(queryable);
395+
}
396+
````
397+
398+
> 如果你需要包含多个导航属性或集合,`WithDetailsAsync`方法可以获得多个表达参数.
399+
400+
#### DefaultWithDetailsFunc
401+
402+
如果你没有将任何表达式传递到 `WithDetailsAsync` 方法,则它包括使用你提供的 `DefaultWithDetailsFunc` 选项的所有详细信息.
403+
404+
你可以在你的 `EntityFrameworkCore` 项目[模块](Module-Development-Basics.md)`ConfigureServices`方法为一个实体配置 `DefaultWithDetailsFunc`.
405+
406+
**示例: 在查询一个 `Order` 时包含 `Lines`**
407+
408+
````csharp
409+
Configure<AbpEntityOptions>(options =>
410+
{
411+
options.Entity<Order>(orderOptions =>
412+
{
413+
orderOptions.DefaultWithDetailsFunc = query => query.Include(o => o.Lines);
414+
});
415+
});
416+
````
417+
418+
> 你可以在这里完全使用 EF Core API,因为这位于 EF Core集成项目中.
419+
420+
然后你可以不带任何参数地调用 `WithDetails` 方法:
421+
422+
````csharp
423+
public async Task TestWithDetails()
424+
{
425+
//通过包含一个子集合获取一个 IQueryable<T>
426+
var queryable = await _orderRepository.WithDetailsAsync();
427+
428+
//执行此查询并获取结果
429+
var orders = await AsyncExecuter.ToListAsync(queryable);
430+
}
431+
````
432+
433+
`WithDetailsAsync()` 执行你已经在 `DefaultWithDetailsFunc` 中设置的表达式.
434+
435+
#### 仓储 Get/Find 方法
436+
437+
有些标准的 [仓储](Repositories.md) 方法带有可选的 `includeDetails` 参数;
438+
439+
* `GetAsync``FindAsync` 方法带有默认值为 `true``includeDetails`.
440+
* `GetListAsync``GetPagedListAsync` 方法带有默认值为 `false``includeDetails`.
441+
442+
这意味着,默认情况下返回**包含子对象的单个实体**,而列表返回方法则默认不包括子对象信息.你可以明确通过 `includeDetails` 来更改此行为.
443+
444+
> 这些方法使用上面解释的 `DefaultWithDetailsFunc` 选项.
445+
446+
**示例:获取一个包含子对象的 `order`**
447+
448+
````csharp
449+
public async Task TestWithDetails(Guid id)
450+
{
451+
var order = await _orderRepository.GetAsync(id);
452+
}
453+
````
454+
455+
**示例:获取一个不包含子对象的 `order`**
456+
457+
````csharp
458+
public async Task TestWithoutDetails(Guid id)
459+
{
460+
var order = await _orderRepository.GetAsync(id, includeDetails: false);
461+
}
462+
````
463+
464+
**示例:获取一个包含子对象的实体列表**
465+
466+
````csharp
467+
public async Task TestWithDetails()
468+
{
469+
var orders = await _orderRepository.GetListAsync(includeDetails: true);
470+
}
471+
````
472+
473+
#### 选择
474+
475+
存储库模式尝试封装 EF Core, 因此你的选项是有限的. 如果你需要高级方案,你可以按照其中一个选项执行:
476+
477+
* 创建自定义存储库方法并使用完整的 EF Core API.
478+
* 在你的项目中引用 `Volo.Abp.EntityFrameworkCore` . 通过这种方式,你可以直接在代码中使用 `Include``ThenInclude` .
479+
480+
请参阅 EF Core 的 [预先加载文档](https://docs.microsoft.com/zh-cn/ef/core/querying/related-data/eager).
481+
482+
### 显示 / 延迟加载
483+
484+
如果你在查询实体时不包括关系,并且以后需要访问导航属性或集合,则你有不同的选择.
485+
486+
#### EnsurePropertyLoadedAsync / EnsureCollectionLoadedAsync
487+
488+
仓储提供 `EnsurePropertyLoadedAsync``EnsureCollectionLoadedAsync` 扩展方法来**显示加载**一个导航属性或子集合.
489+
490+
**示例: 在需要时加载一个 `Order``Lines`**
491+
492+
````csharp
493+
public async Task TestWithDetails(Guid id)
494+
{
495+
var order = await _orderRepository.GetAsync(id, includeDetails: false);
496+
//order.Lines 此时是空的
497+
498+
await _orderRepository.EnsureCollectionLoadedAsync(order, x => x.Lines);
499+
//order.Lines 被填充
500+
}
501+
````
502+
503+
如果导航属性或集合已经被加载那么 `EnsurePropertyLoadedAsync``EnsureCollectionLoadedAsync` 方法不做任何处理. 所以,调用多次也没有问题.
504+
505+
请参阅 EF Core 的[显示加载文档](https://docs.microsoft.com/zh-cn/ef/core/querying/related-data/explicit).
506+
507+
#### 使用代理的延时加载
508+
509+
在某些情况下,可能无法使用显示加载,尤其是当你没有引用 `Repository``DbContext`时.延时加载是 EF Core 加载关联属性/集合的一个功能,,当你第一次访问它.
510+
511+
启用延时加载:
512+
513+
1. 安装 [Microsoft.EntityFrameworkCore.Proxies](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Proxies/) 包到你的项目(通常是 EF Core 集成项目)
514+
2. 为你的 `DbContext` 配置 `UseLazyLoadingProxies` (在 EF Core 项目的模块的 `ConfigureServices` 方法中). 例如:
515+
516+
````csharp
517+
Configure<AbpDbContextOptions>(options =>
518+
{
519+
options.PreConfigure<MyCrmDbContext>(opts =>
520+
{
521+
opts.DbContextOptions.UseLazyLoadingProxies(); //启用延时加载
522+
});
523+
524+
options.UseSqlServer();
525+
});
526+
````
527+
528+
3. 使你的导航属性和集合是 `virtual`. 例如:
529+
530+
````csharp
531+
public virtual ICollection<OrderLine> Lines { get; set; } //虚集合
532+
public virtual Order Order { get; set; } //虚导航属性
533+
````
534+
535+
启用延时加载并整理实体后,你可以自由访问导航属性和集合:
536+
537+
````csharp
538+
public async Task TestWithDetails(Guid id)
539+
{
540+
var order = await _orderRepository.GetAsync(id);
541+
//order.Lines 此时是空的
542+
543+
var lines = order.Lines;
544+
//order.Lines 被填充 (延时加载)
545+
}
546+
````
547+
548+
每当你访问属性/集合时,EF Core 都会自动执行额外的查询,从数据库中加载属性/集合.
549+
550+
> 应谨慎使用延时加载,因为它可能会在某些特定情况下导致性能问题.
551+
552+
请参阅 EF Core 的[延时加载文档](https://docs.microsoft.com/zh-cn/ef/core/querying/related-data/lazy).
553+
275554
## 访问 EF Core API
276555

277556
大多数情况下应该隐藏仓储后面的EF Core API(这也是仓储的设计目地). 但是如果想要通过仓储访问DbContext实现,则可以使用`GetDbContext()``GetDbSet()`扩展方法. 例:

0 commit comments

Comments
 (0)