Entity Framework Core 集成

    是EF Core 集成的主要nuget包. 将其安装到你的项目中(在分层应用程序中适用于 数据访问/基础设施层):

    然后添加 AbpEntityFrameworkCoreModule 模块依赖项(DependsOn Attribute) 到 module(项目中的Mudole类):

    1. using Volo.Abp.EntityFrameworkCore;
    2. using Volo.Abp.Modularity;
    3. namespace MyCompany.MyProject
    4. {
    5. [DependsOn(typeof(AbpEntityFrameworkCoreModule))]
    6. public class MyModule : AbpModule
    7. {
    8. //...
    9. }
    10. }

    EF Core支持多种数据库管理系统(). ABP框架和本文档不依赖于任何特定的DBMS.

    如果要创建一个可重用的应用程序模块,应避免依赖于特定的DBMS包.但在最终的应用程序中,始终会选择一个DBMS.

    参阅文档学习如何切换DBMS.

    创建 DbContext

    你可以平常一样创建DbContext,它需要继承自 AbpDbContext<T>. 如下所示:

    1. using Microsoft.EntityFrameworkCore;
    2. using Volo.Abp.EntityFrameworkCore;
    3. namespace MyCompany.MyProject
    4. {
    5. public class MyDbContext : AbpDbContext<MyDbContext>
    6. {
    7. //...在这里添加 DbSet properties
    8. public MyDbContext(DbContextOptions<MyDbContext> options)
    9. : base(options)
    10. {
    11. }
    12. }
    13. }

    关于EF Core Fluent Mapping

    应用程序启动模板已配置使用映射你的实体到数据库表.

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

    ABP框架有一些实体基类约定(参阅实体文档)提供了一些有用的扩展方法来配置从基本实体类继承的属性.

    ConfigureByConvention 方法

    ConfigureByConvention() 是主要的扩展方法,它对你的实体配置所有的基本属性和约定. 所以在你的流利映射代码中为你所有的实体调用这个方法是 最佳实践,

    示例: 假设你有一个直接继承 AggregateRoot<Guid> 基类的 Book 实体:

    1. public class Book : AuditedAggregateRoot<Guid>
    2. {
    3. public string Name { get; set; }
    4. }

    你可以在你的 DbContext 重写 OnModelCreating 方法并且做以下配置:

    1. protected override void OnModelCreating(ModelBuilder builder)
    2. {
    3. //Always call the base method
    4. base.OnModelCreating(builder);
    5. builder.Entity<Book>(b =>
    6. {
    7. b.ToTable("Books");
    8. //Configure the base properties
    9. b.ConfigureByConvention();
    10. //Configure other properties (if you are using the fluent API)
    11. b.Property(x => x.Name).IsRequired().HasMaxLength(128);
    12. });
    13. }
    • 这里调用了 b.ConfigureByConvention() 它对于配置基本属性非常重要.
    • 你可以在这里配置 Name 属性或者使用data annotation attributes(参阅EF Core 文档).

    配置连接字符串选择

    如果你的应用程序有多个数据库,你可以使用 connectionStringName] Attribute为你的DbContext配置连接字符串名称. 例:

    1. [ConnectionStringName("MySecondConnString")]
    2. public class MyDbContext : AbpDbContext<MyDbContext>
    3. {
    4. }

    如果不进行配置,则使用Default连接字符串. 如果你配置特定的连接字符串的名称,但在应用程序配置中没有定义这个连接字符串名称,那么它会回退到Default连接字符串(参阅连接字符串文档了解更多信息).

    在module中的ConfigureServices方法使用 AddAbpDbContext 在系统注册DbContext类.

    1. using Microsoft.Extensions.DependencyInjection;
    2. using Volo.Abp.Modularity;
    3. namespace MyCompany.MyProject
    4. {
    5. [DependsOn(typeof(AbpEntityFrameworkCoreModule))]
    6. public class MyModule : AbpModule
    7. {
    8. public override void ConfigureServices(ServiceConfigurationContext context)
    9. {
    10. context.Services.AddAbpDbContext<MyDbContext>();
    11. //...
    12. }
    13. }
    14. }

    ABP会自动为DbContext中的实体创建默认仓储. 需要在注册的时使用options添加AddDefaultRepositories():

    1. services.AddAbpDbContext<MyDbContext>(options =>
    2. {
    3. });

    然后你就可以在服务中注入和使用 IRepository<TEntity>IQueryableRepository<TEntity>.

    假如你有一个主键是Guid名为Book实体(聚合根)

    1. public class Book : AggregateRoot<Guid>
    2. {
    3. public string Name { get; set; }
    4. public BookType Type { get; set; }
    5. }

    在中创建一个新的Book实例并且使用仓储持久化到数据库中

    1. public class BookManager : DomainService
    2. {
    3. private readonly IRepository<Book, Guid> _bookRepository;
    4. public BookManager(IRepository<Book, Guid> bookRepository) //注入默认仓储
    5. {
    6. _bookRepository = bookRepository;
    7. }
    8. public async Task<Book> CreateBook(string name, BookType type)
    9. {
    10. Check.NotNullOrWhiteSpace(name, nameof(name));
    11. var book = new Book
    12. {
    13. Id = GuidGenerator.Create(),
    14. Name = name,
    15. Type = type
    16. };
    17. await _bookRepository.InsertAsync(book); //使用仓储提供的标准方法
    18. return book;
    19. }
    20. }

    在这个示例中使用 InsertAsync 将新实例插入到数据库中

    添加自定义仓储

    默认通用仓储可以满足大多数情况下的需求(它实现了IQueryable),但是你可能会需要自定义仓储与仓储方法.

    假设你需要根据图书类型删除所有的书籍. 建议为自定义仓储定义一个接口:

    1. public interface IBookRepository : IRepository<Book, Guid>
    2. {
    3. Task DeleteBooksByType(BookType type);
    4. }

    你通常希望从IRepository派生以继承标准存储库方法. 然而,你没有必要这样做. 仓储接口在分层应用程序的领域层中定义,它在数据访问/基础设施层(中的EntityFrameworkCore项目)中实现

    IBookRepository接口的实现示例:

    1. public class BookRepository : EfCoreRepository<BookStoreDbContext, Book, Guid>, IBookRepository
    2. {
    3. public BookRepository(IDbContextProvider<BookStoreDbContext> dbContextProvider)
    4. : base(dbContextProvider)
    5. {
    6. }
    7. public async Task DeleteBooksByType(BookType type)
    8. {
    9. await DbContext.Database.ExecuteSqlRawAsync(
    10. $"DELETE FROM Books WHERE Type = {(int)type}"
    11. );
    12. }
    13. }

    现在可以在需要时注入IBookRepository并使用DeleteBooksByType方法.

    覆盖默认通用仓储

    即使创建了自定义仓储,仍可以注入使用默认通用仓储(在本例中是 IRepository<Book, Guid>). 默认仓储实现不会使用你创建的自定义仓储类.

    如果要将默认仓储实现替换为自定义仓储,请在AddAbpDbContext使用options执行:

    1. context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
    2. {
    3. options.AddDefaultRepositories();
    4. options.AddRepository<Book, BookRepository>();
    5. });

    在你想要覆盖默认仓储方法对其自定义时,这一点非常需要. 例如你可能希望自定义DeleteAsync方法覆盖默认实现

    1. public async override Task DeleteAsync(
    2. bool autoSave = false,
    3. CancellationToken cancellationToken = default)
    4. {
    5. //TODO: Custom implementation of the delete method
    6. }

    访问 EF Core API

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

    1. public class BookService
    2. {
    3. private readonly IRepository<Book, Guid> _bookRepository;
    4. public BookService(IRepository<Book, Guid> bookRepository)
    5. {
    6. _bookRepository = bookRepository;
    7. }
    8. public void Foo()
    9. DbContext dbContext = _bookRepository.GetDbContext();
    10. DbSet<Book> books = _bookRepository.GetDbSet();
    11. }
    12. }
    • GetDbContext 返回 DbContext 引用,而不是 BookStoreDbContext. 你可以释放它, 但大多数情况下你不会需要它.

    额外属性系统允许你为实现了 IHasExtraProperties 的实体set/get动态属性. 当你想将自定义属性添加到中定义的实体时,它特别有用.

    默认,实体的所有额外属性存储在数据库的一个 JSON 对象中.

    实体扩展系统允许你存储额外属性在数据库的单独字段中. 有关额外属性和实体扩展系统的更多信息,请参阅下列文档:

    本节只解释了 EF Core相关的 ObjectExtensionManager 及其用法.

    ObjectExtensionManager.Instance

    MapEfCoreProperty

    MapEfCoreProperty 是一种快捷扩展方法,用于定义实体的扩展属性并映射到数据库.

    示例: 添加 Title 属性 (数据库字段)到 IdentityRole 实体:

    如果相关模块已实现此功能(通过使用下面说明的 ConfigureEfCoreEntity)则将新属性添加到模型中. 然后你需要运行标准的 Add-MigrationUpdate-Database 命令更新数据库以添加新字段.

    如果你正在开发一个可重用使用的模块,并允许应用程序开发人员将属性添加到你的实体,你可以在实体映射使用 ConfigureEfCoreEntity 扩展方法,但是在配置实体映射时可以使用快捷的扩展方法 ConfigureObjectExtensions:

    1. builder.Entity<YourEntity>(b =>
    2. {
    3. b.ConfigureObjectExtensions();
    4. //...
    5. });

    如果你调用 ConfigureByConvention() 扩展方法(在此示例中 b.ConfigureByConvention),ABP框架内部会调用 ConfigureObjectExtensions 方法. 使用 ConfigureByConvention 方法是最佳实践,因为它还按照约定配置基本属性的数据库映射.

    参阅上面提到的 “ConfigureByConvention 方法“ 了解更多信息.

    高级主题

    设置默认仓储类

    默认的通用仓储的默认实现是EfCoreRepository类,你可以创建自己的实现,并将其做为默认实现

    首先,像这样定义仓储类:

    1. public class MyRepositoryBase<TEntity>
    2. : EfCoreRepository<BookStoreDbContext, TEntity>
    3. where TEntity : class, IEntity
    4. {
    5. public MyRepositoryBase(IDbContextProvider<BookStoreDbContext> dbContextProvider)
    6. : base(dbContextProvider)
    7. {
    8. }
    9. }
    10. public class MyRepositoryBase<TEntity, TKey>
    11. : EfCoreRepository<BookStoreDbContext, TEntity, TKey>
    12. where TEntity : class, IEntity<TKey>
    13. {
    14. public MyRepositoryBase(IDbContextProvider<BookStoreDbContext> dbContextProvider)
    15. : base(dbContextProvider)
    16. {
    17. }
    18. }

    第一个用于具有复合主键的实体,第二个用于具有单个主键的实体

    建议从EfCoreRepository类继承并在需要时重写方法. 否则,你需要手动实现所有标准仓储方法.

    现在,你可以使用SetDefaultRepositoryClasses Options

    1. context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
    2. {
    3. options.SetDefaultRepositoryClasses(
    4. typeof(MyRepositoryBase<,>),
    5. typeof(MyRepositoryBase<>)
    6. );
    7. //...
    8. });

    为默认仓储设置Base DbContext类或接口

    如果你的DbContext继承了另外一个DbContext或实现了一个接口,你可以使用这个基类或接口作为默认仓储的DbContext. 例:

    1. public interface IBookStoreDbContext : IEfCoreDbContext
    2. {
    3. DbSet<Book> Books { get; }
    4. }

    IBookStoreDbContext接口是由BookStoreDbContext实现的. 然后你可以使用AddDefaultRepositories的泛型重载.

    1. context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
    2. {
    3. options.AddDefaultRepositories<IBookStoreDbContext>();
    4. //...
    5. });

    现在,你的自定义仓储也可以使用IBookStoreDbContext接口:

    1. public class BookRepository : EfCoreRepository<IBookStoreDbContext, Book, Guid>, IBookRepository
    2. {
    3. //...
    4. }

    使用DbContext接口的一个优点是它可以被其他实现替换.

    正确定义并使用DbContext接口后,任何其他实现都可以使用以下ReplaceDbContext options 替换它:

    1. context.Services.AddAbpDbContext<OtherDbContext>(options =>
    2. {
    3. //...
    4. options.ReplaceDbContext<IBookStoreDbContext>();
    5. });

    在这个例子中,OtherDbContext实现了IBookStoreDbContext. 此功能允许你在开发时使用多个DbContext(每个模块一个),但在运行时可以使用单个DbContext(实现所有DbContext的所有接口).