基于DDD的.NET开发框架 - ABP仓储实现

返回ABP系列html

ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称。git

ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板。github

ABP的官方网站:http://www.aspnetboilerplate.com数据库

ABP官方文档:http://www.aspnetboilerplate.com/Pages/Documents编程

Github上的开源项目:https://github.com/aspnetboilerplateapp

1、基本操做

仓储的定义:位于领域层和数据映射层之间,使用相似集合的接口来访问领域对象。框架

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

一、IRepository接口async

在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操做)。大多数状况下,这些方法对于简单的实体是足够了。若是这些方法对于一个实体来讲已经足够了,那么就没有必要为这个实体建立仓储接口和仓储类了。

 

1)、查询:

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 == "hi joye.net");

注意:若是根据给定的条件没有查找出实体或者查出不止一个实体,那么Single方法会抛出异常。

FirstOrDefault是类似的,可是若是根据给的的Id或者表达式没有找到实体,那么就会返回null。若是对于给定的条件存在不止一个实体,那么会返回找到的第一个实体。

Load方法不会从数据库中检索实体,可是会建立一个用于懒加载的代理对象。若是你只用了Id属性,那么Entity实际上并无检索到。只有你访问实体的其余属性,才会从数据库中检索。考虑到性能因素,这个就能够替换Get方法。这在NHiberbate中也实现了。若是ORM提供者没有实现它,那么Load方法会和Get方法同样地工做。

一些方法有用于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(或方法),它是在数据库链接打开的时候执行的。你能够返回实体列表,单个实体,一个投影或者执行了该查询的其余东西。

 

2)、插入:

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版本。

 

3)、更新:

IRepository定义了一个方法来更新数据库中已存在的实体。它能够得到要更新的实体并返回相同的实体对象。

TEntity Update(TEntity entity);
Task<TEntity> UpdateAsync(TEntity entity);

 

4)、删除:

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。

最后一个方法接受一个删除符合给定条件的全部实体的方法。注意,匹配给定谓词的全部实体都会从数据库中检索到而后被删除。所以,当心使用它,若是给定的条件存在太多的实体,那么可能会形成性能问题。

 

5)、其余:

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);

 

6)、关于异步方法:

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使用NHibernate和 EntityFramework实现了开箱即用的仓储。关于这两个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):

1)、仓储类应该是无状态的。这意味着,你不该该定义仓储级别的状态对象,并且一个仓储方法调用不该该影响其余的调用。

2)、自定义仓储方法不该该包含业务逻辑或者应用逻辑,而应该只执行数据相关的或者orm特定的任务。

3)、当仓储使用依赖注入时,给其余服务定义更少的或者不要定义依赖。

相关文章
相关标签/搜索