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 ...
随机推荐
- c语言基础学习04
=============================================================================涉及到的知识点有:程序的三种结构.条件分支语句 ...
- .24-浅析webpack源码之事件流compilation(2)
下一个compilation来源于以下代码: compiler.apply(new EntryOptionPlugin()); compiler.applyPluginsBailResult(&quo ...
- 立即掌握SSM框架的要诀
ssm框架的总结: 1. 首先是POM.xml 文件的配置,他的作用主要是添加依懒的关系和自动下载相关的包. 2.对jdbc.properties进行配置 ,作用就是连接你的数据库的配置. 3.对接着 ...
- 常用排序算法java实现
写在前面:纸上得来终觉浅.基本排序算法的思想,可能很多人都说的头头是到,但能说和能写出来,真的还是有很大区别的. 今天整理了一下各种常用排序算法,当然还不全,后面会继续补充.代码中可能有累赘或错误的地 ...
- Oracle临时表空间组
Oracle 10g之前,同一用户的多个会话只可以使用同一个临时表空间,因为在给定的时间只有一个临时表空间默认给用户,为了解决这个潜在的瓶颈,Oracle支持临时表空间组即包含多个临时表空间的集合.临 ...
- jQuery时间格式插件-moment.js的使用
jQuery时间格式插件-moment.js的使用 moment.js插件的使用,使用之前在页面引入对应的js文件: 详细的操作可见moment中文官网:http://momentjs.cn/ 日期格 ...
- Unix/Linux命令:SED
在Unix/Linux系统中,sed命令采用逐行处理的方式对文件进行查找.删除.替换.添加.插入等操作. 语法:sed [OPTION]... {script-only-if-no-other-scr ...
- RocketMQ-顺序消费
看了https://www.jianshu.com/p/453c6e7ff81c这篇博客,得出顺序消费的结论."要实现严格的顺序消息,简单且可行的办法就是:保证生产者 - MQServer ...
- 链表法解决hash冲突
/* @链表法解决hash冲突 * 大单元数组,小单元链表 */ #pragma once #include <string> using namespace std; template& ...
- Java序列化小结
title: Java序列化小结 date: 2017-05-06 20:07:59 tags: 序列化 categories: Java基础 --- Java序列化就是将一个对象转化成一串二进制表示 ...