ABP官方文档翻译 3.3 仓储
仓储
协调领域和数据映射层,使用类集合接口访问领域对象。"(Martin Fowler)
实际上,仓储用来执行领域对象的数据库操作(实体和值类型)。通常,每个对象(或聚合根)使用单独的仓储。
默认仓储
在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)类型主键的实体。如果实体的主键不是int类型,可以继承IRepository<TEntity,TPrimaryKey>接口,如下所示:
public interface IPersonRepository : IRepository<Person, long>
{ }
自定义仓储实现
ABP设计为独立于特定的ORM(对象/关系映射)框架或其他访问数据库的技术。在NHibernate和EntityFramework实现的仓储都是开箱即用的。可参见这些框架的相关文档:
基础仓储方法
每个仓储类有些来自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。所以,使用Single可以写一个lambda表达式来获取实体。示例用法:
var person = _personRepository.Get();
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 > );
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().Take().ToList();
使用GetAll,几乎所有的查询都可以使用Linq编写。即使它被用在连接表达式。
关于IQueryable
当调用仓储的GetAll()方法时,必须有一个打开的数据库连接。这是因为IQueryable<T>是延迟执行的。在调用ToList()方法或在foreach循环(或访问查询项时)里使用IQueryable<T>之前,它不会执行数据库查询。所以,当调用ToList()方法时,数据库连接必须是可用的。对于Web应用,不用关心数据库连接的问题,因为MVC控制器方法默认为一个工作单元,数据库连接在整个请求期间都是可用的。参见UnitOfWork文档了解更多。
自定义返回值
有一个额外的方法,可以使IQueryable在工作单元外使用。
T Query<T>(Func<IQueryable<TEntity>, T> queryMethod);
Query方法接收一个接收IQeryable<T>的lambda表达式(或方法)并返回对象的任何类型。
var people = _personRepository.Query(q => q.Where(p => p.Name.Contains("H")).OrderBy(p => p.Name).ToList());
因为给定的lambda(或方法)在仓储方法内执行,当数据库连接可用时执行。可以执行这个查询返回实体列表、单个实体、一个投射或其他的东西。
插入
IRepository接口定义了insert方法插入实体到数据库:
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返回插入或更新后实体的Id。
更新
IRepository定义了更新数据库中已存在实体的方法。它获取需要更新的实体并返回这个实体对象。
TEntity Update(TEntity entity);
Task<TEntity> UpdateAsync(TEntity entity);
大多数时候不需要显示的调用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是支持的。如果不支持,异步仓储方法将按同步方式执行。例如,在EF中InsertAsync与Insert方法执行方式相同,因为EF只有直到工作单元完成时才会写入新实体到数据库(a.k.a DbContext.SaveChanges)。
管理数据库连接
数据库连接不会在仓储方法中打开或关闭。连接管理由ABP自动管理。
当进入仓储方法时,数据库连接自动打开并开始一个事务。当方法结束并返回时,所有的更改被保存,事务提交、关闭数据库连接,这些由ABP自动完成。如果仓储方法抛出任何类型的异常,事务自动回滚并关闭数据库连接。所有实现IRepository接口类的公共方法都是这样的。
如果一个仓储方法调用另一个仓储方法(甚至是不同仓储的方法),这些方法将共享同样的连接和事务。数据库连接由进入仓储的第一个方法管理(打开或关闭)。关于数据库连接管理的更多信息,参见工作单元文档。
仓储生命周期
所有的仓储接口都是临时的。意味着,每次使用都会实例化。参见依赖注入文档了解更多信息。
仓储最佳实践
- 对于泛型T的实体,尽可能使用IRepository<T>接口。除非真的需要不要创建自定义仓储。预定义的仓储方法足够满足大多数场景。
- 如果创建了一个自定义仓储(通过扩展IRepository<TEntity>接口实现):
- 仓储类应该是无状态的。意味着,不应该定义仓储级别状态的对象,并且一个仓储方法的调用不能影响另一个仓储方法的调用。
- 自定义仓储方法不应该包含业务逻辑或应用逻辑。它应该仅仅执行数据相关或orm特定的任务。
- 当仓储可以使用依赖注入时,尽量少或不依赖于其他服务。
ABP官方文档翻译 3.3 仓储的更多相关文章
- ABP官方文档翻译 2.5 设置管理
设置管理 介绍 关于 ISettingStore 定义设置 设置范围 重写设置定义 获取设置值 服务端 客户端 更改设置 关于缓存 介绍 每个应用都需要存储设置,并且在应用的某些地方需要使用这些设置. ...
- ABP官方文档翻译 10.1 ABP Nuget包
ABP Nuget包 Packages Abp Abp.AspNetCore Abp.Web.Common Abp.Web Abp.Web.Mvc Abp.Web.Api Abp.Web.Api.OD ...
- ABP官方文档翻译 9.3 NHibernate集成
NHibernate集成 Nuget包 配置 实体映射 仓储 默认实现 自定义仓储 应用程序特定基础仓储类 ABP可以使用任何ORM框架,它内置集成NHibernate.此文档将讲解ABP如何使用NH ...
- ABP官方文档翻译 9.2 Entity Framework Core
Entity Framework Core 介绍 DbContext 配置 在Startup类中 在模块PreInitialize方法中 仓储 默认仓储 自定义仓储 应用程序特定基础仓储类 自定义仓储 ...
- ABP官方文档翻译 9.1 EntityFramework集成
EntityFramework集成 Nuget包 DbContext 仓储 默认仓储 自定义仓储 应用特定的基础仓储类 自定义仓储示例 仓储最佳实践 事务管理 数据存储 ABP可以使用ORM框架,它内 ...
- ABP官方文档翻译 7.2 Hangfire集成
Hangfire集成 介绍 ASP.NET Core集成 ASP.NET MVC 5.x集成 面板授权 介绍 Hangfire是一个综合的后台job管理器.你可以 把它集成到ABP,用来取代默认的后台 ...
- ABP官方文档翻译 7.1 后台Jobs和Workers
后台Jobs和Workers 介绍 后台Jobs 关于Job持久化 创建后台Job 在队列中添加一个新Job 默认的后台Job管理器 后台Job存储 配置 禁用Job执行 异常处理 Hangfire集 ...
- ABP官方文档翻译 4.2 数据传输对象
数据传输对象 DTOs的必要性 领域层的抽象 数据隐藏 序列化和懒加载问题 DTO转换和验证 示例 DTOs和实体间的自动映射 辅助接口和类 数据传输对象用来在应用层和展示层之间传输数据. 展示层调用 ...
- ABP官方文档翻译 4.1 应用服务
应用服务 IApplicationService接口 ApplicationService类 CrudService和AsyncCrudAppService类 简单的CRUD应用服务示例 自定义CRU ...
随机推荐
- 51 nod 1203 JZPLCM
原题链接 长度为N的正整数序列S,有Q次询问,每次询问一段区间内所有数的lcm(即最小公倍数).由于答案可能很大,输出答案Mod 10^9 + 7. 例如:2 3 4 5,询问[1,3]区间的最小 ...
- bzoj:2018 [Usaco2009 Nov]农场技艺大赛
Description Input 第1行:10个空格分开的整数: N, a, b, c, d, e, f, g, h, M Output 第1行:满足总重量最轻,且用度之和最大的N头奶牛的总体重模M ...
- [51nod1673]树有几多愁
lyk有一棵树,它想给这棵树重标号. 重标号后,这棵树的所有叶子节点的值为它到根的路径上的编号最小的点的编号. 这棵树的烦恼值为所有叶子节点的值的乘积. lyk想让这棵树的烦恼值最大,你只需输出最大烦 ...
- BZOJ 2463: [中山市选2009]谁能赢呢?(新生必做的水题)
2463: [中山市选2009]谁能赢呢? Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 2372 Solved: 1750[Submit][Sta ...
- [FZU1977] Pandora adventure
来学插头DP了= = GDKOI前觉得不会考数位DP,GDOI前觉得插头DP用不上.. 结果令人伤感>_< 这题并不用增加状态.. 只要在形成环的时候,让形成环的位置在最后一个必走点之后, ...
- python数据类型(二)
一.List(列表) List(列表) 是 Python 中使用最频繁的数据类型. 列表可以完成大多数集合类的数据结构实现.列表中元素的类型可以不相同,它支持数字,字符串甚至可以包含列表(所谓嵌套). ...
- RxJava整合Retrofit遇到的问题总结
一:初上手(填坑) Observable将事件序列执行完毕后,会回调Observe的onNext()方法和onCompleted()方法,当出现异常/错误时会调用onError()方法. 由此,我们推 ...
- python写一个防御DDos的脚本(请安好环境否则无法实验)
起因: 居然有ddos脚本,怎么可以没防御ddos的脚本! 开始: 1.请执行 install.py安装好DDos-defalte,会在root目录下多出这个文件夹 代码: 2.然后执行fyddos. ...
- DedeCMS首页调用缩略图为背景
配合{dede:arclist}{/dede:arclist}标签使用,既可以实现把缩略图作为背景,另外还需要用到background-position这个标签,做好背景的定位. http://www ...
- IntelliJ IDEA 2016.2.x 激活
注册码获取:http://idea.lanyus.com/