返回ABP系列html
ABP是「ASP.NET Boilerplate Project (ASP.NET樣板項目)」的簡稱。git
ASP.NET Boilerplate是一個用最佳實踐和流行技術開發現代WEB應用程序的新起點,它旨在成爲一個通用的WEB應用程序框架和項目模板。github
ABP的官方網站:http://www.aspnetboilerplate.com數據庫
ABP官方文檔:http://www.aspnetboilerplate.com/Pages/Documents編程
Github上的開源項目:https://github.com/aspnetboilerplateapp
倉儲的定義:位於領域層和數據映射層之間,使用相似集合的接口來訪問領域對象。框架
在實踐中,倉儲是執行領域對象(實體和值對象)的數據庫操做。通常地,一個分離的倉儲用於一個實體(或者聚合根)。異步
一、IRepository接口async
在ABP中,一個倉儲類應該實現一個IRepository接口。爲每個倉儲定義一個接口是一個好的作法。異步編程
一個Person實體的倉儲定義以下:
public interface IPersonRepository : IRepository<Person> { }
IPersonRepository擴展了IRepository,它用於定義擁有主鍵類型爲int32的實體。若是你的實體不是int,那麼能夠擴展IRepository<tentity,tprimarykey>接口,以下所示:
public interface IPersonRepository : IRepository<Person, long> { }
IRepository爲倉儲類定義了最通用的方法,如select,insert,update和delete方法(CRUD操做)。大多數狀況下,這些方法對於簡單的實體是足夠了。若是這些方法對於一個實體來講已經足夠了,那麼就沒有必要爲這個實體建立倉儲接口和倉儲類了。
1)、查詢:
IRepository定義了通用的方法,從數據庫中檢索實體。
獲取單個實體:
TEntity Get(TPrimaryKey id); Task<TEntity> GetAsync(TPrimaryKey id); TEntity Single(Expression<Func<TEntity, bool>> predicate); Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> predicate); TEntity FirstOrDefault(TPrimaryKey id); Task<TEntity> FirstOrDefaultAsync(TPrimaryKey id); TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate); Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate); TEntity Load(TPrimaryKey id);
Get方法用於得到一個給定主鍵(Id)的實體。若是在數據庫中沒有找到這個實體,就會拋出異常。Single方法和Get相似,可是它的參數是一個表達式而不是一個Id。所以,你可使用Lambda表達式得到一個實體。好比:
var person = _personRepository.Get(42); var person = _personRepository.Single(p => p.Name == "hi joye.net");
注意:若是根據給定的條件沒有查找出實體或者查出不止一個實體,那麼Single方法會拋出異常。
FirstOrDefault是類似的,可是若是根據給的的Id或者表達式沒有找到實體,那麼就會返回null。若是對於給定的條件存在不止一個實體,那麼會返回找到的第一個實體。
Load方法不會從數據庫中檢索實體,可是會建立一個用於懶加載的代理對象。若是你只用了Id屬性,那麼Entity實際上並無檢索到。只有你訪問實體的其餘屬性,纔會從數據庫中檢索。考慮到性能因素,這個就能夠替換Get方法。這在NHiberbate中也實現了。若是ORM提供者沒有實現它,那麼Load方法會和Get方法同樣地工做。
一些方法有用於async編程模型的異步版本。
得到實體的列表:
List<TEntity> GetAllList(); Task<List<TEntity>> GetAllListAsync(); List<TEntity> GetAllList(Expression<Func<TEntity, bool>> predicate); Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate); IQueryable<TEntity> GetAll();
GetAllList從數據庫中檢索全部的實體。該方法的重載能夠用於過濾實體。用法以下:
var allPeople = _personRepository.GetAllList(); var somePeople = _personRepository.GetAllList(person => person.IsActive && person.Age > 42);
GetAll返回的類型是IQueryable。所以,你能夠在此方法以後添加Linq方法。用法以下:
//Example 1 var query = from person in _personRepository.GetAll() where person.IsActive orderby person.Name select person; var people = query.ToList(); //Example 2: List<Person> personList2 = _personRepository.GetAll().Where(p => p.Name.Contains("H")).OrderBy(p => p.Name).Skip(40).Take(20).ToList();
有了GetAll方法,幾乎全部的查詢均可以使用Linq重寫。甚至能夠用在一個鏈接表達式中。
關於IQueryable:脫離了倉儲方法調用GetAll()方法時,數據庫鏈接必需要打開。這是由於IQueryable的延遲執行。直到調用ToList()方法或者在foreach循環中使用IQueryable(或者訪問查詢到的元素)時,纔會執行數據庫查詢操做。所以,當調用ToList()方法時。數據庫鏈接必須打開。這能夠經過ABP中的UnitOfWork特性標記調用者方法來實現。注意:應用服務方法默認已是UnitOfWork,所以,即便沒有爲應用服務層方法添加UnitOfWork特性,GetAll()方法也會正常工做。
這些方法也存在用於異步編程模型的asyn版本。
自定義返回值:
也存在提供了IQueryable的額外方法,在調用的方法中不須要使用UnitOfWork。
T Query<T>(Func<IQueryable<TEntity>, T> queryMethod);
Query方法接受一個接收IQueryable的lambda(或方法),並返回任何對象的類型。用法以下:
var people = _personRepository.Query(q => q.Where(p => p.Name.Contains("H")).OrderBy(p => p.Name).ToList());
在該倉儲方法中,由於執行了給定的lambda(或方法),它是在數據庫鏈接打開的時候執行的。你能夠返回實體列表,單個實體,一個投影或者執行了該查詢的其餘東西。
2)、插入:
IRepository接口定義了將一個實體插入數據庫的簡單方法:
TEntity Insert(TEntity entity); Task<TEntity> InsertAsync(TEntity entity); TPrimaryKey InsertAndGetId(TEntity entity); Task<TPrimaryKey> InsertAndGetIdAsync(TEntity entity); TEntity InsertOrUpdate(TEntity entity); Task<TEntity> InsertOrUpdateAsync(TEntity entity); TPrimaryKey InsertOrUpdateAndGetId(TEntity entity); Task<TPrimaryKey> InsertOrUpdateAndGetIdAsync(TEntity entity);
Insert方法簡化了將一個實體插入數據庫,並將剛剛插入的實體返回。InsertAndGetId方法返回了新插入實體的Id。若是實體的Id是自動增加的而且須要最新插入實體的Id,那麼該方法頗有用。InsertOrUpdate方法經過檢查Id的值插入或更新給定的實體。最後,當插入或者更新以後,InsertOrUpdateAndGetId返回該實體的值。
全部的方法都存在用於異步編程模型的async版本。
3)、更新:
IRepository定義了一個方法來更新數據庫中已存在的實體。它能夠得到要更新的實體並返回相同的實體對象。
TEntity Update(TEntity entity);
Task<TEntity> UpdateAsync(TEntity entity);
4)、刪除:
IRepository定義了從數據庫中刪除一個已存在的實體的方法。
void Delete(TEntity entity); Task DeleteAsync(TEntity entity); void Delete(TPrimaryKey id); Task DeleteAsync(TPrimaryKey id); void Delete(Expression<Func<TEntity, bool>> predicate); Task DeleteAsync(Expression<Func<TEntity, bool>> predicate);
第一個方法接受一個已存在的實體,第二個方法接受一個要刪除的實體的Id。
最後一個方法接受一個刪除符合給定條件的全部實體的方法。注意,匹配給定謂詞的全部實體都會從數據庫中檢索到而後被刪除。所以,當心使用它,若是給定的條件存在太多的實體,那麼可能會形成性能問題。
5)、其餘:
IRepository也提供了得到表中實體數量的方法。
int Count(); Task<int> CountAsync(); int Count(Expression<Func<TEntity, bool>> predicate); Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate); long LongCount(); Task<long> LongCountAsync(); long LongCount(Expression<Func<TEntity, bool>> predicate); Task<long> LongCountAsync(Expression<Func<TEntity, bool>> predicate);
6)、關於異步方法:
ABP支持異步編程模型(APM)。所以,倉儲方法有異步版本。下面是一個使用了異步模型的應用服務方法例子:
public class PersonAppService : AbpWpfDemoAppServiceBase, IPersonAppService { private readonly IRepository<Person> _personRepository; public PersonAppService(IRepository<Person> personRepository) { _personRepository = personRepository; } public async Task<GetPeopleOutput> GetAllPeople() { var people = await _personRepository.GetAllListAsync(); return new GetPeopleOutput { People = Mapper.Map<List<PersonDto>>(people) }; } }
GetAllPeople方法是異步的,並使用了具備await關鍵字的GetAllListAsync方法。
也許不是全部的ORM框架都支持Async,可是EntityFramework支持。若是不支持,異步倉儲方法就會同步進行。好比,在EF中,InsertAsync和Insert是等效的,由於直到工做單元完成(Dbcontext.SaveChanges),EF纔會將新的實體寫入數據庫。
二、倉儲實現
ABP的設計獨立於一個特定的ORM(對象/關係映射)框架或者訪問數據庫的其餘技術。經過實現倉儲接口,可使用任何框架。
ABP使用NHibernate和 EntityFramework實現了開箱即用的倉儲。關於這兩個ORM框架能夠關注後面的文檔。
當使用NHibernate或EntityFramework時,若是標準方法是足夠使用的話,那麼沒必要爲實體類建立倉儲了。你能夠直接注入IRepository(或IRepository<tentity,tprimarykey>)。下面是使用了一個倉儲將一個實體插入數據庫的應用服務例子:
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,並使用了Insert方法。這樣,當你須要爲一個實體建立一個自定義倉儲方法時,你才應該爲該實體建立一個倉儲類。
三、管理數據庫的鏈接:
在倉儲方法中,數據庫鏈接是沒有打開的或是關閉的。ABP對於數據庫鏈接的管理是自動處理的。
當將要進入一個倉儲方法時,數據庫鏈接會自動打開,而且事務自動開始。當倉儲方法結束並返回的時候,ABP會自動完成:保存全部的更改,完成事務的提交和關閉數據庫鏈接。若是倉儲方法拋出任何類型的異常,那麼事務會自動回滾並關閉數據庫。這對於全部的實現了IRepository接口的類的公共方法都是成立的。
若是一個倉儲方法調用了其餘的倉儲方法,那麼它們會共享相同的鏈接和事務。進入倉儲的第一個方法會管理數據庫的鏈接。更多信息,請留意後面博客的工做單元。
四、倉儲的生命週期
全部的倉儲實例都是Transient(每次使用時都會實例化)的。ABP強烈推薦使用依賴注入技術。當一個倉儲類須要注入時,依賴注入的容器會自動建立該類的新實例。
五、倉儲的最佳實踐
一、對於一個T類型的實體,使用IRepository倉儲接口。除非真的須要,不然不要建立自定義的倉儲。預約義的倉儲方法對於不少狀況足夠用了。
二、若是你正在建立一個自定義的倉儲(經過擴展IRepository):
1)、倉儲類應該是無狀態的。這意味着,你不該該定義倉儲級別的狀態對象,並且一個倉儲方法調用不該該影響其餘的調用。
2)、自定義倉儲方法不該該包含業務邏輯或者應用邏輯,而應該只執行數據相關的或者orm特定的任務。
3)、當倉儲使用依賴注入時,給其餘服務定義更少的或者不要定義依賴。