🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## ABP 基础设施层 - 集成 Entity Framework Core ### 9.3.1 简介 [Abp.EntityFrameworkCore](https://www.nuget.org/packages/Abp.EntityFrameworkCore) nuget package 被用来集成到EF Core ORM框架. 在安装这个包以后,我们应该在模块类 **AbpEntityFrameworkCoreModule** 上添加 **[DependsOn](225823)** 特性。 ### 9.3.2 DbContext EF Core要求有个派生自DbContext的类。在ABP中,我们应该使其派生自 **AbpDbContext**,如下所示: ```csharp public class MyDbContext : AbpDbContext { public DbSet<Product> Products { get; set; } public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { } } ``` 如上所示,构造函数应该取得 **DbContextOptions\<T\>**。 ### 9.3.3 Configuration #### In Startup Class 对于ASP.NET Core项目和往常一样,你会有一个 **Startup** 文件和一个你能使用的EF Core的 **AddDbContext** 扩展方法,如下所示: ```csharp public IServiceProvider ConfigureServices(IServiceCollection services) { ... services.AddDbContext<MyDbContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("Default")); }); ... } ``` 这对ABP来说是一个简单且标准的方式。在这种方式中,连接字符串是静态的(因为我们一直使用"Default"连接字符串)。如果你只有一个数据库并有与之对应的DbContext,那就足够了。但是,如果你想要动态 切换连接字符串且更好的集成ABP框架的连接字符串,那么请使用 **AddAbpDbContext** 方法: ```csharp services.AddAbpDbContext<MyDbContext>(options => { options.DbContextOptions.UseSqlServer(options.ConnectionString); }); ``` 我们使用给定的连接字符串并使用Sql Server作为数据库提供器。通常 **options.ConnectionString** 的值就是 **default连接字符串**。但是ABP使用 **IConnectionStringResolver** 来确定。所以,这个行为方式是可以改变的并且连接字符串可以动态的切换。每当DbContext被实例化的时候,这个动作会传递给 **AddAbpDbContext** 方法。所以,你有机会可以返还不同条件下的连接字符串。 那么,哪里可以设置默认的连接字符串呢? #### In Module PreInitialize 你可以在模块的 **PreInitialize** 方法中来设置它,如下所示: ```csharp public class MyEfCoreAppModule : AbpModule { public override void PreInitialize() { Configuration.DefaultNameOrConnectionString = GetConnectionString("Default"); ... } } ``` 对于非web项目,我们不会有启动类,在这种情况下,我们可以使用 **Configuration.Modules.AbpEfCore().AddDbContext** 方法来配置DbContext,如下所示: ```csharp Configuration.Modules.AbpEfCore().AddDbContext<MyDbContext>(options => { options.DbContextOptions.UseSqlServer(options.ConnectionString); }); ``` ### 9.3.4 Repositories 仓储被用来从更改层的抽象数据访问。详细请看[仓储文档](3.2ABP领域层-仓储.md)。 #### Default Repositories [Abp.EntityFrameworkCore](http://www.nuget.org/packages/Abp.EntityFrameworkCore) 为所有在DbContext中已定义的实体默认实现了仓储。你没必要创建仓储类来使用预定义的仓储方法。例如: ```csharp public class PersonAppService : IPersonAppService { private readonly IRepository<Person> _personRepository; public PersonAppService(IRepository<Person> personRepository) { _personRepository = personRepository; } public void CreatePerson(CreatePersonInput input) { person = new Person { Name = input.Name, EmailAddress = input.EmailAddress }; _personRepository.Insert(person); } } ``` **IRepository\<Person\>** 在 **PersonAppService** 的构造函数中被注入,并且使用了 **Insert** 方法。 使用这种方式,你可以很容易的注入 **IRepository\<TEntity\>或者IRepository\<TEntity, TPrimaryKey\>** 并且使用其中预定义的方法。查看所有的预定义方法请查询[仓储文档](3.2ABP领域层-仓储.md)。 #### 自定义仓储 如果你对标准的仓储方法不满意,你可以对你的实体创建自定义仓储。 ##### 特定于应用的基础仓储类 ABP提供了一个基础类:**EfCoreRepositoryBase** 使用它你可以很容易实现仓储类。为了实现 **IRepository** 接口,你可以使你的仓储类派生自该类。但是最好的方式是创建你自己的基类来扩展 **EfRepositoryBase** 类。因此,你可以轻松的添加共享/通用方法给你的仓储类。下面是SimpleTaskSystem应用对所有仓储的基类实现示例: ```csharp //应用程序中的所有仓储的基类 public class SimpleTaskSystemRepositoryBase<TEntity, TPrimaryKey> : EfCoreRepositoryBase<SimpleTaskSystemDbContext, TEntity, TPrimaryKey> where TEntity : class, IEntity<TPrimaryKey> { public SimpleTaskSystemRepositoryBase(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider) : base(dbContextProvider) { } //为所有仓储添加通用方法 } //对于那些有int类型Id的实体的仓储的快速实现方式 public class SimpleTaskSystemRepositoryBase<TEntity> : SimpleTaskSystemRepositoryBase<TEntity, int> where TEntity : class, IEntity<int> { public SimpleTaskSystemRepositoryBase(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider) : base(dbContextProvider) { } //别在这里添加任何方法,请将方法添加到上面那个类,因为该类被上面类继承 } ``` >注意:我们的仓储类都是继承自 **EfCoreRepositoryBase\<SimpleTaskSystemDbContext, TEntity, TPrimaryKey\>**。这说明ABP在仓储中使用了 **SimpleTaskSystemDbContext**。 ##### 自定义仓储示例 为了实现一个自定义的仓储,你应该使你创建的应用的仓储派生自上面所说的特定于应用的基础仓储类。 假设我们有一个任务实体且任务能够被分配给某个人并且任务有自己的状态(如:new,assigned,completed 等等)。我们可能需要写一个自定义的方法来取得任务列表并且需要使用某些条件来执行数据过滤,如:分配人,任务状态 来过滤。 如下所示: ```csharp public interface ITaskRepository : IRepository<Task, long> { List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state); } public class TaskRepository : SimpleTaskSystemRepositoryBase<Task, long>, ITaskRepository { public TaskRepository(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider) : base(dbContextProvider) { } public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state) { var query = GetAll(); if (assignedPersonId.HasValue) { query = query.Where(task => task.AssignedPerson.Id == assignedPersonId.Value); } if (state.HasValue) { query = query.Where(task => task.State == state); } return query .OrderByDescending(task => task.CreationTime) .Include(task => task.AssignedPerson) .ToList(); } } ``` 首先我们定义了 **ITaskRepository** 接口然后实现它。**GetAll()** 方法返回 **IQueryable\<Task\>**,然后我们可以使用给定的参数添加一些 **Where** 来过滤数据。最后我们可以调用 **ToList()** 方法来获取任务列表。 你也可以在仓储的方法中使用 **Context** 对象取得DbContext然后直接使用EF的API。 >注意:在领域层定义自定义仓储接口,然后在 **EntityFrameworkCore** 项目中实现该接口。因此,你可以从任何项目中注入该接口并不需要引用EF Core。 #### Repository 最佳实践 + 在任何可能的地方使用默认仓储。即使你的实体有自定义仓储,你也能使用默认仓储(如果你使用了标准的仓储方法)。 + 如同上面所述:对自定义的仓储创建 **仓储基类**。 + 在领域层中定义自定义仓储接口(.Core 项目是在启动模板),如果你从你的领域层抽象出EF Core, 那么请在 **.EntityFrameworkCore** 项目中实现自定义仓储类。 ### 9.3.5 未实现的功能 Abp.EntityFrameworkCore 模块当前没有提供如下功能: + Data Filters:Abp.EntityFramework 模块使用 EntityFramework.DynamicFilters 来实现自动[数据过滤](3.6ABP领域层-数据过滤器.md)。当前它是[不支持](https://github.com/jcachat/EntityFramework.DynamicFilters/issues/48) EF Core。然而另一些库可以做到,如EF Plus,正如我们对它调研后所知:它可以用一种有限的方式来实现。例如:EF Plus 不支持导航属性的过滤。 + DateTime normalization:Abp.EntityFramework 模块对从数据库取得的实体的DateTime字段属性使其规范化。但是,对于Abp.EntityFrameworkCore模块实现这个功能是不可能的。