diff --git a/docs/en/Entity-Framework-Core.md b/docs/en/Entity-Framework-Core.md index 989123c060..ab6d3b6adb 100644 --- a/docs/en/Entity-Framework-Core.md +++ b/docs/en/Entity-Framework-Core.md @@ -425,10 +425,10 @@ namespace AbpDemo.Orders { //Get a IQueryable by including sub collections var queryable = await _orderRepository.WithDetailsAsync(x => x.Lines); - + //Apply additional LINQ extension methods var query = queryable.Where(x => x.Id == id); - + //Execute the query and get the result var order = await AsyncExecuter.FirstOrDefaultAsync(query); } @@ -576,7 +576,7 @@ Configure(options => { opts.DbContextOptions.UseLazyLoadingProxies(); //Enable lazy loading }); - + options.UseSqlServer(); }); ```` @@ -874,7 +874,7 @@ Configure(options => ### Customize Bulk Operations -If you have better logic or using an external library for bulk operations, you can override the logic via implementing`IEfCoreBulkOperationProvider`. +If you have better logic or using an external library for bulk operations, you can override the logic via implementing `IEfCoreBulkOperationProvider`. - You may use example template below: @@ -920,4 +920,3 @@ public class MyCustomEfCoreBulkOperationProvider ## See Also * [Entities](Entities.md) -* [Repositories](Repositories.md) diff --git a/docs/zh-Hans/Entity-Framework-Core.md b/docs/zh-Hans/Entity-Framework-Core.md index 78df73150c..036998bc73 100644 --- a/docs/zh-Hans/Entity-Framework-Core.md +++ b/docs/zh-Hans/Entity-Framework-Core.md @@ -30,9 +30,7 @@ namespace MyCompany.MyProject ### 数据库管理系统选择 -EF Core支持多种数据库管理系统([查看全部](https://docs.microsoft.com/zh-cn/ef/core/providers/)). ABP框架和本文档不依赖于任何特定的DBMS. - -如果要创建一个可重用的[应用程序模块](Modules/Index.md),应避免依赖于特定的DBMS包.但在最终的应用程序中,始终会选择一个DBMS. +EF Core支持多种数据库管理系统([查看全部](https://docs.microsoft.com/zh-cn/ef/core/providers/)). ABP框架和本文档不依赖于任何特定的DBMS. 如果要创建一个可重用的[应用程序模块](Modules/Index.md),应避免依赖于特定的DBMS包.但在最终的应用程序中,始终会选择一个DBMS. 参阅[为Entity Framework Core切换到其他DBMS](Entity-Framework-Core-Other-DBMS.md)文档学习如何切换DBMS. @@ -107,8 +105,7 @@ protected override void OnModelCreating(ModelBuilder builder) ### 配置连接字符串选择 -如果你的应用程序有多个数据库,你可以使用 `connectionStringName]` Attribute为你的DbContext配置连接字符串名称. -例: +如果你的应用程序有多个数据库,你可以使用 `[connectionStringName]` Attribute为你的DbContext配置连接字符串名称.例: ```csharp [ConnectionStringName("MySecondConnString")] @@ -120,6 +117,62 @@ public class MyDbContext : AbpDbContext 如果不进行配置,则使用`Default`连接字符串. 如果你配置特定的连接字符串的名称,但在应用程序配置中没有定义这个连接字符串名称,那么它会回退到`Default`连接字符串(参阅[连接字符串文档](Connection-Strings.md)了解更多信息). +### AbpDbContextOptions + +`AbpDbContextOptions` 用于配置 `DbContext`. 当你使用ABP的应用程序启动模板新建解决方案时, 你会看到一个简单的配置 (在 `EntityFrameworkCore` 集成项目模块类) 如下: + +````csharp +Configure(options => +{ + options.UseSqlServer(); +}); +```` + +上面的配置为应用程序的所有 `DbContext`使用SQL Server作为默认DBMS. 上面的配置是简化的写法, 它也可以使用下面的方法进行配置: + +````csharp +Configure(options => +{ + options.Configure(opts => + { + opts.UseSqlServer(); + }); +}); +```` + +`options.Configure(...)` 方法有更多的选项进行配置. 例如, 你可以设置 `DbContextOptions` (EF Core自有的配置): + +````csharp +Configure(options => +{ + options.Configure(opts => + { + opts.DbContextOptions.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + }); +}); +```` + +如果你有唯一的 `DbContext` 或者有多个 `DbContext`, 但是希望对所有的 `DbContext` 使用相同的DBMS, 你无需更多的配置. 但是, 如果你需要为某个特定的 `DbContext` 配置不同的DBMS或对配置进行定制, 你可以进行如下定制: + +````csharp +Configure(options => +{ + // Default configuration for all DbContexts + options.Configure(opts => + { + opts.UseSqlServer(); + }); + + // Customized configuration for a specific DbContext + options.Configure(opts => + { + opts.UseMySQL(); + }); +}); +```` + +> 参阅 [为Entity Framework Core切换DBMS](Entity-Framework-Core-Other-DBMS.md) 文档学习如何配置DBMS. + ## 将DbContext注册到依赖注入 在module中的ConfigureServices方法使用 `AddAbpDbContext` 在[依赖注入](Dependency-Injection.md)系统注册DbContext类. @@ -155,8 +208,7 @@ services.AddAbpDbContext(options => }); ```` -默认情况下为每个[聚合根实体](Entities.md)(`AggregateRoot`派生的子类)创建一个仓储. 如果想要为其他实体也创建仓储 -请将`includeAllEntities` 设置为 `true`: +默认情况下为每个[聚合根实体](Entities.md)(`AggregateRoot`派生的子类)创建一个仓储. 如果想要为其他实体也创建仓储, 请将`includeAllEntities` 设置为 `true`: ````C# services.AddAbpDbContext(options => @@ -165,9 +217,7 @@ services.AddAbpDbContext(options => }); ```` -然后你就可以在服务中注入和使用 `IRepository` 或 `IQueryableRepository`. - -假如你有一个主键是Guid名为Book实体(聚合根) +然后你就可以在服务中注入和使用 `IRepository` 或 `IQueryableRepository`. 假如你有一个主键是Guid名为Book实体(聚合根) ```csharp public class Book : AggregateRoot @@ -185,7 +235,8 @@ public class BookManager : DomainService { private readonly IRepository _bookRepository; - public BookManager(IRepository bookRepository) //注入默认仓储 + //inject default repository to the constructor + public BookManager(IRepository bookRepository) { _bookRepository = bookRepository; } @@ -201,7 +252,8 @@ public class BookManager : DomainService Type = type }; - await _bookRepository.InsertAsync(book); //使用仓储提供的标准方法 + //Use a standard repository method + await _bookRepository.InsertAsync(book); return book; } @@ -212,9 +264,9 @@ public class BookManager : DomainService #### 添加自定义仓储 -默认通用仓储可以满足大多数情况下的需求(它实现了`IQueryable`),但是你可能会需要自定义仓储与仓储方法. +默认通用仓储可以满足大多数情况下的需求(它实现了`IQueryable`),但是你可能会需要自定义仓储与仓储方法. 假设你需要根据图书类型删除所有的书籍. -假设你需要根据图书类型删除所有的书籍. 建议为自定义仓储定义一个接口: +建议为自定义仓储定义一个接口: ````csharp public interface IBookRepository : IRepository @@ -228,7 +280,8 @@ public interface IBookRepository : IRepository IBookRepository接口的实现示例: ````csharp -public class BookRepository : EfCoreRepository, IBookRepository +public class BookRepository + : EfCoreRepository, IBookRepository { public BookRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) @@ -237,7 +290,8 @@ public class BookRepository : EfCoreRepository, public async Task DeleteBooksByType(BookType type) { - await DbContext.Database.ExecuteSqlRawAsync( + var dbContext = await GetDbContextAsync(); + await dbContext.Database.ExecuteSqlRawAsync( $"DELETE FROM Books WHERE Type = {(int)type}" ); } @@ -256,11 +310,13 @@ public class BookRepository : EfCoreRepository, context.Services.AddAbpDbContext(options => { options.AddDefaultRepositories(); + + //Replaces IRepository options.AddRepository(); }); ```` -在你想要覆盖默认仓储方法对其自定义时,这一点非常需要. 例如你可能希望自定义`DeleteAsync`方法覆盖默认实现 +在你想要覆盖默认仓储方法对其自定义时,这一点非常需要. 例如你可能希望自定义`DeleteAsync`方法覆盖默认实现, 以使用更有效的方式删除特定的实体. ````csharp public async override Task DeleteAsync( @@ -369,10 +425,10 @@ namespace AbpDemo.Orders { //通过包含子集合获取一个 IQueryable var queryable = await _orderRepository.WithDetailsAsync(x => x.Lines); - + //应用其他的 LINQ 扩展方法 var query = queryable.Where(x => x.Id == id); - + //执行此查询并获取结果 var order = await AsyncExecuter.FirstOrDefaultAsync(query); } @@ -380,7 +436,7 @@ namespace AbpDemo.Orders } ```` -> `AsyncExecuter` 用于执行异步 LINQ 扩展,而无需依赖 EF Core. 如果你将 EF Core NuGet 包引用添加到你的项目中,则可以直接使用 `await query.FirstOrDefaultAsync()`. 但是, 这次你依赖于域层中的 EF 核心.请参阅. 请参阅 [仓储文档](Repositories.md) 以了解更多. +> `AsyncExecuter` 用于执行异步 LINQ 扩展,而无需依赖 EF Core. 如果你将 EF Core NuGet 包引用添加到你的项目中,则可以直接使用 `await query.FirstOrDefaultAsync()`. 但是, 这次你依赖于域层中的 EF Core. 请参阅 [仓储文档](Repositories.md) 以了解更多. **示例: 获取一个包含 `lines` 的 `orders` 列表** @@ -401,7 +457,7 @@ public async Task TestWithDetails() 如果你没有将任何表达式传递到 `WithDetailsAsync` 方法,则它包括使用你提供的 `DefaultWithDetailsFunc` 选项的所有详细信息. -你可以在你的 `EntityFrameworkCore` 项目[模块](Module-Development-Basics.md)的 `ConfigureServices`方法为一个实体配置 `DefaultWithDetailsFunc`. +你可以在你的 `EntityFrameworkCore` 项目[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法为一个实体配置 `DefaultWithDetailsFunc`. **示例: 在查询一个 `Order` 时包含 `Lines`** @@ -470,7 +526,7 @@ public async Task TestWithDetails() } ```` -#### 选择 +#### 更多的替代选择 存储库模式尝试封装 EF Core, 因此你的选项是有限的. 如果你需要高级方案,你可以按照其中一个选项执行: @@ -479,7 +535,7 @@ public async Task TestWithDetails() 请参阅 EF Core 的 [预先加载文档](https://docs.microsoft.com/zh-cn/ef/core/querying/related-data/eager). -### 显示 / 延迟加载 +### 显式 / 延迟加载 如果你在查询实体时不包括关系,并且以后需要访问导航属性或集合,则你有不同的选择. @@ -506,7 +562,7 @@ public async Task TestWithDetails(Guid id) #### 使用代理的延时加载 -在某些情况下,可能无法使用显示加载,尤其是当你没有引用 `Repository` 或 `DbContext`时.延时加载是 EF Core 加载关联属性/集合的一个功能,,当你第一次访问它. +在某些情况下,可能无法使用显式加载,尤其是当你没有引用 `Repository` 或 `DbContext`时.延时加载是 EF Core 加载关联属性/集合的一个功能, 当你第一次访问它. 启用延时加载: @@ -520,7 +576,7 @@ Configure(options => { opts.DbContextOptions.UseLazyLoadingProxies(); //启用延时加载 }); - + options.UseSqlServer(); }); ```` @@ -553,35 +609,26 @@ public async Task TestWithDetails(Guid id) ## 访问 EF Core API -大多数情况下应该隐藏仓储后面的EF Core API(这也是仓储的设计目地). 但是如果想要通过仓储访问DbContext实现,则可以使用`GetDbContext()`或`GetDbSet()`扩展方法. 例: +大多数情况下应该隐藏仓储后面的EF Core API(这也是仓储的设计目的). 但是如果想要通过仓储访问DbContext实现,则可以使用`GetDbContext()`或`GetDbSet()`扩展方法. 例如: ````csharp -public class BookService +public async Task TestAsync() { - private readonly IRepository _bookRepository; - - public BookService(IRepository bookRepository) - { - _bookRepository = bookRepository; - } - - public void Foo() - { - DbContext dbContext = _bookRepository.GetDbContext(); - DbSet books = _bookRepository.GetDbSet(); - } + var dbContext = await _orderRepository.GetDbContextAsync(); + var dbSet = await _orderRepository.GetDbSetAsync(); + //var dbSet = dbContext.Set(); //Alternative, when you have the DbContext } ```` -* `GetDbContext` 返回 `DbContext` 引用,而不是 `BookStoreDbContext`. 你可以释放它, 但大多数情况下你不会需要它. +* `GetDbContext` 返回 `DbContext` 引用,而不是 `BookStoreDbContext`. 你可以强制转化它, 但大多数情况下你不会需要它. > 要点: 你必须在使用`DbContext`的项目里引用`Volo.Abp.EntityFrameworkCore`包. 这会破坏封装,但在这种情况下,这就是你需要的. -## Extra Properties & Object Extension Manager +## 额外属性 & Object Extension Manager 额外属性系统允许你为实现了 `IHasExtraProperties` 的实体set/get动态属性. 当你想将自定义属性添加到[应用程序模块](Modules/Index.md)中定义的实体时,它特别有用. -默认,实体的所有额外属性存储在数据库的一个 `JSON` 对象中. +默认情况下, 实体的所有额外属性存储在数据库的一个 `JSON` 对象中. 实体扩展系统允许你存储额外属性在数据库的单独字段中. 有关额外属性和实体扩展系统的更多信息,请参阅下列文档: @@ -611,28 +658,100 @@ ObjectExtensionManager.Instance ); ```` +### MapEfCoreEntity + +`MapEfCoreEntity` 一个配置 `Entity` 的快捷扩展方法. + +**示例**: 设置 `IdentityRole` 实体的 `Name` 的最大长度: + +````csharp +ObjectExtensionManager.Instance + .MapEfCoreEntity(builder => + { + builder.As>().Property(x => x.Name).HasMaxLength(200); + }); +```` + +### MapEfCoreDbContext + +`MapEfCoreDbContext` 一个配置 `DbContext` 的快捷扩展方法. + +**示例**: 设置 `IdentityDbContext` 的 `IdentityRole` 实体的 `IdentityRole` 的最大长度: + +````csharp +ObjectExtensionManager.Instance.MapEfCoreDbContext(b => +{ + b.Entity().Property(x => x.Name).HasMaxLength(200); +}); +```` + 如果相关模块已实现此功能(通过使用下面说明的 `ConfigureEfCoreEntity`)则将新属性添加到模型中. 然后你需要运行标准的 `Add-Migration` 和 `Update-Database` 命令更新数据库以添加新字段. ->`MapEfCoreProperty` 方法必须在使用相关的 `DbContext` 之前调用,它是一个静态方法. 最好的方法是尽早的应用程序中使用它. 应用程序启动模板含有 `YourProjectNameEntityExtensions` 类,可以在放心的在此类中使用此方法. +>`MapEfCoreProperty`, `MapEfCoreEntity` and `MapEfCoreDbContext` 方法必须在使用相关的 `DbContext` 之前调用,它是一个静态方法. 最好的方法是尽早的应用程序中使用它. 应用程序启动模板含有 `YourProjectNameEfCoreEntityExtensionMappings` 类,可以在放心的在此类中使用此方法. -### ConfigureEfCoreEntity +### ConfigureEfCoreEntity, ApplyObjectExtensionMappings 和 TryConfigureObjectExtensions 如果你正在开发一个可重用使用的模块,并允许应用程序开发人员将属性添加到你的实体,你可以在实体映射使用 `ConfigureEfCoreEntity` 扩展方法,但是在配置实体映射时可以使用快捷的扩展方法 `ConfigureObjectExtensions`: +**示例**: ````csharp -builder.Entity(b => +public static class QADbContextModelCreatingExtensions { - b.ConfigureObjectExtensions(); - //... -}); + public static void ConfigureQA( + this ModelBuilder builder, + Action optionsAction = null) + { + Check.NotNull(builder, nameof(builder)); + + var options = new QAModelBuilderConfigurationOptions( + QADatabaseDbProperties.DbTablePrefix, + QADatabaseDbProperties.DbSchema + ); + + optionsAction?.Invoke(options); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "Questions", options.Schema); + b.ConfigureByConvention(); + //... + + //Call this in the end of buildAction. + b.ApplyObjectExtensionMappings(); + }); + + //... + + //Call this in the end of ConfigureQA. + builder.TryConfigureObjectExtensions(); + } +} ```` -如果你调用 `ConfigureByConvention()` 扩展方法(在此示例中 `b.ConfigureByConvention`),ABP框架内部会调用 `ConfigureObjectExtensions` 方法. 使用 `ConfigureByConvention` 方法是**最佳实践**,因为它还按照约定配置基本属性的数据库映射. +如果你调用 `ConfigureByConvention()` 扩展方法(在此示例中 `b.ConfigureByConvention`),ABP框架内部会调用 `ConfigureObjectExtensions` 和 `ConfigureEfCoreEntity` 方法. 使用 `ConfigureByConvention` 方法是**最佳实践**,因为它还按照约定配置基本属性的数据库映射. 参阅上面提到的 "*ConfigureByConvention 方法*" 了解更多信息. ## 高级主题 +### 控制多租户 + +如果你的方案是基于 [多租户](Multi-Tenancy.md)的, 租户可以拥有 **独立数据库**, 你在解决方案中可以拥有 **多个** `DbContext` 类, 并且其中的一些 `DbContext` 类 **只能在主机端** 可用, 这种情况下建议在 `DbContext` 类上添加 `[IgnoreMultiTenancy]` 属性. ABP 保证相关的 `DbContext` 始终使用主机 [连接字符串](Connection-Strings.md), 即使你在租户上下文中. + +**示例:** + +````csharp +[IgnoreMultiTenancy] +public class MyDbContext : AbpDbContext +{ + ... +} +```` + +不要使用 `[IgnoreMultiTenancy]` 特性如果 `DbContext` 中任何一个实体可以被持久化到多租户数据库中. + +> 当你使用repositories时, ABP 已经为未实现`IMultiTenant`接口的实体使用了主机数据库. 所以, 如果你使用repositories访问数据库, 多数时候你不需要 `[IgnoreMultiTenancy]` 特性. + ### 设置默认仓储类 默认的通用仓储的默认实现是`EfCoreRepository`类,你可以创建自己的实现,并将其做为默认实现 @@ -674,6 +793,7 @@ context.Services.AddAbpDbContext(options => typeof(MyRepositoryBase<,>), typeof(MyRepositoryBase<>) ); + //... }); ``` @@ -736,6 +856,68 @@ context.Services.AddAbpDbContext(options => 在这个例子中,`OtherDbContext`实现了`IBookStoreDbContext`. 此功能允许你在开发时使用多个DbContext(每个模块一个),但在运行时可以使用单个DbContext(实现所有DbContext的所有接口). +### 拆分查询 + +ABP 为了更好的性能, 默认全局启用 [拆分查询](https://docs.microsoft.com/en-us/ef/core/querying/single-split-queries). 你可以按需修改. + +**示例** + +````csharp +Configure(options => +{ + options.UseSqlServer(optionsBuilder => + { + optionsBuilder.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery); + }); +}); +```` + +### 自定义批量操作 + +如果你有更好的逻辑或使用外部库实现批量操作, 你可以通过实现 `IEfCoreBulkOperationProvider` 覆写这个逻辑. + +- 你可以使用下面的示例模板: + +```csharp +public class MyCustomEfCoreBulkOperationProvider + : IEfCoreBulkOperationProvider, ITransientDependency +{ + public async Task DeleteManyAsync( + IEfCoreRepository repository, + IEnumerable entities, + bool autoSave, + CancellationToken cancellationToken) + where TDbContext : IEfCoreDbContext + where TEntity : class, IEntity + { + // Your logic here. + } + + public async Task InsertManyAsync( + IEfCoreRepository repository, + IEnumerable entities, + bool autoSave, + CancellationToken cancellationToken) + where TDbContext : IEfCoreDbContext + where TEntity : class, IEntity + { + // Your logic here. + } + + public async Task UpdateManyAsync( + IEfCoreRepository repository, + IEnumerable entities, + bool autoSave, + CancellationToken cancellationToken) + where TDbContext : IEfCoreDbContext + where TEntity : class, IEntity + { + // Your logic here. + } +} +``` + ## 另请参阅 * [实体](Entities.md) +* [仓储](Repositories.md)