点这里进入ABP系列文章总目录html
基于DDD的现代ASP.NET开发框架--ABP系列之十一、ABP领域层——仓储(Repositories)
git
ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称。github
ABP的官方网站:http://www.aspnetboilerplate.com数据库
ABP在Github上的开源项目:https://github.com/aspnetboilerplate架构
本文由台湾-小张提供翻译app
仓储定义:“在领域层和数据映射层的中介,使用相似集合的接口来存取领域对象”(Martin Fowler)。框架
实际上,仓储被用于领域对象在数据库上的操做(实体Entity和值对象Value types)。通常来讲,咱们针对不一样的实体(或聚合根Aggregate Root)会建立相对应的仓储。异步
在ABP中,仓储类要实现IRepository接口。最好的方式是针对不一样仓储对象定义各自不一样的接口。async
针对Person实体的仓储接口声明的示例以下所示:ide
public interface IPersonRepository : IRepository<Person>
{
}
IPersonRepository继承自IRepository<TEntity>,用来定义Id的类型为int(Int32)的实体。若是你的实体Id数据类型不是int,你能够继承IRepository<TEntity, TPrimaryKey>接口,以下所示:
public interface IPersonRepository : IRepository<Person, long>
{
}
对于仓储类,IRepository定义了许多泛型的方法。好比: Select,Insert,Update,Delete方法(CRUD操做)。在大多数的时候,这些方法已足已应付通常实体的须要。若是这些方对于实体来讲已足够,咱们便不须要再去建立这个实体所需的仓储接口/类。在Implementation章节有更多细节。
IRepository定义了从数据库中检索实体的经常使用方法。
TEntity Get(TPrimaryKey id); Task<TEntity> GetAsync(TPrimaryKey id); TEntity Single(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 => o.Name == "Halil ibrahim Kalkan");
注意,Single方法会在给出的条件找不到实体或符合的实体超过一个以上时,都会抛出例外。
FirstOrDefault也同样,可是当没有符合Lambda表达式或Id的实体时,会回传null(取代抛出异常)。当有超过一个以上的实体符合条件,它只会返回第一个实体。
Load并不会从数据库中检索实体,但它会建立延迟执行所需的代理对象。若是你只使用Id属性,实际上并不会检索实体,它只有在你存取想要查询实体的某个属性时才会从数据库中查询实体。当有性能需求的时候,这个方法能够用来替代Get方法。Load方法在NHibernate与ABP的整合中也有实现。若是ORM提供者(Provider)没有实现这个方法,Load方法运行的会和Get方法同样。
ABP有些方法具备异步(Async)版本,能够应用在异步开发模型上(见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 = _personRespository.GetAllList(); var somePeople = _personRepository.GetAllList(person => person.IsActive && person.Age > 42);
GetAll返回IQueryable<T>类型的对象。所以咱们能够在调用完这个方法以后进行Linq操做。示例:
//例子一 var query = from person in _personRepository.GetAll() where person.IsActive orderby person.Name select person; var people = query.ToList(); //例子二 List<Person> personList2 = _personRepository.GetAll().Where(p => p.Name.Contains("H")).OrderBy(p => p.Name).Skip(40).Take(20).ToList();
若是调用GetAll方法,那么几乎全部查询均可以使用Linq完成。甚至能够用它来编写Join表达式。
说明:关于IQueryable<T>
当你调用GetAll这个方法在Repository对象之外的地方,一定会开启数据库链接。这是由于IQueryable<T>容许延迟执行。它会直到你调用ToList方法或在forEach循环上(或是一些存取已查询的对象方法)使用IQueryable<T>时,才会实际执行数据库的查询。所以,当你调用ToList方法时,数据库链接必需是启用状态。咱们可使用ABP所提供的UnitOfWork特性在调用的方法上来实现。注意,Application Service方法预设都已是UnitOfWork。所以,使用了GetAll方法就不须要如同Application Service的方法上添加UnitOfWork特性。
有些方法拥有异步版本,可应用在异步开发模型(见关于async方法章节)。
ABP也有一个额外的方法来实现IQueryable<T>的延迟加载效果,而不须要在调用的方法上添加UnitOfWork这个属性卷标。
T Query<T>(Func<IQueryable<Tentity>,T> queryMethod);
查询方法接受Lambda(或一个方法)来接收IQueryable<T>而且返回任何对象类型。示例以下:
var people = _personRepository.Query(q => q.Where(p => p.Name.Contains("H")).OrderBy(p => p.Name).ToList());
由于是采用Lambda(或方法)在仓储对象的方法中执行,它会在数据库链接开启以后才被执行。你能够返回实体集合,或一个实体,或一个具部份字段(注: 非Select *)或其它执行查询后的查询结果集。
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);
新增方法会新增实体到数据库而且返回相同的已新增实体。InsertAndGetId方法返回新增实体的标识符(Id)。当咱们采用自动递增标识符值且须要取得实体的新产生标识符值时很是好用。InsertOfUpdate会新增或更新实体,选择那一种是根据Id是否有值来决定。最后,InsertOrUpdatedAndGetId会在实体被新增或更新后返回Id值。
全部的方法都拥有异步版本可应用在异步开发模型(见关于异步方法章节)
IRepository定义一个方法来实现更新一个已存在于数据库中的实体。它更新实体并返回相同的实体对象。
TEntity Update(TEntity entity);
Task<TEntity> UpdateAsync(TEntity entity);
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。
最后一个方法接受一个条件来删除符合条件的实体。要注意,全部符合predicate表达式的实体会先被检索然后删除。所以,使用上要很当心,这是有可能形成许多问题,假若是有太多实体符合条件。
全部的方法都拥有async版原本应用在异步开发模型(见关于异步方法章节)。
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<TEntity, bool>> predicate);
全部的方法都拥有async版本被应用在异步开发模型(见关于异步方法章节)。
ABP支持异步开发模型。所以,仓储方法拥有Async版本。在这里有一个使用异步模型的application service方法的示例:
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方法是异步的而且使用GetAllListAsync与await保留关键字。
Async不是在每一个ORM框架都有提供。
上例是从EF所提供的异步能力。若是ORM框架没有提供Async的仓储方法则它会以同步的方式操做。一样地,举例来讲,InsertAsync操做起来和EF的新增是同样的,由于EF会直到单元做业(unit of work)完成以后才会写入新实体到数据库中(DbContext.SaveChanges)。
ABP在设计上是采起不指定特定ORM框架或其它存取数据库技术的方式。只要实现IRepository接口,任何框架均可以使用。
仓储要使用NHibernate或EF来实现都很简单。见实现这些框架在ABP仓储对象上一文:
当你使用NHibernate或EntityFramework,若是提供的方法已足够使用,你就不须要为你的实体建立仓储对象了。咱们能够直接注入IRepository<TEntity>(或IRepository<TEntity, TPrimaryKey>)。下面的示例为application service使用仓储对象来新增实体到数据库:
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方法。当你有须要为实体建立一个客制的仓储方法,那么你就应该建立一个仓储类给指定的实体。
数据库链接的开启和关闭,在仓储方法中,ABP会自动化的进行链接管理。
当仓储方法被调用后,数据库链接会自动开启且启动事务。当仓储方法执行结束而且返回之后,全部的实体变化都会被储存, 事务被提交而且数据库链接被关闭,一切都由ABP自动化的控制。若是仓储方法抛出任何类型的异常,事务会自动地回滚而且数据链接会被关闭。上述全部操做在实现了IRepository接口的仓储类全部公开的方法中均可以被调用。
若是仓储方法调用其它仓储方法(即使是不一样仓储的方法),它们共享同一个链接和事务。链接会由仓储方法调用链最上层的那个仓储方法所管理。更多关于数据库管理,详见UnitOfWork文件。
全部的仓储对象都是暂时性的。这就是说,它们是在有须要的时候才会被建立。ABP大量的使用依赖注入,当仓储类须要被注入的时候,新的类实体会由注入容器会自动地建立。见相根据注入文件有更多信息。
但愿更多国内的架构师能关注到ABP这个项目,也许这其中有能帮助到您的地方,也许有您的参与,这个项目能够发展得更好。
欢迎加ABP架构设计交流QQ群:134710707