在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
领域层 译者注:对象不是通过它们的属性来下根本性的定义,而应该是通过它的线性连续性和标识性定义的。。所以,实体是具有唯一标识的ID且存储在数据库中。实体通常被映射成数据库中的一个表。 实体类(Entity classes) public class Person : Entity { public virtual string Name { get; set; } public virtual DateTime CreationTime { get; set; } public Task() { CreationTime = DateTime.Now; } } Person 类被定义为一个实体。它具有两个属性,它的父类中有Id属性。Id是该实体的主键。所以,Id是所有继承自Entity类的实体的主键(所有实体的主键都是Id字段)。 Id(主键)数据类型可以被更改。默认是int(int32)类型。如果你想给Id定义其它类型,你应该像下面示例一样来声明Id的类型。 public class Person : Entity<long> { public virtual string Name { get; set; } public virtual DateTime CreationTime { get; set; } public Task() { CreationTime = DateTime.Now; } } 你可以设置为string,Guid或者其它数据类型。 实体类重写了 equality (==) 操作符用来判断两个实体对象是否相等(两个实体的Id是否相等)。还定义了一个IsTransient()方法来检测实体是否有Id属性。 接口约定 (1)审计(Auditing) 实体类实现 IHasCreationTime 接口就可以具有CreationTime的属性。当该实体被插入到数据库时, ABP会自动设置该属性的值为当前时间。 public interface IHasCreationTime { DateTime CreationTime { get; set; } } Person类可以被重写像下面示例一样实现IHasCreationTime 接口: public class Person : Entity<long>, IHasCreationTime { public virtual string Name { get; set; } public virtual DateTime CreationTime { get; set; } public Task() { CreationTime = DateTime.Now; } } ICreationAudited 扩展自 IHasCreationTime 并且该接口具有属性 CreatorUserId : public interface ICreationAudited : IHasCreationTime { long? CreatorUserId { get; set; } } 当保存一个新的实体时,ABP会自动设置CreatorUserId 的属性值为当前用户的Id 你可以轻松的实现ICreationAudited接口,通过派生自实体类 CreationAuditedEntity (因为该类已经实现了ICreationAudited接口,我们可以直接继承CreationAuditedEntity 类就实现了上述功能)。它有一个实现不同ID数据类型的泛型版本(默认是int),可以为ID(Entity类中的ID)赋予不同的数据类型。 public interface IModificationAudited { DateTime? LastModificationTime { get; set; } long? LastModifierUserId { get; set; } } 当更新一个实体时,ABP会自动设置这些属性的值。你只需要在你的实体类里面实现这些属性。 如果你想实现所有的审计属性,你可以直接扩展 IAudited 接口;示例如下: public interface IAudited : ICreationAudited, IModificationAudited { } 作为一个快速开发方式,你可以直接派生自AuditedEntity 类,不需要再去实现IAudited接口(AuditedEntity 类已经实现了该功能,直接继承该类就可以实现上述功能),AuditedEntity 类有一个实现不同ID数据类型的泛型版本(默认是int),可以为ID(Entity类中的ID)赋予不同的数据类型。 (2)软删除(Soft delete) 软删除是一个通用的模式被用来标记一个已经被删除的实体,而不是实际从数据库中删除记录。例如:你可能不想从数据库中硬删除一条用户记录,因为它被许多其它的表所关联。为了实现软删除的目的我们可以实现该接口 ISoftDelete: public interface ISoftDelete{ bool IsDeleted { get; set; } } ABP实现了开箱即用的软删除模式。当一个实现了软删除的实体正在被被删除,ABP会察觉到这个动作,并且阻止其删除,设置IsDeleted 属性值为true并且更新数据库中的实体。也就是说,被软删除的记录不可以从数据库中检索出,ABP会为我们自动过滤软删除的记录。(例如:Select查询,这里指通过ABP查询,不是通过数据库中的查询分析器查询。) 如果你用了软删除,你有可能也想实现这个功能,就是记录谁删除了这个实体。要实现该功能你可以实现IDeletionAudited 接口,请看下面示例: public interface IDeletionAudited : ISoftDelete { long? DeleterUserId { get; set; } DateTime? DeletionTime { get; set; } } 正如你所看到的IDeletionAudited 扩展自 ISoftDelete接口。当一个实体被删除的时候ABP会自动的为这些属性设置值。 public interface IFullAudited : IAudited, IDeletionAudited { } 作为一个快捷方式,你可以直接从FullAuditedEntity 类派生你的实体类,因为该类已经实现了IFullAudited接口。 注意:所有的审计接口和类都有一个泛型模板为了导航定义属性到你的User 实体(例如:ICreationAudited<TUser>和FullAuditedEntity<TPrimaryKey, TUser>),这里的TUser指的进行创建,修改和删除的用户的实体类的类型,详细请看源代码(Abp.Domain.Entities.Auditing空间下的FullAuditedEntity<TPrimaryKey, TUser>类),TprimaryKey 只的是Entity基类Id类型,默认是int。 (3)激活状态/闲置状态(Active/Passive) 有些实体需要被标记为激活状态或者闲置状态。那么你可以为实体采取active/passive状态的行动。基于这个原因而创建的实体,你可以扩展IPassivable 接口来实现该功能。该接口定义了IsActive 的属性。 如果你首次创建的实体被标记为激活状态,你可以在构造函数设置IsActive属性值为true。 这是不同于软删除(IsDeleted)。如果实体被软删除,它不能从数据库中被检索到(ABP已经过滤了软删除记录)。但是对于激活状态/闲置状态的实体,你完全取决于你怎样去获取这些被标记了的实体。 IEntity接口
实际上,仓储被用于领域对象在数据库上的操作(实体Entity和值对象Value types)。一般来说,我们针对不同的实体(或聚合根Aggregate Root)会创建相对应的仓储。 IRepository接口 针对Person实体的仓储接口声明的示例如下所示: public interface IPersonRepository : IRepository<Person> { } IPersonRepository继承自IRepository<TEntity>,用来定义Id的类型为int(Int32)的实体。如果你的实体Id数据类型不是int,你可以继承IRepository<TEntity, TPrimaryKey>接口,如下所示: public interface IPersonRepository : IRepository<Person, long> { } 对于仓储类,IRepository定义了许多泛型的方法。比如: Select,Insert,Update,Delete方法(CRUD操作)。在大多数的时候,这些方法已足已应付一般实体的需要。如果这些方对于实体来说已足够,我们便不需要再去创建这个实体所需的仓储接口/类。在Implementation章节有更多细节。 (1)查询(Query) IRepository定义了从数据库中检索实体的常用方法。 A、取得单一实体(Getting single entity): TEntity Get(TPrimaryKey id); Task<TEntity> GetAsync(TPrimaryKey id); TEntity Single(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 => o.Name == "Halil ibrahim Kalkan"); 注意,Single方法会在给出的条件找不到实体或符合的实体超过一个以上时,都会抛出例外。 FirstOrDefault也一样,但是当没有符合Lambda表达式或Id的实体时,会回传null(取代抛出异常)。当有超过一个以上的实体符合条件,它只会返回第一个实体。 Load并不会从数据库中检索实体,但它会创建延迟执行所需的代理对象。如果你只使用Id属性,实际上并不会检索实体,它只有在你存取想要查询实体的某个属性时才会从数据库中查询实体。当有性能需求的时候,这个方法可以用来替代Get方法。Load方法在NHibernate与ABP的整合中也有实现。如果ORM提供者(Provider)没有实现这个方法,Load方法运行的会和Get方法一样。 ABP有些方法具有异步(Async)版本,可以应用在异步开发模型上(见Async方法相关章节)。 B、取得实体列表(Getting 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被用于从数据库中检索所有实体。重载并且提供过滤实体的功能,如下: var allPeople = _personRespository.GetAllList(); var somePeople = _personRepository.GetAllList(person => person.IsActive && person.Age > 42); GetAll返回IQueryable<T>类型的对象。因此我们可以在调用完这个方法之后进行Linq操作。示例:
//例子一 var query = from person in _personRepository.GetAll() where person.IsActive orderby person.Name select person; var people = query.ToList(); //例子二 List<Person> personList2 = _personRepository.GetAll().Where(p => p.Name.Contains("H")).OrderBy(p => p.Name).Skip(40).Take(20).ToList(); 如果调用GetAll方法,那么几乎所有查询都可以使用Linq完成。甚至可以用它来编写Join表达式。 说明:关于IQueryable<T> 有些方法拥有异步版本,可应用在异步开发模型(见关于async方法章节)。 自定义返回值(Custom return value) ABP也有一个额外的方法来实现IQueryable<T>的延迟加载效果,而不需要在调用的方法上添加UnitOfWork这个属性卷标。 T Query<T>(Func<IQueryable<Tentity>,T> queryMethod); 查询方法接受Lambda(或一个方法)来接收IQueryable<T>并且返回任何对象类型。示例如下:
var people = _personRepository.Query(q => q.Where(p => p.Name.Contains("H")).OrderBy(p => p.Name).ToList()); (2)新增(insert) 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)。当我们采用自动递增标识符值且需要取得实体的新产生标识符值时非常好用。InsertOfUpdate会新增或更新实体,选择那一种是根据Id是否有值来决定。最后,InsertOrUpdatedAndGetId会在实体被新增或更新后返回Id值。
所有的方法都拥有异步版本可应用在异步开发模型(见关于异步方法章节) (3)更新(UPDATE) IRepository定义一个方法来实现更新一个已存在于数据库中的实体。它更新实体并返回相同的实体对象。 TEntity Update(TEntity entity); Task<TEntity> UpdateAsync(TEntity entity); (4)删除(Delete)
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。
最后一个方法接受一个条件来删除符合条件的实体。要注意,所有符合predicate表达式的实体会先被检索而后删除。因此,使用上要很小心,这是有可能造成许多问题,假如果有太多实体符合条件。 所有的方法都拥有async版本来应用在异步开发模型(见关于异步方法章节)。 (5)其它方法(others) 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<TEntity, bool>> predicate); 所有的方法都拥有async版本被应用在异步开发模型(见关于异步方法章节)。 (6)关于异步方法(About Async methods) ABP支持异步开发模型。因此,仓储方法拥有Async版本。在这里有一个使用异步模型的application service方法的示例: 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方法是异步的并且使用GetAllListAsync与await保留关键字。 Async不是在每个ORM框架都有提供。 上例是从EF所提供的异步能力。如果ORM框架没有提供Async的仓储方法则它会以同步的方式操作。同样地,举例来说,InsertAsync操作起来和EF的新增是一样的,因为EF会直到单元作业(unit of work)完成之后才会写入新实体到数据库中(DbContext.SaveChanges)。 仓储的实现 仓储要使用NHibernate或EF来实现都很简单。 EntityFramework 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方法。当你有需要为实体创建一个客制的仓储方法,那么你就应该创建一个仓储类给指定的实体。 管理数据库连接 当仓储方法被调用后,数据库连接会自动开启且启动事务。当仓储方法执行结束并且返回以后,所有的实体变化都会被储存, 事务被提交并且数据库连接被关闭,一切都由ABP自动化的控制。如果仓储方法抛出任何类型的异常,事务会自动地回滚并且数据连接会被关闭。上述所有操作在实现了IRepository接口的仓储类所有公开的方法中都可以被调用。 如果仓储方法调用其它仓储方法(即便是不同仓储的方法),它们共享同一个连接和事务。连接会由仓储方法调用链最上层的那个仓储方法所管理。更多关于数据库管理,详见UnitOfWork文件。 储的生命周期 仓储的最佳实践 |
请发表评论