ABP官方文檔翻譯 9.1 EntityFramework集成
EntityFramework集成
- Nuget包
- DbContext
- 倉儲
- 默認倉儲
- 自定義倉儲
- 應用特定的基礎倉儲類
- 自定義倉儲示例
- 倉儲最佳實踐
- 事務管理
- 數據存儲
ABP可以使用ORM框架,它內置集成EntityFramework。本文檔將講解ABP如何使用EntityFramework。假定你對EntityFramework已經有了初級水平。
Nuget包
在ABP中使用Abp.EntityFramework nuget包擴展了EntityFramework。需要將它添加到工程中。最好在一個單獨的程序集(dll)中實現EntityFramework並在此程序集中引用Abp.EntityFramework包。
DbContext
如你所知,為了使用EntityFramework,你需要在應用中定義一個DbContext。一個實例DBContext如下所示:
public class SimpleTaskSystemDbContext : AbpDbContext { public virtual IDbSet<Person> People { get; set; } public virtual IDbSet<Task> Tasks { get; set; } public SimpleTaskSystemDbContext() :base("Default") { } public SimpleTaskSystemDbContext(string nameOrConnectionString) : base(nameOrConnectionString) { } public SimpleTaskSystemDbContext(DbConnection existingConnection) : base(existingConnection, false) { }public SimpleTaskSystemDbContext(DbConnection existingConnection, bool contextOwnsConnection) : base(existingConnection, contextOwnsConnection) { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<Person>().ToTable("StsPeople"); modelBuilder.Entity<Task>().ToTable("StsTasks").HasOptional(t => t.AssignedPerson); } }
這是一個正規的DbContext類,除此之外它遵循以下規則:
- 從AbpDbContext集成而不是DbContext。
- 需要有構造函數如上面的示例(構造函數參數命名也需要一樣)。說明:
- 默認構造函數傳遞“Default”到基類作為連接字符串。所以,在web.config/app.config文件中有一個名為“Default”的連接字符串。ABP不使用個這構造函數,但是EF命令行遷移工具和工具命令(如 update-database)使用它。
- 帶nameOrConnectiongString參數的構造函數,ABP用來在運行時傳遞連接名稱或字符串。
- 帶existingConnection參數的構造函數用於單元測試,ABP不直接使用。
- 帶existingConnection和contxtOwnsConnection的構造函數,當使用DbContextEfTransactionStrategy時(參見下面的事務管理部分),ABP用於單數據庫多dbcontext的場景來共享同一個連接、事務。
EntityFramework使用常規的方式將類映射到數據庫表。除非自定義了功能,你不需要做任何配置。在這個例子中,我們將實體映射到不同的表。Task實體默認映射到Tasks表。但是我們可以改變它映射到StsTasks表。比起使用數據註解,我更喜歡使用流配置。你可以選擇自己喜歡的方式。
倉儲
倉儲用來從高層抽象數據訪問。參見倉儲文檔了解更多。
默認倉儲
Abp.EntityFramework為定義在DbContext中的所有實體實現默認倉儲。你不必創建倉儲類來使用預定義的倉儲方法。示例:
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); } }
PersonAppService構造註入了IRepository<Person>並使用了Insert方法。使用這種方式,你可以簡單註入IRepository<TEntity>(或IRepository<TEntity,TPrimaryKey>)並使用預定義方法。
自定義倉儲
如果標準的倉儲方法不能滿足,你可以為你的實體創建自定義倉儲類。
應用特定的基礎倉儲類
ABP提供了一個基礎類EfRepositoryBase來實現倉儲。為了實現IRepository接口,可以將你的倉儲類從這個類集成。但是最好創建自己的基類並擴展EfRepositoryBase。從而,你可以在自己的倉儲中添加shared/common方法或重寫已經存在的方法。下面是SimpleTaskSystem應用的所有倉儲基類的示例:
//Base class for all repositories in my application public class SimpleTaskSystemRepositoryBase<TEntity, TPrimaryKey> : EfRepositoryBase<SimpleTaskSystemDbContext, TEntity, TPrimaryKey> where TEntity : class, IEntity<TPrimaryKey> { public SimpleTaskSystemRepositoryBase(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider) : base(dbContextProvider) { } //add common methods for all repositories } //A shortcut for entities those have integer Id public class SimpleTaskSystemRepositoryBase<TEntity> : SimpleTaskSystemRepositoryBase<TEntity, int> where TEntity : class, IEntity<int> { public SimpleTaskSystemRepositoryBase(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider) : base(dbContextProvider) { } //do not add any method here, add to the class above (because this class inherits it) }
註意,我們從EfRepositoryBase<SimpleTaskSystemDbContext,TEntity,TPrimaryKey>繼承。這表明ABP在我們的倉儲中使用SimpleTaskSystemDbContext。
默認,對於所有給定DbContext(在這個例子中為SimpleTaskDbContext)的倉儲都使用EfRepositoryBase實現。你可以在你的DbContext中添加AutoRepository特性來使用自己的倉儲基類,如下:
[AutoRepositoryTypes( typeof(IRepository<>), typeof(IRepository<,>), typeof(SimpleTaskSystemEfRepositoryBase<>), typeof(SimpleTaskSystemEfRepositoryBase<,>) )] public class SimpleTaskSystemDbContext : AbpDbContext { ... }
自定義倉儲示例
為了實現自定義倉儲,在你的應用中繼承一個以上我們定義的基礎倉儲類。
假定,我們有一個Task實體,它可以分配給一個Person(實體)並Task有一個State(new,assigned,completed...等等)。我們需要編寫一個自定義方法使用一些條件和AssisgnedPerson屬性來預獲取Tasks的列表。參見下面的示例代碼:
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(); } }
我們首先定義了ITaskRepostory接口並實現了它。GetAll()返回IQueryable<Task>,然後我們可以使用給定的參數添加一些Where過濾器。最後我們調用ToList()來獲取Tasks列表。
你也可以在倉儲方法中使用Context對象引用你自己的DbContext,然後直接使用Entity Framework APIs。
註意:對於分層應用,在domain/core層定義自定義倉儲接口,在EntityFramework工程中實現它們。從而,你可以在任何工程中註入這個接口而不用引用EF。
倉儲最佳實踐
- 在可能的地方使用默認倉儲。即使你有一個實體的自定義倉儲(如果你將使用標準的倉儲方法),也可以使用默認的倉儲。
- 總是為自定義倉儲創建倉儲基類,如上面定義的那樣。
- 在領域層定義自定義倉儲的接口,如果你想將EF從你的domain/application中抽象出來,在EntityFramework工程中自定義倉儲類。
事務管理
ABP有內置的工作單元系統來管理數據庫連接和事務。Entity Framework有不同的事務管理方式。ABP默認使用TransactionScope方式,但是也有DbContext事務API的內置實現。如果你想切換到DbContext事務API,可以在模塊的PreInitialize方法中配置它:
Configuration.ReplaceService<IEfTransactionStrategy, DbContextEfTransactionStrategy>(DependencyLifeStyle.Transient);
記住,在代碼中添加"using Abp.Configuration.Startup"以便能使用ReplaceService的泛型擴展方法。
另外,如在DbContext部分所描述的那樣,你的DbContext需要有構造函數。
數據存儲
因為ABP內置集成EntityFramework,那麽它就可以使用EntityFramework所支持的數據存儲。我們免費的啟動模板設計為使用Sql Server,但是你可以修改他們以便使用不同的數據存儲。
例如,如果你想使用MySql,請參見這個文檔。
返回主目錄
ABP官方文檔翻譯 9.1 EntityFramework集成