Entity Framework Core
Abp.EntityFrameworkCore nuget包用來集成EntityFramework(EF)Core ORM框架。安裝這個包之后,我們需要為AbpEntityFrameworkCoreModule添加DependsOn特性。
EF Core需要定義一個從DbContext繼承的類。在ABP中,我們應該從AbpDbContext繼承,如下所示:
public class MyDbContext : AbpDbContext { public DbSet<Product> Products { get; set; } public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { } }
如上所示,構造函數有一個DbContextOptions<T>的參數。
在ConfigureServices方法中使用AddAbpDbContext方法,如下所示:
services.AddAbpDbContext<MyDbContext>(options => { options.DbContextOptions.UseSqlServer(options.ConnectionString); });
對於非web工程,沒有Startup類。在這種情況下,我們在模塊中使用Configuration.Modules.AbpEfCore().AddDbContext方法來配置DbContext,如下所示:
Configuration.Modules.AbpEfCore().AddDbContext<MyDbContext>(options => { options.DbContextOptions.UseSqlServer(options.ConnectionString); });
我們使用給定的連接字符串並使用Sql Server作為數據庫提供者。options.ConnectionString通常為默認的連接字符串(參見下一部分)。但是ABP使用IConnectionStringResolver來決定。所以,這種行為是可以改變的,連接字符串也可以動態決定。無論何時DbContext示例創建時,傳遞給AddDbContext的action都會執行。所以,你有機會可以根據條件返回不同的連接字符串。
所以,在什么地方設置默認字符串呢?
你可以在模塊的PreInitialize方法中設置默認字符串,如下所示:
public class MyEfCoreAppModule : AbpModule { public override void PreInitialize() { Configuration.DefaultNameOrConnectionString = GetConnectionString("Default"); ... } }
這樣,你可以定義GetConnectionString方法從一個配置文件中(一般從appsettings.json文件)返回連接字符串。
倉儲用來從高層抽象數據訪問。參見倉儲文檔了解更多。
Abp.EntityFrameworkCore為所有定義在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提供了一個基類EfCoreRepositoryBase來簡單的實現倉儲。為了實現IRepository接口,你可以從這個類繼承。但是最好創建自己的基礎類並擴展EfRepositoryBase。這樣,你可以容易的在自己的倉儲中添加shared/common方法。下面為SimpleTaskSystem應用所有倉儲的基類示例:
//Base class for all repositories in my application public class SimpleTaskSystemRepositoryBase<TEntity, TPrimaryKey> : EfCoreRepositoryBase<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) }
注意,我們從EfCoreRepositoryBase<SimpleTaskSystemDbContext,TEntity,TPrimaryKey>繼承。這表明在我們的倉儲中ABP使用SimpleTaskSystemDbContext。
默認,給定DbContext(在這個例子中為SimpleTaskSystemDbContext)的所有倉儲使用EfCoreRepositoryBase實現。你可以通過在DbContext上添加AutoRepositoryTypes特性來使用自己的倉儲基類來取代默認的倉儲基類。如下所示:
[AutoRepositoryTypes( typeof(IRepository<>), typeof(IRepository<,>), typeof(SimpleTaskSystemEfRepositoryBase<>), typeof(SimpleTaskSystemEfRepositoryBase<,>) )] public class SimpleTaskSystemDbContext : AbpDbContext { ... }
為了實現自定義倉儲,需要繼承我們上面定義的特定倉儲基礎類。
假定,我們有一個Task實體,它可以被分配給一個Person(實體)並且任務有一個狀態(new,assigned,completed...等等)。我們需要編寫一個自定義方法基於一些條件並基於AssisgnedPerson屬性預獲取person實體,在一個數據庫查詢語句中來獲取任務列表。參見示例代碼:
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()方法來獲取Tasks列表。
你也可以在倉儲方法中使用Context對象引用你的DbContext並直接使用Entity Framework APIs。
注意:對於分層應用,在domain/core層定義自定義倉儲接口,在EntityFrameworkCore工程中實現它。這樣,你可以從任何工程中注入這個接口而不用引用EFCore。
即使你有一個TaskRepository,如上所示,任何類仍然可以注入IRepository<Task,long>並使用它。大多數情況下這沒有什么問題。但是,如果在你的自定義倉儲中重寫了一個基類方法會怎樣呢?比如說在你的自定義倉儲中重寫了Delete方法在刪除時添加一個自定義行為。如果一個類注入IRepository<Task,long>並使用默認倉儲刪除一個任務,你自定義的行為將不會起作用。為了克服這個問題,你可以使用默認的你喜歡的一個取代你的自定義實現,如下所示:
Configuration.ReplaceService<IRepository<Task, Guid>>(() => { IocManager.IocContainer.Register( Component.For<IRepository<Task, Guid>, ITaskRepository, TaskRepository>() .ImplementedBy<TaskRepository>() .LifestyleTransient() ); });
我們為IRepository<Task,Guid>,ITaskRepository和TaskRepository注冊TaskRepository。所以,這些中的任何一個都可以被注入並使用TaskRepository。
- 在任何可能的地方使用默認倉儲。即使你已經有一個實體的默認倉儲(如果你將使用標准倉儲方法)也可以使用默認的倉儲。
- 總是在應用程序中為自定義倉儲創建倉儲基類,如上定義。
- 如果你想從domain/application中抽象出EF Core,那么在domain層定義自定義倉儲的接口(在啟動模板中為.Core工程),在.EntityFrameworkCore工程中定義自定義倉儲類。