斗爷

导航

ABP框架系列之四:(Repositories-仓库)

"Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects" (Martin Fowler).

Repositories, in practice, are used to perform database operations for domain objects (Entity and Value types). Generally, a seperated repository is used for each Entity (or Aggregate Root).

“在域和数据映射层之间使用类似于接口的接口访问域对象”(Martin Fowler)。

实际上,存储库用于执行域对象的数据库操作(实体和值类型)。通常,每个实体都使用独立的存储库(或聚合根)。

Default Repositories

In ASP.NET Boilerplate, a repository classes implement IRepository<TEntity, TPrimaryKey> interface. ABP can automatically creates default repositories for each entity type. You can directly inject IRepository<TEntity> (or IRepository<TEntity, TPrimaryKey>). An example application service uses a repository to insert an entity to database:

在ASP.NET的样板,一个库类实现<< tentity IRepository,tprimarykey >接口。ABP可以自动为每个实体类型创建默认存储库。你可以直接注入IRepository < tentity >(或< tentity IRepository,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 contructor-injects IRepository<Person> and uses the Insert method.

personappservice构造函数注入IRepository <person>采用插入法。

Custom Repositories

You only create a repository class for an entity when you need to create a custom repository method(s) for that entity.

当您需要为该实体创建自定义存储库方法时,只为实体创建一个存储库类。

Custom Repository Interface

A repository definition for a Person entity is shown below:

public interface IPersonRepository : IRepository<Person>
{

}

IPersonRepository extends IRepository<TEntity>. It's used to define entities which has a primary key type of int (Int32). If your entity's primary key is not int, you can extend IRepository<TEntity, TPrimaryKey> interface as shown below:

public interface IPersonRepository : IRepository<Person, long>
{

}

Custom Repository Implementation(自定义库的实现

ASP.NET Boilerplate is designed to be independent from a particular ORM (Object/Relational Mapping) framework or another technique to access to database. Repositories are implemented in NHibernate andEntityFramework as out-of-the-box. See documents to implement repositories in ASP.NET Boilerplate in these frameworks:

ASP.NET样板的设计是独立于特定ORM(对象/关系映射)框架或其他技术访问数据库。库是在andentityframework NHibernate实现开箱。这些框架中看到ASP.NET样板实施库文件:

  • NHibernate integration
  • EntityFramework integration

Base Repository Methods

Every repository has some common methods coming from IRepository<TEntity> interface. We will investigate most of them here.

Querying

Getting single entity
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 method is used to get an Entity with given primary key (Id). It throws exception if there is no entity in database with given Id. Single method is similar to Get but takes an expression rather than an Id. So, you can write a lambda expression to get an Entity. Example usages:

get方法用于获取具有给定主键(id)的实体。如果数据库中没有给定id的实体,则抛出异常。示例用法:

var person = _personRepository.Get(42);
var person = _personRepository.Single(p => p.Name == "John");

Notice that Single method throws exception if there is no entity with given conditions or there are more than one entity.

注意,如果没有给定条件的实体或有多个实体,则单个方法抛出异常。

FirstOrDefault is similar but returns null (instead of throwing exception) if there is no entity with given Id or expression. Returns first found entity if there are more than one entity for given conditions.

FirstOrDefault类似但返回null(而不是抛出异常)如果有特定身份或表达无实体。如果给定条件有多个实体,则返回第一个已找到实体。

Load does not retrieves entity from database but creates a proxy object for lazy loading. If you only use Id property, Entity is not actually retrieved. It's retrieved from database only if you access to other properties of entity. This can be used instead of Get, for performance reasons. It's implemented in NHibernate. If ORM provider does not implements it, Load method works as identical as Get method.

加载不会从数据库检索实体,但为延迟加载创建代理对象。如果只使用id属性,则不实际检索实体。只有在访问实体的其他属性时才从数据库中检索。由于性能的原因,这可以用来代替get。它的实施机制。如果ORM提供者没有实现它,加载方法的工作方式与get方法相同。

Getting a list of entities
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 is used to retrieve all entities from database. Overload of it can be used to filter entities. Examples:

getalllist用于检索数据库中的所有实体。它的重载可以用来过滤实体。实例:

var allPeople = _personRepository.GetAllList();
var somePeople = _personRepository.GetAllList(person => person.IsActive && person.Age > 42);

GetAll returns IQueryable<T>. So, you can add Linq methods after it. Examples:

获得返回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();

With using GetAll, almost all queries can be written in Linq. Even it can be used in a join expression.

使用GelAll,几乎都可以用LINQ查询。甚至可以在连接表达式中使用它。

About IQueryable<T>

When you call GetAll() out of a repository method, there must be an open database connection. This is because of deferred execution of IQueryable<T>. It does not perform database query unless you call ToList() method or use the IQueryable<T> in a foreach loop (or somehow access to queried items). So, when you call ToList() method, database connection must be alive. For a web application, you don't care about that in most cases since MVC controller methods are unit of work by default and database connection is available for entire request. See UnitOfWork documentation to understand it better.

当你调用getall()出库的方法,必须有一个开放的数据库连接。这是由于延期执行IQueryable <T>。除非你不调用tolist()方法或在foreach循环使用IQueryable <T>执行数据库查询(或者访问查询项目)。所以,当你调用tolist()方法、数据库连接必须活着。对于Web应用程序,在大多数情况下您并不关心这一点,因为MVC控制器方法默认为工作单元,整个请求可使用数据库连接。看到UnitOfWork文件更好的理解。

Custom return value

There is also an additional method to provide power of IQueryable that can be usable out of a unit of work.

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

Query method accepts a lambda (or method) that recieves IQueryable<T> and returns any type of object. Example:

var people = _personRepository.Query(q => q.Where(p => p.Name.Contains("H")).OrderBy(p => p.Name).ToList());

Since given lamda (or method) is executed inside the repository method, it's executed when database connection is available. You can return a list of entities, a single entity, a projection or something else that executes the query.

Insert

IRepository interface defines methods to insert an entity to database:

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 method simply inserts new entity to database and returns the same inserted entity. InsertAndGetId method returns Id of new inserted entity. This is useful if Id is auto increment and you need Id of the new inserted entity.InsertOrUpdate inserts or updated given entity by checking it's Id's value. Lastly, InsertOrUpdateAndGetId returns Id of the entity after inserting or updating.

插入方法只需将新实体插入数据库并返回相同的插入实体。insertandgetid方法返回新插入实体的ID。如果我是你的ID自动增量和需要插入新entity.insertorupdate插入或更新检查它的ID的值给定的实体,这是有用的。最后,插入或更新后的实体insertorupdateandgetid返回ID。

Update

IRepository defines methods to update an existing entity in the database. It gets the entity to be updated and returns the same entity object.

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

Most of times you don't need to explicitly call Update methods since unit of work system automatically saves all changes when unit of work completes. See unit of work documentation for more.

大多数情况下,您不需要显式地调用更新方法,因为工作单元系统在工作单元完成时自动保存所有更改。更多见工作单元文档。

Delete

IRepository defines methods to delete an existing entity from the database

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

First method accepts an existing entity, second one accepts Id of the entity to delete. The last one accepts a condition to delete all entities fit to given condition. Notice that all entities matches given predicate may be retrived from database and then deleted (based on repository implementation). So, use it carefully, it may cause performance problems if there are too many entities with given condition.

第一个方法接受现有实体,第二个接受实体的ID以删除。最后一个接受条件删除所有符合给定条件的实体。注意:所有实体匹配给定的谓词可以确定从数据库然后删除(基于知识库的实现)。所以,小心使用它,如果给定条件下有太多实体,可能会导致性能问题。

Others

IRepository also provides methods to get count of entities in a table.

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

About Async Methods(关于异步方法

ASP.NET Boilerplate supports async programming model. So, repository methods has Async versions. Here, a sample application service method that uses async model:

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 method is async and uses GetAllListAsync with await keyword.

Async may not be supported by all ORM frameworks. It's supported by EntityFramework. If not supported, Async repository methods works synchronously. Also, for example, InsertAsync works same as Insert in EntityFramework since EF does not write new entities to database until unit of work completes (a.k.a. DbContext.SaveChanges).

异步可能不被所有的ORM框架支持。它是由EntityFramework。如果不支持,异步存储方法同步工作。另外,例如,insertasync作品一样,将在EntityFramework自EF不写新的主体数据库到工作单元完成(又名DbContext。调用SaveChanges)。

Managing Database Connection(管理数据库连接)

A database connection is not opened or closed in a repository method. Connection management is made automatically by ASP.NET Boilerplate.

A database connection is opened and a transaction begins while entering a repository method automatically. When the method ends and returns, all changes are saved, transaction is commited and database connection isclosed automatically by ASP.NET Boilerplate. If your repository method throws any type of Exception, the transaction is automatically rolled back and database connection is closed. This is true for all public methods of classes those implement IRepository interface.

在自动进入存储库方法时,数据库连接被打开,事务开始。在方法结束时返回,所有的改变都被保存的,交易被提交与数据库的连接被关闭时自动通过ASP.NET样板。如果存储库方法抛出任何类型的异常,则事务将自动回滚,数据库连接已关闭。这类实现接口的所有公共方法的IRepository是真实的。

If a repository method calls to another repository method (even a method of different repository) they share same connection and transaction. Connection is managed (opened/closed) by the first method that enters a repository. For more information on database connection management, see UnitOfWork documentation.

如果存储库方法调用另一个存储库方法(甚至是不同存储库的方法),它们共享相同的连接和事务。连接是通过进入存储库的第一个方法来管理(打开/关闭)的。在数据库连接管理的更多信息,参见UnitOfWork文档。

Lifetime of a Repository(仓库的生命周期

All repository instances are Transient. It means, they are instantiated per usage. See Dependency Injection documentation for more information.

所有存储库实例都是暂时的。它意味着,每个使用都实例化它们。有关更多信息,请参见依赖注入文档。

Repository Best Practices

  • For an entity of T, use IRepository<T> wherever it's possible. Don't create custom repositories unless it's really needed. Pre-defined repository methods will be enough for many cases.
  • 对一个实体,使用IRepository <T>在可能的地方。除非真正需要,否则不要创建自定义存储库。预定义的存储库方法在很多情况下已经足够了。
  • If you are creating a custom repository (by extending IRepository<TEntity>);
    • Repository classes should be stateless. That means, you should not define repository-level state objects and a repository method call should not effect another call.
    • Custom repository methods should not contain business logic or application logic. It should just perform data-related or orm-specific tasks.
    • While repositories can use dependency injection, define less or no dependency to other services.
    • 存储库类应该是无状态的。这意味着,您不应该定义存储库级的状态对象,而存储库方法调用不应影响另一个调用。
      自定义存储库方法不应包含业务逻辑或应用程序逻辑。它应该只执行与数据相关的或特定于ORM的任务。
      虽然存储库可以使用依赖注入,但定义对其他服务的依赖性较少或不依赖。

posted on 2017-11-24 17:14  斗哥哥  阅读(2478)  评论(0编辑  收藏  举报