文档目录
本节内容:
领域和映射层之间的媒介使用一种类似集合的接口来访问实体。通常地,每个实体(或聚合根)使用一个分离的仓储。
默认仓储
在ABP里,一个仓储类实现IRepository<TEntity,TPrimaryKey>接口。ABP默认地为每个实体类型自动创建一个默认仓储。你可以直接注入IRepository<TEntity>(或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<Person>并使用Insert方法。
自定义仓储
只有当实体需要创建一个自定义的仓储方法时,才需要你创建一个仓储类。
自定义仓储接口
如下示例,为一个Person实体定义一个仓储:
public interface IPersonRepository : IRepository<Person> { }
IPersonRepository扩展了IRepository<TEntity>,它用来定义具有int(Int32)类型Id属性的实体。如果你的实体键不是int,你可以扩展IRepository<TEntity,TPrimaryKey>接口,如下所示:
public interface IPersonRepository : IRepository<Person, long> { }
自定义仓储实现
ABP设计成与ORM(对象/关系映射)框架分离、或其它访问数据库技术分离。仓储开箱即用地实现了NHibernate和EntityFramework。查看ABP对这些框架的实现的相关文档:
基仓储方法
每个仓储包含一些通用的来自IRepository<TEntity>接口的方法,我们在此把它们的大部分方法,研究一下。
查询
获取一个单独实体
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)的实体。如果无法从数据库中找到给定Id的实体,将抛出异常。Single方法类似于Get方法,但接受一个表达式,而不是一个Id,所以你可以写一个lambda表达式来获取一个实体,用法示例:
var person = _personRepository.Get(42); var person = _personRepository.Single(p => p.Name == "Halil ?brahim Kalkan");
注意:Single会在无法获取符合表达式的实体,或是有多个符合表达式的实体时,抛出异常。
FirstOrDefault类似,但在找不到给定Id的实体时,返回null(代替抛出异常)。如果找到多个实体,则返回第一个。
Load不从数据库获取实体,但为延迟加载创建一个代理对象。如果你只是使用Id属性,那么实质上,不会从数据库中获取实体,只有当你访问实体的其它属性时,它才从数据库中获取实体。出于性能考虑,这个方法用来代替Get。它已经在NHibernate中实现了。如果ORM供应器没有实现它,Load方法就跟Get方法是一样的。
获取一个实体列表
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<T>,所以你在这个方法后可以添加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里,甚至它可以用在一个json表达式里。
关于 IQueryable<T>
当你在一个仓储方法之外调用GetAll(),必须有一个打开的数据库连接,这是因为IQueryable<T>是延迟执行的。它不会执行数据库的查询,除非你调用ToList()方法或在一个foreach循环(或其它方式访问查询里的项)。所以,当你调用ToList()方法时,数据库连接必须可用。对于一个Web项目,在部分情况你不必关心这个,因为Mvc控制器方法默认都是工作单元,且数据库连接在整个请求里都是可用的。为更好地理解它,请查看工作单元文档。
自定义返回值
还有一个另外的方法提供更强大的IQueryable,可以用在工作单元之外。
T Query<T>(Func<IQueryable<TEntity>, T> queryMethod);
Query方法接受一个lambda表达式(或方法),该表达式(或方法)接收IQueryable<T>并返回任何类型的对象。例如:
var people = _personRepository.Query(q => q.Where(p => p.Name.Contains("H")).OrderBy(p => p.Name).ToList());
由于给定的lambda(或方法)在仓储方法在执行,当数据库连接可用时,它被执行。你可以在执行查询后,返回实体列表、单个实体、一个投射或其它。
插入
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是自增时非常有用。InsertOrUpdate根据Id值执行插入或更新操作。最后,InsertOrUpdateAndGetId在插入或更新实体后,返回它的Id值。
更新
IRepository定义了更新一个已存在于数据库的实体的方法,它获取一个需要更新的实体,返回相同的实体。
TEntity Update(TEntity entity);
Task<TEntity> UpdateAsync(TEntity entity);
大部分时间,你不需要显式地调用Update方法,因为工作单元会在完成时调用Update方法。见工作单元文档获取更多信息。
删除
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。最后一个根据给定条件删除所有符合的实体,注意:所有匹配谓词的实体可能会从数据库中先获取到内存,然后再删除(看仓储如何实现了),所以使用它要小心了,这在有大量符合条件的实体时,可能引起性能问题。
其它
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);
关于异步方法
ABP支持异常编程模式,所以仓储方法有异步版本。如下例子为一个应用服务方法使用异常模式:
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框架都支持异步。EntityFramework支持。如果不支持,异步方法以同步的方式工作。同样的,例如,在EntityFramework中InsertAsync和Insert工作方式相同,因为EF直到工作单元完成前(也就是DbContext.SaveChanges),代码不写入新的实体。
管理数据库连接
在一个仓储方法里,它不打开或关闭数据库连接,ABP自动管理数据库连接。
当进入一个仓储方法,ABP自动打开一个数据库连接并开始一个事务,当这个方法结束并返回时,所有的变化被保存,事务提交后关闭数据库连接。如果你的仓储方法抛出任何类型的异常,自动回滚事务并关闭数据库连接。这适用于所有实现IRepository接口的类的公开方法。
如果一个仓储方法调用另一个仓储方法(即使是一个不同仓储的方法),它们共享相同的连接和事务,第一个方法管理数据库的连接(打开/关闭)。获取更多的数据库连接管理信息,请查阅工作单元文档。
一个仓储的生命周期
所有仓储实例都是短暂的,它的意思是:它们为每次的使用都进行实例化。查阅依赖注入文档获取更多信息。
仓储最佳实践