ABP官方文档翻译 3.3 仓储

 仓储

  • 默认仓储
  • 自定义仓储
    • 自定义仓储接口
    • 自定义仓储实现
  • 基础仓储方法管理数据库连接
    • 查询

      • 获取单个实体
      • 获取实体列表
    • 关于IQueryable
      • 自定义返回值
    • 插入
    • 更新
    • 删除
    • 其他
    • 关于异步方法
  • 管理数据库连接
  • 仓储生命周期
  • 仓储最佳实践

  协调领域和数据映射层,使用类集合接口访问领域对象。"(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(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编写。即使它被用在连接表达式。

关于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特定的任务。
    • 当仓储可以使用依赖注入时,尽量少或不依赖于其他服务。

返回主目录

时间: 2024-08-07 00:02:17

ABP官方文档翻译 3.3 仓储的相关文章

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.OData Abp.Web.Resources Abp.Web.SignalR Abp.Owin Abp.EntityFramework.Common Abp.EntityFramework Abp.EntityFramework.GraphDiff Abp.EntityFrameworkCore Ab

ABP官方文档翻译 9.3 NHibernate集成

NHibernate集成 Nuget包 配置 实体映射 仓储 默认实现 自定义仓储 应用程序特定基础仓储类 ABP可以使用任何ORM框架,它内置集成NHibernate.此文档将讲解ABP如何使用NHibernate,假定你对NHibernate已经有了一定的了解. Nuget包 在ABP中实现NHibernate做为ORM框架的Nuget包为Abp.NHibernate.你需要在应用程序中添加它.最好在一个单独的程序集中实现NHibernate并在这个程序集里依赖Abp.NHibernate包

ABP官方文档翻译 9.1 EntityFramework集成

EntityFramework集成 Nuget包 DbContext 仓储 默认仓储 自定义仓储 应用特定的基础仓储类 自定义仓储示例 仓储最佳实践 事务管理 数据存储 ABP可以使用ORM框架,它内置集成EntityFramework.本文档将讲解ABP如何使用EntityFramework.假定你对EntityFramework已经有了初级水平. Nuget包 在ABP中使用Abp.EntityFramework nuget包扩展了EntityFramework.需要将它添加到工程中.最好在

ABP官方文档翻译 7.2 Hangfire集成

Hangfire集成 介绍 ASP.NET Core集成 ASP.NET MVC 5.x集成 面板授权 介绍 Hangfire是一个综合的后台job管理器.你可以 把它集成到ABP,用来取代默认的后台job管理器.Hangfire可以使用相同的后台jobAPI.因此,你的代码与Hangfire是独立的.但是,如果你喜欢的话,可以直接使用Hangfire的API. Hangfire集成依赖于使用的框架. ASP.NET Core集成 Abp.HangFire.AspNetCore包用来集成到ASP

ABP官方文档翻译 4.1 应用服务

应用服务 IApplicationService接口 ApplicationService类 CrudService和AsyncCrudAppService类 简单的CRUD应用服务示例 自定义CRUD应用服务 GettingList Create和Update 其他方法参数 CRUD权限 工作单元 应用服务生命周期 应用服务将领域逻辑暴露给展示层.在展示层使用DTO(数据传输对象)作为参数调用应用服务,应用服务使用领域对象执行一些特定的业务逻辑,并返回DTO到展示层.因此,展示层与领域层是完全

ABP官方文档翻译 3.6 工作单元

工作单元 介绍 ABP中的连接和事务管理 传统的工作单元方法 控制工作单元 UnitOfWork特性 IUnitOfWorkManager 工作单元详情 禁用工作单元 无事务工作单元 一个工作单元方法调用另一个 工作单元范围 自动保存更改 IRepository.GetAll()方法 工作单元特性限制 选项 方法 SaveChanges 事件 介绍 在使用数据库的应用中,连接和事务管理是最重要的概念之一.什么时候打开连接,什么时候开始一个事务,如何释放连接等等.ABP使用工作单元系统来管理连接和

ABP官方文档翻译 3.5 规约

规约 介绍 示例 创建规范类 使用仓储规约 组合规约 讨论 什么时候使用? 什么时候不使用? 介绍 规约模式是一种特别的软件设计模式,通过使用布尔逻辑将业务规则链接起来重新调配业务规则.(维基百科). 尤其是,它通常用来为实体或其他业务对象定义可复用的过滤器. 示例 在这个部分,我们将看到规约模式的必要性.本部分是通用的,和ABP的实现没有必然的关系. 假定,有一个服务方法,计算所有客户的总数量,如下所示: public class CustomerManager { public int Ge

ABP官方文档翻译 1.2 N层架构

N层架构 介绍 ABP架构 其他(通用) 领域层 应用层 基础设施层 网络和展现层 其他 总结 介绍 应用程序代码库的分层架构是被广泛认可的可以减少程序复杂度.提高代码复用率的技术.为了实现分层架构,ABP遵循领域驱动设计的原则.在领域驱动设计中有四个基本层: 表现层:提供用户接口.使用应用层实现用户交互. 应用层:桥接表现层和领域层.协调业务对象来执行特定的应用任务. 领域层:包括业务对象以及业务规则.此层是整个应用的核心. 基础设施层:提供通用的技术能力来支持高层.基础设施层可以是使用ORM

ABP官方文档翻译 3.4 领域服务

领域服务 介绍 IDomainService接口和DomainService类 示例 创建接口 服务实现 使用应用服务 一些探讨 为什么只有应用服务? 如何强制使用领域服务? 介绍 领域服务(或者在DDD中单纯的服务)用来执行领域操作和业务规则.Eric Evans在他的DDD书中描述了一个好的服务有三个特征: 1. 与领域概念关联的操作,但不是实体或值对象的自然组成部分. 2. 接口的定义依照领域模型的其他元素. 3. 操作是无状态的. 不像应用服务那样获取或返回DTO,领域服务获取或返回领域