• 基于DDD的.NET开发框架


    返回ABP系列

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

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

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

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

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

    一、基本操作

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

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

    1、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操作)。大多数情况下,这些方法对于简单的实体是足够了。如果这些方法对于一个实体来说已经足够了,那么就没有必要为这个实体创建仓储接口和仓储类了。

    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才会将新的实体写入数据库。

    2、仓储实现

    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方法。这样,当你需要为一个实体创建一个自定义仓储方法时,你才应该为该实体创建一个仓储类。

    3、管理数据库的连接:

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

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

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

    4、仓储的生命周期

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

    5、仓储的最佳实践

    1、对于一个T类型的实体,使用IRepository仓储接口。除非真的需要,否则不要创建自定义的仓储。预定义的仓储方法对于很多情况足够用了。

    2、如果你正在创建一个自定义的仓储(通过扩展IRepository):

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

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

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

  • 相关阅读:
    简明Python3教程 12.问题解决
    简明Python3教程 11.数据结构
    【SPOJ 694】Distinct Substrings
    【codeforces Manthan, Codefest 17 C】Helga Hufflepuff's Cup
    【CF Manthan, Codefest 17 B】Marvolo Gaunt's Ring
    【CF Manthan, Codefest 17 A】Tom Riddle's Diary
    【SPOJ 220】 PHRASES
    【POJ 3261】Milk Patterns
    【POJ 3294】Life Forms
    【POJ 1226】Substrings
  • 原文地址:https://www.cnblogs.com/yinrq/p/5542300.html
Copyright © 2020-2023  润新知