返回总目录


本篇目录

Martin Fowler对仓储的定义

位于领域层和数据映射层之间,使用类似集合的接口来访问领域对象。

在实践中,仓储是执行领域对象(实体和值对象)的数据库操作。一般地,一个分离的仓储用于一个实体(或者聚合根)。

IRepository接口###

在ABP中,一个仓储类应该实现一个IRepository接口。为每一个仓储定义一个接口是一个好的做法。

一个Person实体的仓储定义如下:

public interface IPersonRepository : IRepository<Person>
{
}

IPersonRepository扩展了IRepository,它用于定义拥有主键类型为int32的实体。如果你的实体不是int,那么可以扩展IRepository<TEntity,TPrimaryKey>接口,如下所示:

public interface IPersonRepository : IRepository<Person, long>
{
}

IRepository为仓储类定义了最通用的方法,如select,insert,update和delete方法(CRUD操作)。大多数情况下,这些方法对于简单的实体是足够了。如果这些方法对于一个实体来说已经足够了,那么就没有必要为这个实体创建仓储接口和仓储类了。看下面。

查询

IRepository定义了通用的方法,从数据库中检索实体。

获得单个实体
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)的实体。如果在数据库中没有找到这个实体,就会抛出异常。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属性,那么Entity实际上并没有检索到。只有你访问实体的其他属性,才会从数据库中检索。考虑到性能因素,这个就可以替换Get方法。这在NHiberbate中也实现了。如果ORM提供者没有实现它,那么Load方法会和Get方法一样地工作。

一些方法有用于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 = _personRepository.GetAllList();
var somePeople = _personRepository.GetAllList(person => person.IsActive && person.Age > 42);

GetAll返回的类型是IQueryable。因此,你可以在此方法之后添加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的延迟执行。直到调用ToList()方法或者在foreach循环中使用IQueryable(或者访问查询到的元素)时,才会执行数据库查询操作。因此,当调用ToList()方法时。数据库连接必须打开。这可以通过ABP中的UnitOfWork特性标记调用者方法来实现。注意:应用服务方法默认已经是UnitOfWork,因此,即使没有为应用服务层方法添加UnitOfWork特性,GetAll()方法也会正常工作。

这些方法也存在用于异步编程模型的asyn版本。

自定义返回值

也存在提供了IQueryable的额外方法,在调用的方法中不需要使用UnitOfWork。

T Query<T>(Func<IQueryable<TEntity>, T> queryMethod);

Query方法接受一个接收IQueryable的lambda(或方法),并返回任何对象的类型。例子如下:

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是自动增长的并且需要最新插入实体的Id,那么该方法很有用。InsertOrUpdate方法通过检查Id的值插入或更新给定的实体。最后,当插入或者更新之后,InsertOrUpdateAndGetId返回该实体的值。

所有的方法都存在用于异步编程模型的async版本。

更新

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。

最后一个方法接受一个删除符合给定条件的所有实体的方法。注意,匹配给定谓词的所有实体都会从数据库中检索到然后被删除。因此,小心使用它,如果给定的条件存在太多的实体,那么可能会造成性能问题。

其他

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支持异步编程模型(APM)。因此,仓储方法有异步版本。下面是一个使用了异步模型的应用服务方法样例:

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框架都支持Async,但是EntityFramework支持。如果不支持,异步仓储方法就会同步进行。比如,在EF中,InsertAsync和Insert是等效的,因为直到工作单元完成(Dbcontext.SaveChanges),EF才会将新的实体写入数据库。

仓储实现###

ABP的设计独立于一个特定的ORM(对象/关系映射)框架或者访问数据库的其他技术。通过实现仓储接口,可以使用任何框架。

ABP使用NHibernateEntityFramework实现了开箱即用的仓储。关于这两个ORM框架可以关注后面的文档。

当使用NHibernate或EntityFramework时,如果标准方法是足够使用的话,那么不必为实体类创建仓储了。你可以直接注入IRepository(或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,并使用了Insert方法。这样,当你需要为一个实体创建一个自定义仓储方法时,你才应该为该实体创建一个仓储类。

管理数据库连接###

在仓储方法中,数据库连接是没有打开的或是关闭的。ABP对于数据库连接的管理是自动处理的。

当将要进入一个仓储方法时,数据库连接会自动打开,并且事务自动开始。当仓储方法结束并返回的时候,ABP会自动完成:保存所有的更改,完成事务的提交和关闭数据库连接。如果仓储方法抛出任何类型的异常,那么事务会自动回滚并关闭数据库。这对于所有的实现了IRepository接口的类的公共方法都是成立的。

如果一个仓储方法调用了其他的仓储方法,那么它们会共享相同的连接和事务。进入仓储的第一个方法会管理数据库的连接。更多信息,请留意后面博客的工作单元。

一篇不错的数据库连接博客:细说数据库连接

仓储的生命周期###

所有的仓储实例都是Transient(每次使用时都会实例化)的。ABP强烈推荐使用依赖注入技术。当一个仓储类需要注入时,依赖注入的容器会自动创建该类的新实例。

仓储最佳实践###

  • 对于一个T类型的实体,使用IRepository仓储接口。除非真的需要,否则不要创建自定义的仓储。预定义的仓储方法对于很多情况足够用了。
  • 如果你正在创建一个自定义的仓储(通过扩展IRepository):
    • 仓储类应该是无状态的。这意味着,你不应该定义仓储级别的状态对象,而且一个仓储方法调用不应该影响其他的调用。
    • 自定义仓储方法不应该包含业务逻辑或者应用逻辑,而应该只执行数据相关的或者orm特定的任务。
    • 当仓储使用依赖注入时,给其他服务定义更少的或者不要定义依赖。

ABP理论学习之仓储的更多相关文章

  1. ABP(现代ASP.NET样板开发框架)系列之11、ABP领域层——仓储(Repositories)

    点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之11.ABP领域层——仓储(Repositories) ABP是“ASP.NET Boilerplate Proj ...

  2. ABP理论学习之Javascript API(理论完结篇)

    返回总目录 本篇目录 Ajax Notification Message UI block和busy 事件总线 Logging 其他工具功能 说在前面的话 不知不觉,我们送走了2015,同时迎来了20 ...

  3. ABP领域层——仓储(Repositories)

    ABP领域层——仓储(Repositories) 点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之11.ABP领域层——仓储(Repositories) ABP是 ...

  4. ABP理论学习之开篇介绍

    返回总目录 为了和2016年春节赛跑,完成该系列博客,我牺牲了今天中午的时间来完成该系列的第一篇----开篇介绍.开篇介绍嘛,读过大学教材的同学都知道,这玩意总是那么无聊,跟考试没关系,干脆直接跳过, ...

  5. ABP理论学习之N层架构

    返回总目录 自从写这个系列博客之后,发现很多园友还是希望有个直接运行的demo,其实在github上就有官方的demo,我直接把这demo的链接放到这里吧,另外,我分析,这些找不到demo的同学,很可 ...

  6. ABP理论学习之依赖注入

    返回总目录 本篇目录 什么是依赖注入 传统方式产生的问题 解决办法 依赖注入框架 ABP中的依赖注入基础设施 注册 解析 其他 ASP.NET MVC和ASP.NET Web API集成 最后提示 什 ...

  7. ABP理论学习之设置管理

    返回总目录 本篇目录 介绍 定义设置 获取设置值 更改设置 关于缓存 介绍 每个应用程序都需要存储一些设置信息,然后在应用程序中的某个地方使用这些设置.ABP提供了健壮的基础设施来存储或检索服务端和客 ...

  8. ABP理论学习之领域服务

    返回总目录 本篇目录 介绍 IDomainService接口和DomainService类 样例 创建一个接口 服务实现 调用应用服务 一些讨论 何不只使用应用服务 如何强制使用领域服务 介绍 领域服 ...

  9. ABP理论学习之工作单元(Unit of Work)

    返回总目录 本篇目录 公共连接和事务管理方法 ABP中的连接和事务管理 仓储类 应用服务 工作单元 工作单元详解 关闭工作单元 非事务的工作单元 工作单元方法调用其它 工作单元作用域 自动保存 IRe ...

随机推荐

  1. 【Java EE 学习 77 下】【数据采集系统第九天】【使用spring实现答案水平分库】【未解决问题:分库查询问题】

    之前说过,如果一个数据库中要存储的数据量整体比较小,但是其中一个表存储的数据比较多,比如日志表,这时候就要考虑分表存储了:但是如果一个数据库整体存储的容量就比较大,该怎么办呢?这时候就需要考虑分库了, ...

  2. 手机端页面rem自适应脚本

    什么是rem 参照 web app变革之rem 在我看来,rem就是1rem单位就等于html节点fontsize的像素值.所以改变html节点的fontsize是最为关键的一步.根据手机宽度改变相对 ...

  3. Eclipse中使用Maven创建web项目

    一.创建一个Maven项目 1.Eclipse中用Maven创建项目 上图中点击next 2.继续next 3.选maven-archetype-webapp后,next 4.填写相应的信息,Pack ...

  4. js整理5

    proto 每个对象具有的属性,指向构造该对象的构造函数的原型对象 prototype 函数的特有属性,指向原型对象:原型对象可以是对象,数组,函数等类型: constructor 原型对象和实例,都 ...

  5. Block Markov Coding & Decoding

    Block Markov coding在一系列block上进行.在除了第一个和最后一个block上,都发送一个新消息.但是,每个block上发送的码字不仅取决于新的信息,也跟之前的一个或多个block ...

  6. C#区分多态和重载-delphi也类似

    Delphi也是基于继承和接口的多态性.

  7. jQuery页面加载初始化的3种方法

    jQuery 页面加载初始化的方法有3种 ,页面在加载的时候都会执行脚本,应该没什么区别,主要看习惯吧,本人觉得第二种方法最好,比较简洁. 第一种: $(document).ready(functio ...

  8. Jstack Jmap jstat

    jstack jmap jstat 代码,这里以这个为例怎样使用jstack诊断Java应用程序故障 public class DeadLock { public static void main(S ...

  9. Ubuntu 树莓派2b交叉编译环境

    在一个平台上生成另一个平台上的可执行代码.为什么要大费周折的进行交叉编译呢?一句话:不得已而为之.有时是因为目的平台上不允许或不能够安装所需要的编译器,而又需要这个编译器的某些特征:有时是因为目的平台 ...

  10. 图片Base64编码

    我们经常在做Jquery插件的时候需要插入一些自定义的控件,比如说按钮,而我们自己又觉着button标签很丑,而且不同浏览器显示的效果还不一样,这个时候我们需要用到图片,当然,我们可以通过img标签添 ...