Browse Source

Merge branch 'dev' into hamza/arabic-localization

pull/14662/head
Hamza Albreem 4 years ago
parent
commit
8fe7ddd1af
  1. 65
      docs/en/Migration-Guides/Abp-7_0.md
  2. 264
      docs/zh-Hans/Repositories.md

65
docs/en/Migration-Guides/Abp-7_0.md

@ -2,6 +2,14 @@
This document is a guide for upgrading ABP v6.0 solutions to ABP v7.0. There is a change in this version that may affect your applications, please read it carefully and apply the necessary changes to your application. This document is a guide for upgrading ABP v6.0 solutions to ABP v7.0. There is a change in this version that may affect your applications, please read it carefully and apply the necessary changes to your application.
## `FormTenantResolveContributor` Removed from the `AbpTenantResolveOptions`
`FormTenantResolveContributor` has been removed from the `AbpTenantResolveOptions`. Thus, if you need to get tenant info from `HTTP Request From`, please add a custom `TenantResolveContributor` to implement it.
## `IHybridServiceScopeFactory` Removed
`IHybridServiceScopeFactory` has been removed. Please use the `IServiceScopeFactory` instead.
## Hybrid JSON was removed. ## Hybrid JSON was removed.
Since [System.Text.Json](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/overview) library supports more custom features in NET 7, ABP no longer need the hybrid Json feature. Since [System.Text.Json](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/overview) library supports more custom features in NET 7, ABP no longer need the hybrid Json feature.
@ -34,3 +42,60 @@ Check the docs to see the more info: https://github.com/abpframework/abp/blob/de
Check the docs to see how to customize a JSON contract: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/custom-contracts Check the docs to see how to customize a JSON contract: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/custom-contracts
## "Manage Host Features" Moved to the Settings Page
"Manage Host Features" button has been moved from Tenants page to Settings page.
See https://github.com/abpframework/abp/pull/13359 for more info.
## Removed the `setter` from the Auditing Interfaces
`AuditedEntity` and other base entity classes will continue to have public setters. If you want to make them private, don't derive from these base classes, but implement the interfaces yourself.
See https://github.com/abpframework/abp/issues/12229#issuecomment-1191384798 for more info.
## Added Abp prefix to DbProperties Classes
Please update the database migration and related connection string names.
## `EntityCreatingEventData`, `EntityUpdatingEventData`, `EntityDeletingEventData` and `EntityChangingEventData` has been removed.
They are deprecated don't use them anymore.
## LayoutHookInfo.cs, LayoutHookViewModel.cs, LayoutHooks.cs, AbpLayoutHookOptions.cs classes have been moved under the Volo.Abp.Ui.LayoutHooks namespace.
See https://github.com/abpframework/abp/pull/13903 for more info.
## Removed `abp.auth.policies`
`abp.auth.polices` has been removed, use `abp.auth.grantedPolicies` instead.
## Static C# Proxy Generation
The `abp generate-proxy -t csharp ..` command will generate all the `classes/enums/other types` in the client side (including application service interfaces) behalf of you.
If you have reference to the target contracts package, then you can pass a parameter `--without-contracts (shortcut: -c)`.
See https://github.com/abpframework/abp/issues/13613#issue-1333088953 for more info.
## Dynamic Permissions
* `IPermissionDefinitionManager` methods are converted to asynchronous, and renamed (added Async postfix).
* Removed `MultiTenancySides` from permission groups.
* Inherit `MultiTenancySides` enum from byte (default was int).
* Needs to add migration for new entities in the Permission Management module.
See https://github.com/abpframework/abp/pull/13644 for more info.
## External Localization Infrastructure
* Introduced `LocalizationResourceBase` that is base for localization resources. `LocalizationResource` inherits from it for typed (static) localization resources (like before). Also introduced `NonTypedLocalizationResource` that inherits from `LocalizationResourceBase` for dynamic/external localization resources. We are using `LocalizationResourceBase` for most of the places where we were using `LocalizationResource` before and that can be a breaking change for some applications.
* All layouts in all MVC UI themes should add this line just before the **ApplicationConfigurationString** line:
```html
<script src="~/Abp/ApplicationLocalizationScript?cultureName=@CultureInfo.CurrentUICulture.Name"></script>
```
We've already done this for our themes.
See https://github.com/abpframework/abp/pull/13845 for more info.

264
docs/zh-Hans/Repositories.md

@ -13,82 +13,193 @@ ABP为每个聚合根或实体提供了 **默认的通用(泛型)仓储** .
**默认通用仓储用法示例:** **默认通用仓储用法示例:**
````C# ````C#
public class PersonAppService : ApplicationService using System;
{ using System.Threading.Tasks;
private readonly IRepository<Person, Guid> _personRepository; using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
public PersonAppService(IRepository<Person, Guid> personRepository) namespace Demo
{
public class PersonAppService : ApplicationService
{ {
_personRepository = personRepository; private readonly IRepository<Person, Guid> _personRepository;
}
public async Task Create(CreatePersonDto input) public PersonAppService(IRepository<Person, Guid> personRepository)
{ {
var person = new Person { Name = input.Name, Age = input.Age }; _personRepository = personRepository;
}
public async Task CreateAsync(CreatePersonDto input)
{
var person = new Person(input.Name);
await _personRepository.InsertAsync(person); await _personRepository.InsertAsync(person);
}
public async Task<int> GetCountAsync(string filter)
{
return await _personRepository.CountAsync(p => p.Name.Contains(filter));
}
} }
}
````
在这个例子中;
* `PersonAppService` 在它的构造函数中注入了 `IRepository<Person, Guid>` 。
* `CreateAsync` 方法使用了 `InsertAsync` 创建并保存新的实体。
* `GetCountAsync` 方法用来从数据库中获取符合指定条件的的人员的数量。
### 标准仓储方法
通用仓储提供了一些开箱即用的标准CRUD功能:
public List<PersonDto> GetList(string nameFilter) * `GetAsync`: 根据指定的`Id`或断言(lambda表达式)返回实体。
* 将在指定的实体不存在时,抛出异常 `EntityNotFoundException`
* 如果指定的条件存在多个实体时,抛出异常 `InvalidOperationException`
* `FindAsync`: 根据指定的`Id`或断言(lambda表达式)返回实体。
* 如果指定的实体不存在时,返回 `null` 。
* 如果指定的条件存在多个实体时,抛出异常 `InvalidOperationException`
* `InsertAsync`: 在数据库里插入一个新的实体。
* `UpdateAsync`: 在数据库里更新一个已经存在的实体。
* `DeleteAsync`: 从数据库里删除指定的实体。
* 这个方法还有一个重载根据指定的断言(lambda表达式)来删除满足条件的多个实体。
* `GetListAsync`: 返回数据库里的所有实体。
* `GetPagedListAsync`: 返回一个指定长度的实体列表。 他拥有 `skipCount`, `maxResultCount` and `sorting` 参数.
* `GetCountAsync`: 获取数据库里所有实体的数量
这些方法还有还一些重载。
* 提供 `UpdateAsync` 和 `DeleteAsync` 方法根据实体对象或者id来更新或者删除实体。
* 提供 `DeleteAsync` 方法用来删除符合指定条件的多个实体。
### 在实体中使用LINQ
仓储提供了一个`GetQueryableAsync`方法来获取一个`IQueryable<TEntity>`对象。你可以通过这个对象来对实体执行LINQ查询以操作数据库。
**示例: 在仓储中使用LINQ表达式**
````csharp
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace Demo
{
public class PersonAppService : ApplicationService
{ {
var people = _personRepository private readonly IRepository<Person, Guid> _personRepository;
.Where(p => p.Name.Contains(nameFilter))
.ToList(); public PersonAppService(IRepository<Person, Guid> personRepository)
{
_personRepository = personRepository;
}
return people public async Task<List<PersonDto>> GetListAsync(string filter)
.Select(p => new PersonDto {Id = p.Id, Name = p.Name, Age = p.Age}) {
.ToList(); // 获取 IQueryable<Person>
IQueryable<Person> queryable = await _personRepository.GetQueryableAsync();
// 创建一个查询
var query = from person in queryable
where person.Name == filter
orderby person.Name
select person;
// 执行查询
var people = query.ToList();
// 转DTO并返回给客户端
return people.Select(p => new PersonDto {Name = p.Name}).ToList();
}
} }
} }
```` ````
> 参阅 "*IQueryable & 异步操作*" 部分了解如何使用 **异步扩展方法**, 如 `ToListAsync()` (建议始终使用异步) 而不是 `ToList()`. 你也可以使用LINQ扩展方法:
在这个例子中; ````csharp
public async Task<List<PersonDto>> GetListAsync(string filter)
{
// 获取 IQueryable<Person>
IQueryable<Person> queryable = await _personRepository.GetQueryableAsync();
* `PersonAppService` 在它的构造函数中注入了 `IRepository<Person, Guid>` . // 创建一个查询
* `Create` 方法使用了 `InsertAsync` 创建并保存新的实体. var people = queryable
* `GetList` 方法使用标准LINQ `Where` 和 `ToList` 方法在数据源中过滤并获取People集合. .Where(p => p.Name.Contains(filter))
.OrderBy(p => p.Name)
.ToList();
> 上面的示例在[实体](Entities.md)与[DTO](Data-Transfer-Objects.md)之间使用了手动映射. 参阅 [对象映射](Object-To-Object-Mapping.md) 了解自动映射的使用方式. // 转DTO并返回给客户端
return people.Select(p => new PersonDto {Name = p.Name}).ToList();
}
````
通用仓储提供了一些开箱即用的标准CRUD功能: 你可以使用仓储返回的`IQueryable` 配合标准LINQ方法自由查询。
* 提供 `Insert` 方法用于保存新实体. > 在这个例子中使用了 `ToList()` 方法, 但是**强烈建议使用异步方法**来执行数据库查询,比如在这个例子中,可以使用 `ToListAsync()`
* 提供 `Update` 和 `Delete` 方法通过实体或实体id更新或删除实体. >
* 提供 `Delete` 方法使用条件表达式过滤删除多个实体. > 查看 **IQueryable & 异步操作** 小节来学习如何做到这一点。
* 实现了 `IQueryable<TEntity>`, 所以你可以使用LINQ和扩展方法 `FirstOrDefault`, `Where`, `OrderBy`, `ToList` 等...
* 所有方法都具有 **sync(同步)** 和 **async(异步)** 版本.
### 基础仓储 ### 批量操作
下面这些方法可以用来对数据库执行批量操作;
* `InsertManyAsync`
* `UpdateManyAsync`
* `DeleteManyAsync`
这些方法可以操作多个实体,如果底层数据库提供程序支持,则可以进行批量操作。
> 使用`UpdateManyAsync`和`DeleteManyAsSync`方法时,乐观锁可能会失效。
### 软 / 硬 删除
`IRepository<TEntity, TKey>` 接口扩展了标准 `IQueryable<TEntity>` 你可以使用标准LINQ方法自由查询.但是,某些ORM提供程序或数据库系统可能不支持`IQueryable`接口. 如果一个实体是**软删除**实体(即实现了`ISoftDelete`接口),则仓储的`DeleteSync`方法不会删除该实体,而是在数据库中标记为“已删除”。数据过滤器系统确保不会从数据库中正常检索软删除的实体。
如果您的实体是软删除实体,如果您需要物理删除这个实体,您可以使用`HardDeleteAsync`方法强制删除。
> 阅读 [数据过滤](Data-Filtering.md) 文档以了解更多关于软删除。
### 确保实体存在
`EnsureExistsAsync`扩展方法通过实体id或实体查询表达式来确保实体存在,如果其不存在,它将抛出`EntityNotFoundException`异常。
## 其他通用仓储类型
`IRepository<TEntity, TKey>` 接口扩展了标准 `IQueryable<TEntity>` 你可以使用标准LINQ方法自由查询。这对于大多数应用程序都很好。但是,某些ORM提供程序或数据库系统可能不支持`IQueryable`接口。如果您想使用这样的提供者,就不能依赖`IQueryable`。
### 基础仓储
ABP提供了 `IBasicRepository<TEntity, TPrimaryKey>` 和 `IBasicRepository<TEntity>` 接口来支持这样的场景. 你可以扩展这些接口(并可选择性地从`BasicRepositoryBase`派生)为你的实体创建自定义存储库. ABP提供了 `IBasicRepository<TEntity, TPrimaryKey>` 和 `IBasicRepository<TEntity>` 接口来支持这样的场景. 你可以扩展这些接口(并可选择性地从`BasicRepositoryBase`派生)为你的实体创建自定义存储库.
依赖于 `IBasicRepository` 而不是依赖 `IRepository` 有一个优点, 即使它们不支持 `IQueryable` 也可以使用所有的数据源, 但主要的供应商, 像 Entity Framework, NHibernate 或 MongoDb 已经支持了 `IQueryable`. 依赖于 `IBasicRepository` 而不是依赖 `IRepository` 有一个优点, 即使它们不支持 `IQueryable` 也可以使用所有的数据源。
但主要的供应商, 像 Entity Framework, NHibernate 或 MongoDb 已经支持了 `IQueryable`.
因此, 使用 `IRepository` 是典型应用程序的 **建议方法**. 但是可重用的模块开发人员可能会考虑使用 `IBasicRepository` 来支持广泛的数据源. 因此, 使用 `IRepository` 是典型应用程序的 **建议方法**. 但是可重用的模块开发人员可能会考虑使用 `IBasicRepository` 来支持广泛的数据源.
### 只读仓储 ### 只读仓储
对于想要使用只读仓储的开发者,我们提供了`IReadOnlyRepository<TEntity, TKey>` 与 `IReadOnlyBasicRepository<Tentity, TKey>`接口. 对于想要使用只读仓储的开发者,我们提供了`IReadOnlyRepository<TEntity, TKey>` 与 `IReadOnlyBasicRepository<Tentity, TKey>`接口。
### 无主键的通用(泛型)仓储 ### 无主键的通用(泛型)仓储
如果你的实体没有id主键 (例如, 它可能具有复合主键) 那么你不能使用上面定义的 `IRepository<TEntity, TKey>`, 在这种情况下你可以仅使用实体(类型)注入 `IRepository<TEntity>`. 如果你的实体没有id主键 (例如, 它可能具有复合主键) 那么你不能使用上面定义的 `IRepository<TEntity, TKey>`, 在这种情况下你可以仅使用实体(类型)注入 `IRepository<TEntity>`。
> `IRepository<TEntity>` 有一些缺失的方法, 通常与实体的 `Id` 属性一起使用. 由于实体在这种情况下没有 `Id` 属性, 因此这些方法不可用. 比如 `Get` 方法通过id获取具有指定id的实体. 不过, 你仍然可以使用`IQueryable<TEntity>`的功能通过标准LINQ方法查询实体.
> `IRepository<TEntity>` 有一些缺失的方法, 通常与实体的 `Id` 属性一起使用. 由于实体在这种情况下没有 `Id` 属性, 因此这些方法不可用. 比如 `Get` 方法通过id获取具有指定id的实体. 不过, 你仍然可以使用`IQueryable<TEntity>`的功能通过标准LINQ方法查询实体。
### 自定义仓储 ### 自定义仓储
对于大多数情况, 默认通用仓储就足够了. 但是, 你可能会需要为实体创建自定义仓储类. 对于大多数情况, 默认通用仓储就足够了。但是, 你可能会需要为实体创建自定义仓储类。
#### 自定义仓储示例 #### 自定义仓储示例
ABP不会强制你实现任何接口或从存储库的任何基类继承. 它可以只是一个简单的POCO类. 但是建议继承现有的仓储接口和类, 获得开箱即用的标准方法使你的工作更轻松. ABP不会强制你实现任何接口或从存储库的任何基类继承。它可以只是一个简单的POCO类。 但是建议继承现有的仓储接口和类,获得开箱即用的标准方法使你的工作更轻松。
#### 自定义仓储接口 #### 自定义仓储接口
@ -101,11 +212,11 @@ public interface IPersonRepository : IRepository<Person, Guid>
} }
``` ```
此接口扩展了 `IRepository<Person, Guid>` 以使用已有的通用仓储功能. 此接口扩展了 `IRepository<Person, Guid>` 以使用已有的通用仓储功能。
#### 自定义仓储实现 #### 自定义仓储实现
自定义存储库依赖于你使用的数据访问工具. 在此示例中, 我们将使用Entity Framework Core: 自定义存储库依赖于你使用的数据访问工具。 在此示例中, 我们将使用Entity Framework Core:
````C# ````C#
public class PersonRepository : EfCoreRepository<MyDbContext, Person, Guid>, IPersonRepository public class PersonRepository : EfCoreRepository<MyDbContext, Person, Guid>, IPersonRepository
@ -126,18 +237,20 @@ public class PersonRepository : EfCoreRepository<MyDbContext, Person, Guid>, IPe
} }
```` ````
你可以直接使用数据库访问提供程序 (本例中是 `DbContext` ) 来执行操作. 你可以直接使用数据库访问提供程序 (本例中是 `dbContext` ) 来执行操作.
> 请参阅[EF Core](Entity-Framework-Core.md)或[MongoDb](MongoDB.md)了解如何自定义仓储. > 请参阅[EF Core](Entity-Framework-Core.md)或[MongoDb](MongoDB.md)了解如何自定义仓储.
## IQueryable & 异步操作 ## IQueryable & 异步操作
`IRepository` 继承自 `IQueryable`,这意味着你可以**直接使用LINQ扩展方法**. 如上面的*泛型仓储*示例. `IRepository`提供`GetQueryableAsync()`来获取`IQueryable`,这意味着您可以**直接在其上使用LINQ扩展方法**,如上面的“*在实体中使用LINQ”部分所示。
**示例: 使用 `Where(...)` 和 `ToList()` 扩展方法** **示例: 使用 `Where(...)` 和 `ToList()` 扩展方法**
````csharp ````csharp
var people = _personRepository var queryable = await _personRepository.GetQueryableAsync();
var people = queryable
.Where(p => p.Name.Contains(nameFilter)) .Where(p => p.Name.Contains(nameFilter))
.ToList(); .ToList();
```` ````
@ -157,7 +270,7 @@ var people = _personRepository
### 选项-1: 引用EF Core ### 选项-1: 引用EF Core
最简单的方法是在你想要使用异步方法的项目直接引用EF Core包. **最简单的方法**是在你想要使用异步方法的项目直接引用EF Core包.
> 添加[Volo.Abp.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore)NuGet包到你的项目间接引用EF Core包. 这可以确保你的应用程序其余部分兼容正确版本的EF Core. > 添加[Volo.Abp.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore)NuGet包到你的项目间接引用EF Core包. 这可以确保你的应用程序其余部分兼容正确版本的EF Core.
@ -166,14 +279,15 @@ var people = _personRepository
**示例: 直接使用 `ToListAsync()`** **示例: 直接使用 `ToListAsync()`**
````csharp ````csharp
var people = _personRepository var queryable = await _personRepository.GetQueryableAsync();
var people = queryable
.Where(p => p.Name.Contains(nameFilter)) .Where(p => p.Name.Contains(nameFilter))
.ToListAsync(); .ToListAsync();
```` ````
此方法建议; 当以下情况时,这个方法是推荐的:
* 如果你正在开发一个应用程序并且**不打算在将来** 更新FE Core,或者如果以后需要更改,你可以**容忍**它. 如果你正在开发最终的应用程序,这是合理的. * 如果你正在开发一个应用程序并且**不打算在将来** 更新FE Core,或者如果以后真的需要更改,你也能**容忍**它。我们认为,如果您正在开发最终应用程序,这是合理的。
#### MongoDB #### MongoDB
@ -182,19 +296,41 @@ var people = _personRepository
**示例: 转换Cast `IQueryable<T>` 为 `IMongoQueryable<T>` 并且使用 `ToListAsync()`** **示例: 转换Cast `IQueryable<T>` 为 `IMongoQueryable<T>` 并且使用 `ToListAsync()`**
````csharp ````csharp
var people = ((IMongoQueryable<Person>)_personRepository var queryable = await _personRepository.GetQueryableAsync();
var people = ((IMongoQueryable<Person>) queryable
.Where(p => p.Name.Contains(nameFilter))) .Where(p => p.Name.Contains(nameFilter)))
.ToListAsync(); .ToListAsync();
```` ````
### 选项-2: 自定义仓储方法 ### 选项-2: 使用IRepository异步扩展方法
你始终可以创建自定义仓储方法并使用特定数据库提供程序的API,比如这里的异步扩展方法. 有关自定义存储库的更多信息,请参阅[EF Core](Entity-Framework-Core.md)或[MongoDb](MongoDB.md)文档. ABP框架为仓储提供异步扩展方法,与异步LINQ扩展方法类似。
此方法建议; **示例: 在仓储中使用 `CountAsync` 和 `FirstOrDefaultAsync` 方法 **
* 如果你想**完全隔离**你的领域和应用层和数据库提供程序. ````csharp
* 如果你开发可**重用的[应用模块](Modules/Index.md)**,并且不想强制使用特定的数据库提供程序,这应该作为一种[最佳实践](Best-Practices/Index.md). var countAll = await _personRepository
.CountAsync();
var count = await _personRepository
.CountAsync(x => x.Name.StartsWith("A"));
var book1984 = await _bookRepository
.FirstOrDefaultAsync(x => x.Name == "John");
````
支持这些标准的LINQ方法: *AllAsync, AnyAsync, AverageAsync, ContainsAsync, CountAsync, FirstAsync, FirstOrDefaultAsync, LastAsync, LastOrDefaultAsync, LongCountAsync, MaxAsync, MinAsync, SingleAsync, SingleOrDefaultAsync, SumAsync, ToArrayAsync, ToListAsync*.
这种方法仍有**局限性**。您需要直接在存储库对象上调用扩展方法。例如,以下用法**不受支持**:
```csharp
var queryable = await _bookRepository.GetQueryableAsync();
var count = await queryable.Where(x => x.Name.Contains("A")).CountAsync();
```
这是因为本例中的`CountAsync()`方法是在`IQueryable`接口上调用的,而不是在存储库对象上调用的。请参见此类情况的其他选项。
建议 **尽可能使用此方法**.
### 选项-3: IAsyncQueryableExecuter ### 选项-3: IAsyncQueryableExecuter
@ -229,8 +365,11 @@ namespace AbpDemo
public async Task<ListResultDto<ProductDto>> GetListAsync(string name) public async Task<ListResultDto<ProductDto>> GetListAsync(string name)
{ {
//Obtain the IQueryable<T>
var queryable = await _productRepository.GetQueryableAsync();
//Create the query //Create the query
var query = _productRepository var query = queryable
.Where(p => p.Name.Contains(name)) .Where(p => p.Name.Contains(name))
.OrderBy(p => p.Name); .OrderBy(p => p.Name);
@ -245,10 +384,21 @@ namespace AbpDemo
> `ApplicationService` 和 `DomainService` 基类已经预属性注入了 `AsyncExecuter` 属性,所以你可直接使用. > `ApplicationService` 和 `DomainService` 基类已经预属性注入了 `AsyncExecuter` 属性,所以你可直接使用.
ABP框架使用实际数据库提供程序的API异步执行查询.虽然这不是执行查询的常见方式,但它是使用异步API而不依赖于数据库提供者的最佳方式. ABP框架使用实际数据库提供程序的API异步执行查询。虽然这不是执行查询的常见方式,但它是使用异步API而不依赖于数据库提供者的最佳方式。
此方法建议; 当以下情况时,这个方法是推荐的:
* 如果您想开发应用程序代码**而不依赖**数据库提供程序。
* 如果你正在构建一个没有数据库提供程序集成包的**可重用库**,但是在某些情况下需要执行 `IQueryable<T>`对象. * 如果你正在构建一个没有数据库提供程序集成包的**可重用库**,但是在某些情况下需要执行 `IQueryable<T>`对象.
例如,ABP框架在 `CrudAppService` 基类中(参阅[应用程序](Application-Services.md)文档)使用 `IAsyncQueryableExecuter`. 例如,ABP框架在 `CrudAppService` 基类中(参阅[应用程序](Application-Services.md)文档)使用 `IAsyncQueryableExecuter`.
### 选项-4: 自定义仓储方法
你始终可以创建自定义仓储方法并使用特定数据库提供程序的API,比如这里的异步扩展方法. 有关自定义存储库的更多信息,请参阅[EF Core](Entity-Framework-Core.md)或[MongoDb](MongoDB.md)文档.
当以下情况时,这个方法是推荐的:
* 如果你想**完全隔离**你的领域和应用层和数据库提供程序.
* 如果你开发可**重用的[应用模块](Modules/Index.md)**,并且不想强制使用特定的数据库提供程序,这应该作为一种[最佳实践](Best-Practices/Index.md).

Loading…
Cancel
Save