仓储(存储库)

仓储模式“ 使用类似于集合的接口来访问域对象,在域和数据映射层之间进行调解 ”(Martin Fowler)。

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

默认仓储

在ASP.NET Boilerplate中,仓储类实现 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>
{

}

自定义仓储实现

ASP.NET Boilerplate旨在独立于特定的ORM(对象/关系映射)框架或其他访问数据库的技术。仓储在NHibernate和 EntityFramework中实现,开箱即用。请参阅以下文档以在这些框架上的ASP.NET Boilerplate中实现仓储:

基本仓储方法

每个仓储都有一些来自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);

获取方法来得到一个实体与给定的主键(ID)。如果数据库中没有具有给定Id的实体,则会引发异常。方法类似于获取,但需要一个表达式,而不是一个ID。这样,您可以编写lambda表达式来获取实体。示例用法:

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

请注意,如果没有具有给定条件的实体或存在多个实体,则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 <T>

从仓储方法调用GetAll()时,必须存在打开的数据库连接。这是因为IQueryable <T>的延迟执行。除非您调用ToList()方法或在foreach循环中使用IQueryable <T>(或以某种方式访问查询的项目),否则它不会执行数据库查询。因此,当您调用ToList()方法时,数据库连接必须处于活动状态。对于Web应用程序,在大多数情况下您不必担心,因为默认情况下MVC控制器方法是工作单元,并且数据库连接可用于整个请求。请参阅UnitOfWork文档以更好地理解它。

自定义退货价值

还有一种方法可以提供IQueryable的功能,可以在一个工作单元中使用。

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

Query方法接受一个接收IQueryable <T>的lambda(或方法)并返回任何类型的对象。例:

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

由于给定的lamda(或方法)在存储库方法内执行,因此在数据库连接可用时执行。您可以返回实体列表,单个实体或投影或执行查询的其他内容。

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

插入方法简单地在一个数据库中插入新实体一个和返回相同的插入实体。InsertAndGetId方法返回一个新插入的实体的ID。如果Id是自动递增并且您需要新插入实体的Id,这将非常有用。InsertOrUpdate方法插入或通过检查其标识的值更新给定的实体。最后,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);

关于异步方法

ASP.NET Boilerplate支持异步编程模型。存储库方法具有异步版本。这是一个使用异步模型的示例应用程序服务方法:

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支持。如果不支持,则Async存储库方法同步工作。例如,InsertAsync的工作方式与EntityFramework中的Insert相同,因为在工作单元完成之前,EF不会将新实体写入数据库(也就是DbContext.SaveChanges)。

管理数据库连接

不在存储库方法中打开或关闭数据库连接。连接管理由ASP.NET Boilerplate自动完成。

打开存储库方法时,将打开数据库连接自动开始事务当方法结束并返回时,将保存所有更改提交事务并由ASP.NET Boilerplate 关闭数据库连接如果您的存储库方法抛出任何类型的Exception,则会自动回滚事务并关闭数据库连接。对于实现IRepository接口的所有类的公共方法都是如此。

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

存储库的生命周期

所有存储库实例都是Transient这意味着它们是按用途实例化的。有关更多信息,请参阅依赖注入文档。

存储库最佳实践

nidie.com.cn - 用心与你沟通