在ABP中要使用EntityFramework作为ORM框架的话,需要到Nuget上下载一个名为Abp.EntityFramework的包。比较好的做法是:新建一个独立的程序集(dll),然后在这个程序集中调用这个包和EntityFramework。

    要使用EntityFramework,首先需要定义一个DbContext类。下面是一个DbContex类的示例:

    1. {
    2. public virtual IDbSet<Person> People { get; set; }
    3. public virtual IDbSet<Task> Tasks { get; set; }
    4. public SimpleTaskSystemDbContext()
    5. : base("Default")
    6. {
    7. }
    8. public SimpleTaskSystemDbContext(string nameOrConnectionString)
    9. : base(nameOrConnectionString)
    10. {
    11. }
    12. public SimpleTaskSystemDbContext(DbConnection existingConnection)
    13. : base(existingConnection, false)
    14. {
    15. }
    16. public SimpleTaskSystemDbContext(DbConnection existingConnection, bool contextOwnsConnection)
    17. : base(existingConnection, contextOwnsConnection)
    18. {
    19. }
    20. protected override void OnModelCreating(DbModelBuilder modelBuilder)
    21. {
    22. modelBuilder.Entity<Person>().ToTable("StsPeople");
    23. modelBuilder.Entity<Task>().ToTable("StsTasks").HasOptional(t => t.AssignedPerson);
    24. }
    25. }

    上面是一个常规的ABP的DbContext,它还需要遵循下列规则:

    • 它派生自 AbpDbContext 而不是DbContext。

    • 如上面示例所示:它应该有构造函数(构造函数的参数名字也应该相同):

      • Default 构造函数传入 “Default” 给基类作为连接字符串。所以,这表示在web.config/app.config文件中有一个名字为 “Default” 的连接字符串。该构造函数不是被ABP使用,它是被迁移工具EF命令行使用的,如:update-database。

      • 在运行时ABP通过构造函数取得 nameOrConnectionString 来传入连接名或连接字符串。

      • 在单元测试的时候使用构造函数来取得 existingConnection,而不是直接由ABP使用。

    EntityFramework可以使用约定的方式来映射实体和数据表。除非你想进行自定义映射,否则你甚至不需要做任何配置。在上例中,我们将实体映射到了不同的表中。默认情况下(按照约定优先于配置的原则,会默认采用约定的方式),Task实体会映射到Tasks表,但在这里我们将它映射到了StsTasks表。相比起使用Data Annotation模式来进行自定义映射,我更喜欢使用Fluent API模式。当然,你可以选择你所喜欢的模式,这里并没有特别的限制。

    仓储被用来从更高层抽象数据访问的。详细请看。

    默认仓储


    为所有在DbContext中已定义的实体默认实现了仓储。你没必要创建仓储类来使用预定义的仓储方法。例如:

    IRepository\PersonAppService 的构造函数中被注入,并且使用了 Insert 方法。 使用这种方式,你可以很容易的注入 IRepository\或者IRepository\ 并且使用其中预定义的方法。查看所有的预定义方法请查询仓储文档

    自定义仓储

    如果你对标准的仓储方法不满意,你可以对你的实体创建自定义仓储。

    特定于应用的基础仓储类

    ABP提供了一个基础类:EfRepositoryBase 使用它你可以很容易实现仓储类。为了实现 IRepository 接口,你可以使你的仓储类派生自该类。但是最好的方式是创建你自己的基类来扩展 EfRepositoryBase 类。因此,你可以轻松的添加共享/通用方法给你的仓储类。下面是SimpleTaskSystem应用对所有仓储的基类实现示例:

    1. //应用程序中的所有仓储的基类
    2. public class SimpleTaskSystemRepositoryBase<TEntity, TPrimaryKey> : EfRepositoryBase<SimpleTaskSystemDbContext, TEntity, TPrimaryKey>
    3. where TEntity : class, IEntity<TPrimaryKey>
    4. {
    5. public SimpleTaskSystemRepositoryBase(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider)
    6. : base(dbContextProvider)
    7. {
    8. }
    9. //为所有仓储添加通用方法
    10. }
    11. //对于那些有int类型Id的实体的仓储的快速实现方式
    12. public class SimpleTaskSystemRepositoryBase<TEntity> : SimpleTaskSystemRepositoryBase<TEntity, int>
    13. where TEntity : class, IEntity<int>
    14. {
    15. public SimpleTaskSystemRepositoryBase(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider)
    16. : base(dbContextProvider)
    17. {
    18. //别在这里添加任何方法,请将方法添加到上面那个类,因为该类继承了上面的泛型类
    19. }

    你指定的 DbContext 默认都是使用 EfRepositoryBase 来实现仓储功能的,例如:上面示例 SimpleTaskSystemDbContext。你可以用你自己的仓储基类来替换默认的仓储基类 EfRepositoryBase。这需要你将 AutoRepositoryTypes 特性添加到你的 DbContext 上,如下所示:

    自定义仓储示例

    为了实现一个自定义的仓储,你应该使你创建的应用的仓储派生自上面所说的特定于应用的基础仓储类。

    1. public interface ITaskRepository : IRepository<Task, long>
    2. List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state);
    3. }
    4. public class TaskRepository : SimpleTaskSystemRepositoryBase<Task, long>, ITaskRepository
    5. {
    6. public TaskRepository(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider)
    7. : base(dbContextProvider)
    8. {
    9. }
    10. public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state)
    11. {
    12. var query = GetAll();
    13. if (assignedPersonId.HasValue)
    14. {
    15. query = query.Where(task => task.AssignedPerson.Id == assignedPersonId.Value);
    16. }
    17. if (state.HasValue)
    18. {
    19. query = query.Where(task => task.State == state);
    20. }
    21. return query
    22. .OrderByDescending(task => task.CreationTime)
    23. .Include(task => task.AssignedPerson)
    24. .ToList();
    25. }
    26. }

    首先我们定义了 ITaskRepository 接口然后实现它。GetAll() 方法返回 IQueryable\,然后我们可以使用给定的参数添加一些 Where 来过滤数据。最后我们可以调用 ToList() 方法来获取任务列表。

    你也可以在仓储的方法中使用 Context 对象取得DbContext然后直接使用EF的API。

    Repository 最佳实践

    • 在任何可能的地方使用默认仓储。即使你的实体有自定义仓储,你也能使用默认仓储(如果你使用了标准的仓储方法)。

    • 在领域层中定义自定义仓储接口(.Core 项目是在启动模板),如果你从你的领域层抽象出EF Core, 那么请在 .EntityFramework 项目中实现自定义仓储类。

    ABP已有内置的工作单元来管理数据库连接和事务。EF有不同的.aspx)。ABP默认使用 TransactionScope 包裹的方式,对于DbContext事务API也有内置的扩展。如果你想切换到DbContext事务API,你可以在模块的PreInitialize方法中配置它;如下所示:

    记住引用 using Abp.Configuration.Startup 命名空间,来使用 ReplaceService 扩展方法。

    还有,如同上面DbContext章节所描述的一样,你的DbContext 应该有构造函数

    例如,如果你想使用MySQL,请参考该文档;