@@ -30,7 +30,7 @@ namespace MyCompany.MyProject
30
30
31
31
### 数据库管理系统选择
32
32
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.
34
34
35
35
如果要创建一个可重用的[ 应用程序模块] ( Modules/Index.md ) ,应避免依赖于特定的DBMS包.但在最终的应用程序中,始终会选择一个DBMS.
36
36
@@ -60,7 +60,7 @@ namespace MyCompany.MyProject
60
60
61
61
### 关于EF Core Fluent Mapping
62
62
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/ ) 映射你的实体到数据库表.
64
64
65
65
你依然为你的实体属性使用** data annotation attributes** (像` [Required] ` ),而ABP文档通常遵循** fluent mapping API** approach方法. 如何使用取决与你.
66
66
@@ -272,6 +272,285 @@ public async override Task DeleteAsync(
272
272
}
273
273
````
274
274
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
+
275
554
## 访问 EF Core API
276
555
277
556
大多数情况下应该隐藏仓储后面的EF Core API(这也是仓储的设计目地). 但是如果想要通过仓储访问DbContext实现,则可以使用` GetDbContext() ` 或` GetDbSet() ` 扩展方法. 例:
0 commit comments