详解ABP框架中的数据过滤器与数据传输对象的使用

数据过滤器(Data filters)
在数据库开发中,我们一般会运用软删除(soft-delete)模式,即不直接从数据库删除数据,而是标记这笔数据为已删除。因此,如果实体被软删除了,那么它就应该不会在应用程序中被检索到。要达到这种效果,我们需要在每次检索实体的查询语句上添加SQL的Where条件IsDeleted = false。这是个乏味的工作,但它是个容易被忘掉的事情。因此,我们应该要有个自动的机制来处理这些问题。

ABP提供数据过滤器(Data filters),它使用自动化的,基于规则的过滤查询。ABP已经有一些预定义的过滤器,当然也可以自行创建你专属的过滤器。

注意:只针对EntityFramework:ABP数据过滤器仅实现在EntityFramework。还无法在其它ORM工具中使用。见其它ORM章节于本文末端。

预定义过滤器
1.软删除接口(ISoftDelete)

软删除过滤器(Soft-delete filter )会过滤从数据库查询出来的实体且是自动套用(从结果集中提取出来)。如果实体需要被软删除,它需要实现ISoftDelete接口,该接口仅定义了一个IsDeleted属性。例:

 public class Person : Entity, ISoftDelete {
 public virtual string Name { get; set; }
 public virtual bool IsDeleted { get; set; }
 }

Person实体实际上并没有从数据库中被删除,当删除此实体时,IsDeleted属性值会被设定为true。当你使用IRepository.Delete方法时,ABP会自动完成这些工作(你可以手动设定IsDeleted为true,但是Delete方法更加自然且是较建议的方式)。

当实现了ISoftDelete之后,当你已经从数据库中取得了People列表,已被删除的People实体并不会被检索到。在这里有一个示例类,该类使用了person仓储来取得所有的People实体:

 public class MyService {
 private readonly IRepository<Person> _personRepository;

 public MyService(IRepository<Person> personRepository) {
  _personRepository = personRepository;
 }

 public List<Person> GetPeople() {
  return _personRepository.GetAllList();
 }
 }

GetPeople方法仅取得Person实体,该实体其IsDeleted = false(非删除状态)。所有的仓储方法以及导航属性都能够正常运作。我们可以添加一些其它的Where条件,Join...等等。它将会自动地添加IsDeleted=false条件到生成的SQL查询语句中。

注意:何时启用?ISoftDelete过滤器总是启用,除非你直接禁用它。

提醒:如果你实现了IDeletionAudited接口(该接口继承自ISoftDelete),删除创建时间和被删除的用户Id,这些都会由ABP进行自动的处理。

2.多租接口(IMustHaveTenant)

如果你创建一个多租户的应用程序(储存所有租户的数据于单一一个数据库中),你肯定不会希望某个租户看到其它租户的资料。你可以实现IMustHaveTenant接口于此案例中,示例如下:

 public class Product : IMustHaveTenant {
 public virtual int TenantId { get; set; }

 public virtual string Name { get; set; }
 }

IMustHaveTenant定义了TenantId来区别不同的租户实体。ABP使用IAbpSession来取得当前TenantId并且自动地替当前租户进行过滤查询的处理。

注意:何时启用?IMustHaveTenant默认是启用的。如果当前使用并没有登入到系统或是当前用户是一个管理级使用者(管理级使用者即为一个最高权限的使用者,它可以管理所有租户和租户的资料),ABP会自动地禁用IMustHaveTenant过滤器。因此,所有的租户的数据都可以被应用程序所检索。注意,这与安全性无关,你应该要对敏感数据进行验证授权处理。

3.多租接口(IMayHaveTenant)

如果一个实体类由多个租户(tenant)以及管理级使用者(host)所共享(这意味着该实体对象或许由租户(tenant)或是管理级使用者(host)所掌控),你可以使用IMayHaveTenantfilter。IMayHaveTenant接口定义了TenantId但是它是可空类(nullable)。

 public class Product : IMayHaveTenant {
 public virtual int? TenantId { get; set; }

 public virtual string Name { get; set; }
 }

当为null值,则代表这是一个管理级使用者(host)所掌控的实体,若为非null值,则代表这个实体是由租户(tenant)所掌控,而其Id值即为TenantId。ABP使用IAbpSession接口来取得当前TenantId。IMayHaveTenant过滤器并不如IMustHaveTenant普遍常用。但是当作为管理级使用者(host)和租户(tenant)所需要的通用结构使用时,你或许会需要用到它。

何时启用?IMayHaveTenant接口总是启用的,除非你直接禁用它。

禁用过滤器
可以在工作单元(unit of work)中调用DisableFilter方法来禁用某个过滤器,如下所示:

var people1 = _personRepository.GetAllList();
using(_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.SoftDelete)) {
 var people2 = _personRepository.GetAllList();
 }

var people3 = _personRepository.GetAllList();

DisableFilter方法取得一或多个过滤器名称,且类型皆为string。AbpDataFilters.SoftDelete是一个常数字符串其包含了ABP标准的软删除过滤器。

people2亦可取得已标记为删除的People实体,而people1和people3将会是唯一的非已标记为删除的People实体。若配合使用using语法,你可以禁用其控制范围内(Scope)的过滤器。如果你不使用 using 语法 ,此过滤器会被一直禁用,直到工作单元(unit of work)结束或者再度启用它。(意思是:如果你使用"using"关键字声明,过滤器是启用状态;当前工作单元(unit of work)结束后,过滤器是禁止状态。如果不使用"using"关键字声明,默认过滤器是禁用状态,此时可以手动启用过滤器。)

你可以注入IUnitOfWorkManager并且在上述示例中使用。同样的,你可以使用CurrentUnitOfWork属性作为一个在应用服务中的简便方式(它是从ApplicationService类继承而来的)。

注意:关于using语法:假如过滤器在你调用DisableFilter方法并配合using语法之前已是启用,则过滤器会被禁用,并且会自动地在using语法结束后在度启用。但是若过滤器在using语法之前就已经被禁用了,DisableFilter方法实际上并不做任何式,并且过滤器会维持禁用状态即便是using语法的结束后。

启用过滤器
你可以在工作单元(unit of work)中使用EnableFilter方法启用过滤器,如同DisableFilter方法一般(两者互为正反两面)。EnableFilter亦会返回disposable来自动地重新禁用过滤器。

设定过滤器参数
 过滤器可以被参数化(parametric)。IMustHaveTenant过滤器是这类过滤器的一个范本,因为当前租户(tenant)的Id是在执行时期决定的。对于这些过滤器,如果真有需要,我们可以改变过滤器的值。举例如下:

CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42);
另一个示例如下:设定IMayHaveTenant过滤器的tenantId值:

CurrentUnitOfWork.SetfilterParameter(AbpDataFilters.MayHaveTenant, AbpDataFilters.Parameters.TenantId, 42);

自定义过滤器
欲创建定制的过滤器并且整合到ABP中,首先我们需要定义一个接口,该接口将会由使用这个过滤器的实体所实现。假设我们想要自动化地依PersonId进行过滤,示例如下:

 public interface IHasPerson {
 int PersonId { get; set; }
 }

然后我们就可以实现这个接口在我们的实体上了,示例如下:

 public class Phone : Entity, IHasPerson {
 [ForeignKey("PersonId")]
 public virtual Person Person { get; set; }

 public virtual int PersonId { get; set; }

 public virtual string Number { get; set; }
 }

因为ABP使用EntityFramework.DynamicFilters这个过滤器,我们使用它的规则(rule)来定义过滤器。在我们的DbContext类中,我们重写了OnModelCreating并且定义了过滤器如下示例所示:

 protected override void OnModelCreating(DbModelBuilder modelBuilder) {
 base.OnModelCreating(modelBuilder);
 modelBuilder.Filter("PersonFilter", (IHasPerson entity, int personId) => entity.PersonId == personId, 0 );
 }

PersonFilter过滤器在这里是一个唯一的过滤器名称。再来就是过滤器接口的参数定义和personId过滤器参数(不一定需要,假如过滤器是属于不可参数化(parametric)型),最后一个参数为personId的默认值。

最后一个步骤,我们需要注册这个过滤器到ABP工作单元(unit of work)系统中,设定的位置在我们模块里的PreInitialize方法。

Configuration.UnitOfWork.RegisterFilter("PersonFilter", false);

第一个参数是我们刚刚所定义的唯一名称,第二个参数指示这个过滤器预设是启用还是禁用。在声明完这些可参数化(parametric)的过滤器后,我们可以在执行期间指定它的值来操作这个过滤器。

 using(CurrentUnitOfWork.EnableFilter("PersonFilter")) {
 CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42);
 var phone = _phoneRepository.GetAllList();
 // ...
 }

我们可以从有些数据源中取得personId而不需要写死在程序代码中。上述示例是为了要能够程序化过滤器。过滤器可拥有0到更多的参数。假如是无参数的过滤器,它就不需要设定过滤器的值。同样地,假如它预设是启用,就不需要手动启用(当然的,我们也可以禁用它)。

EntityFramework.DynamicFilters的文件:若需要更多关于动态数据过滤器的相关信息,可以见其在git上的文件https://github.com/jcachat/EntityFramework.DynamicFilters

我们可以为安全性创建一个定制化的过滤器,主/被动实体,多租户...诸如此类的。

其它对象关系映射工具
ABP数据过滤器仅实现在Entity Framework上。对于其它ORM工具则尚不可用。但是, 实际上,你可以模仿这个模式到其它使用仓储来取得数据的案例下。这此案例中, 你可以创建一个定制的仓储并且覆写GetAll方法,如果有需要的话,可以一起修改其它资料检索方法。

数据传输对象(DTOs)
数据传输对象(Data Transfer Objects)用于应用层和展现层的数据传输。

展现层传入数据传输对象(DTO)调用一个应用服务方法,接着应用服务通过领域对象执行一些特定的业务逻辑并且返回DTO给展现层。这样展现层和领域层被完全分离开了。在具有良好分层的应用程序中,展现层不会直接使用领域对象(仓库,实体)。

数据传输对象的作用
为每个应用服务方法创建DTO看起来是一项乏味耗时的工作。但如果你正确使用它们,这将会解救你的项目。为啥呢?

1.抽象领域层 (Abstraction of domain layer)

在展现层中数据传输对象对领域对象进行了有效的抽象。这样你的层(layers)将被恰当的隔离开来。甚至当你想要完全替换展现层时,你还可以继续使用已经存在的应用层和领域层。反之,你可以重写领域层,修改数据库结构,实体和ORM框架,但并不需要对展现层做任何修改,只要你的应用层没有发生改变。

2.数据隐藏 (Data hiding)

想象一下,你有一个User实体拥有属性Id, Name, EmailAddress和Password。如果UserAppService的GetAllUsers()方法的返回值类型为List。这样任何人都可以查看所有人的密码,即使你没有将它打印在屏幕上。这不仅仅是安全问题,这还跟数据隐藏有关。应用服务应只返回展现层所需要的,不多不少刚刚好。

3.序列化 & 惰性加载 (Serialization & lazy load problems)

当你将数据(对象)返回给展现层时,数据有可能会被序列化。举个例子,在一个返回Json的MVC的Action中,你的对象需要被序列化成JSON并发送给客户端。直接返回实体给展现层将有可能会出现麻烦。

在真实的项目中,实体会引用其他实体。User实体会引用Role实体。所以,当你序列化User时,Role也将被序列化。而且Role还拥有一个List并且Permission还引用了PermissionGroup等等….你能想象这些对象都将被序列化吗?这有很有可能使整个数据库数据意外的被序列化。那么该如何解决呢?将属性标记为不可序列化?不行,因为你不知道属性何时该被序列化何时不该序列化。所以在这种情况下,返回一个可安全序列化,特别定制的数据传输对象是不错的选择哦。

几乎所有的ORM框架都支持惰性加载。只有当你需要加载实体时它才会被加载。比如User类型引用Role类型。当你从数据库获取User时,Role属性并没有被填充。当你第一次读取Role属性时,才会从数据库中加载Role。所以,当你返回这样一个实体给展现层时,很容易引起副作用(从数据库中加载)。如果序列化工具读取实体,它将会递归地读取所有属性,这样你的整个数据库都将会被读取。

在展现层中使用实体还会有更多的问题。最佳的方案就是展现层不应该引用任何包含领域层的程序集。

DTO 约定 & 验证
ABP对数据传输对象提供了强大的支持。它提供了一些相关的(Conventional)类型 & 接口并对DTO命名和使用约定提供了建议。当你像这里一样使用DTO,ABP将会自动化一些任务使你更加轻松。

一个例子 (Example)

让我们来看一个完整的例子。我们相要编写一个应用服务方法根据name来搜索people并返回people列表。Person实体代码如下:

public class Person : Entity
{
 public virtual string Name { get; set; }
 public virtual string EmailAddress { get; set; }
 public virtual string Password { get; set; }
}

首先,我们定义一个应用服务接口:

public interface IPersonAppService : IApplicationService
{
 SearchPeopleOutput SearchPeople(SearchPeopleInput input);
}

ABP建议命名input/ouput对象类似于MethodNameInput/MethodNameOutput,对于每个应用服务方法都需要将Input和Output进行分开定义。甚至你的方法只接收或者返回一个值,也最好创建相应的DTO类型。这样,你的代码才会更具有扩展性,你可以添加更多的属性而不需要更改方法的签名,这并不会破坏现有的客户端应用。

当然,方法返回值有可能是void,之后你添加一个返回值并不会破坏现有的应用。如果你的方法不需要任何参数,那么你不需要定义一个Input Dto。但是创建一个Input Dto可能是个更好的方案,因为该方法在将来有可能会需要一个参数。当然是否创建这取决于你。 Input和Output DTO类型定义如下:

 public class SearchPeopleInput : IInputDto
{
 [StringLength(40, MinimumLength = 1)]
 public string SearchedName { get; set; }
}

public class SearchPeopleOutput : IOutputDto
{
 public List<PersonDto> People { get; set; }
}

public class PersonDto : EntityDto
{
 public string Name { get; set; }
 public string EmailAddress { get; set; }
}

验证:作为约定,Input DTO实现IInputDto 接口,Output DTO实现IOutputDto接口。当你声明IInputDto参数时, 在方法执行前ABP将会自动对其进行有效性验证。这类似于ASP.NET MVC验证机制,但是请注意应用服务并不是一个控制器(Controller)。ABP对其进行拦截并检查输入。查看DTO 验证(DTO Validation)文档获取更多信息。 EntityDto是一个简单具有与实体相同的Id属性的简单类型。如果你的实体Id不为int型你可以使用它泛型版本。EntityDto也实现了IDto接口。你可以看到PersonDto并不包含Password属性,因为展现层并不需要它。

跟进一步之前我们先实现IPersonAppService:

public class PersonAppService : IPersonAppService
{
 private readonly IPersonRepository _personRepository;

 public PersonAppService(IPersonRepository personRepository)
 {
 _personRepository = personRepository;
 }
 public SearchPeopleOutput SearchPeople(SearchPeopleInput input)
 {
 //获取实体
 var peopleEntityList = _personRepository.GetAllList(person => person.Name.Contains(input.SearchedName));

 //转换成DTO
 var peopleDtoList = peopleEntityList
  .Select(person => new PersonDto
    {
     Id = person.Id,
     Name = person.Name,
     EmailAddress = person.EmailAddress
    }).ToList();

 return new SearchPeopleOutput { People = peopleDtoList };
 }
}

我们从数据库获取实体,将实体转换成DTO并返回output。注意我们没有手动检测Input的数据有效性。ABP会自动验证它。ABP甚至会检查Input是否为null,如果为null则会抛出异常。这避免了我们在每个方法中都手动检查数据有效性。

但是你很可能不喜欢手动将Person实体转换成PersonDto。这真的是个乏味的工作。Peson实体包含大量属性时更是如此。

DTO和实体间的自动映射
还好这里有些工具可以让映射(转换)变得十分简单。AutoMapper就是其中之一。你可以通过nuget把它添加到你的项目中。让我们使用AutoMapper来重写SearchPeople方法:

public SearchPeopleOutput SearchPeople(SearchPeopleInput input)
{
 var peopleEntityList = _personRepository.GetAllList(person => person.Name.Contains(input.SearchedName));
 return new SearchPeopleOutput { People = Mapper.Map<List<PersonDto>>(peopleEntityList) };
}

这就是全部代码。你可以在实体和DTO中添加更多的属性,但是转换代码依然保持不变。在这之前你只需要做一件事:映射

Mapper.CreateMap<Person, PersonDto>();

AutoMapper创建了映射的代码。这样,动态映射就不会成为性能问题。真是快速又方便。AutoMapper根据Person实体创建了PersonDto,并根据命名约定来给PersonDto的属性赋值。命名约定是可配置的并且很灵活。你也可以自定义映射和使用更多特性,查看AutoMapper的文档获取更多信息。

使用特性(attributes)和扩展方法来映射 (Mapping using attributes and extension methods)

ABP提供了几种attributes和扩展方法来定义映射。使用它你需要通过nuget将Abp.AutoMapper添加到你的项目中。使用AutoMap特性(attribute)可以有两种方式进行映射,一种是使用AutoMapFrom和AutoMapTo。另一种是使用MapTo扩展方法。定义映射的例子如下:

[AutoMap(typeof(MyClass2))] //定义映射(这样有两种方式进行映射)
public class MyClass1
{
 public string TestProp { get; set; }
}

public class MyClass2
{
 public string TestProp { get; set; }
}

接着你可以通过MapTo扩展方法来进行映射:

var obj1 = new MyClass1 { TestProp = "Test value" };
var obj2 = obj1.MapTo<MyClass2>(); //创建了新的MyClass2对象,并将obj1.TestProp的值赋值给新的MyClass2对象的TestProp属性。

上面的代码根据MyClass1创建了新的MyClass2对象。你也可以映射已存在的对象,如下所示:

var obj1 = new MyClass1 { TestProp = "Test value" };
var obj2 = new MyClass2();
obj1.MapTo(obj2); //根据obj1设置obj2的属性

 辅助接口和类型
ABP还提供了一些辅助接口,定义了常用的标准化属性。

ILimitedResultRequest定义了MaxResultCount属性。所以你可以在你的Input DTO上实现该接口来限制结果集数量。

IPagedResultRequest扩展了ILimitedResultRequest,它添加了SkipCount属性。所以我们在SearchPeopleInput实现该接口用来分页:

public class SearchPeopleInput : IInputDto, IPagedResultRequest
{
 [StringLength(40, MinimumLength = 1)]
 public string SearchedName { get; set; }

 public int MaxResultCount { get; set; }
 public int SkipCount { get; set; }
}

对于分页请求,你可以将实现IHasTotalCount的Output DTO作为返回结果。标准化属性帮助我们创建可复用的代码和规范。可在Abp.Application.Services.Dto命名空间下查看其他的接口和类型。

(0)

相关推荐

  • ABP框架的体系结构及模块系统讲解

    DDD分层 为了减少复杂性和提高代码的可重用性,采用分层架构是一种被广泛接受的技术. 为了实现分层的体系结构,ABP遵循DDD(领域驱动设计)的原则,将分为四个层次: 展现层(Presentation):提供一个用户界面,实现用户交互操作. 应用层(Application):进行展现层与领域层之间的协调,协调业务对象来执行特定的应用程序的任务.它不包含业务逻辑. 领域层(Domain):包括业务对象和业务规则,这是应用程序的核心层. 基础设施层(Infrastructure):提供通用技术来支持

  • 详解ABP框架的参数有效性验证和权限验证

    参数有效性验证 应用程序的输入数据首先应该被检验是否有效.输入的数据能被用户或其他应用程序提交.在Web应用中,通常进行2次数据有效性检验:包括客户端检验和服务端检验.客户端的检验主要是使用户有一个好的用户体验. 首先最好是在客户端检验其表单输入的有效性并且展示给客户端的那些字段输入是无效的.但是,服务器端的校验是更关键和不可缺失的(不要只做客户端检验而不做服务器端检验). 服务器端的检验通常是被应用服务(层)执行,应用服务(层)中的方法首先检验数据的有效性,然后才使用这些通过验证的数据.ABP

  • ABP框架中导航菜单的使用及JavaScript API获取菜单的方法

    每一个WEB应用程序都有导航菜单,Abp也为用户提供了通用的创建和显示菜单方式. 创建菜单 一个应用程序可能包含不同的模块,而每个模块都可能有它自己的菜单项.在Abp中,需要创建一个派生自NavigationProvider的类来定义一个菜单项. 假设我们有一个这样的主菜单: Tasks Reports Administration 1 User Management 2 Role Management 由上可知,Administration菜单项有两个子菜单项.对应的生成方法如下: publi

  • ASP.NET样板项目ABP框架的特性总结

    ABP是"ASP.NET Boilerplate Project (ASP.NET样板项目)"的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板. ASP.NET Boilerplate 基于DDD的经典分层架构思想,实现了众多DDD的概念(但没有实现所有DDD的概念). ABP的官方网站:http://www.aspnetboilerplate.com ABP在Github上的开源

  • ABP框架中的日志功能完全解析

    ASP.NET Boilerplate使用Castle Windsor's logging facility日志记录工具,并且可以使用不同的日志类库,比如:Log4Net, NLog, Serilog... 等等.对于所有的日志类库,Castle提供了一个通用的接口来实现,我们可以很方便的处理各种特殊的日志库,而且当业务需要的时候,很容易替换日志组件. 译者注释:Castle是什么:Castle是针对.NET平台的一个开源项目,从数据访问框架ORM到IOC容器,再到WEB层的MVC框架.AOP,

  • 解析ABP框架领域层中的实体类与仓储类

    领域层 实体是DDD(领域驱动设计)的核心概念之一.Eric Evans是这样描述的"很多对象不是通过它们的属性定义的,而是通过一连串的连续性事件和标识定义的"(引用领域驱动设计一书). 译者注:对象不是通过它们的属性来下根本性的定义,而应该是通过它的线性连续性和标识性定义的..所以,实体是具有唯一标识的ID且存储在数据库中.实体通常被映射成数据库中的一个表. 实体类(Entity classes) 在ABP中,实体继承自Entity类,请看下面示例: public class Per

  • 详解ABP框架中领域层的领域事件Domain events

    在C#中,一个类可以定义其专属的事件并且其它类可以注册该事件并监听,当事件被触发时可以获得事件通知.这对于对于桌面应用程序或独立的Windows Service来说非常有用.但是, 对于Web应用程序来说会有点问题,因为对象是根据请求(request)被创建并且它们的生命周期都很短暂.我们很难注册其它类别的事件.同样地,直接注册其它类别的事件也造成了类之间的耦合性. 在应用系统中,领域事件被用于解耦并且重用(re-use)商业逻辑. 事件总线 事件总线为一个单体(singleton)的对象,它由

  • 基于ASP.NET MVC的ABP框架入门学习教程

    为什么使用ABP 我们近几年陆续开发了一些Web应用和桌面应用,需求或简单或复杂,实现或优雅或丑陋.一个基本的事实是:我们只是积累了一些经验或提高了对,NET的熟悉程度. 随着软件开发经验的不断增加,我们发现其实很多工作都是重复机械的,而且随着软件复杂度的不断提升,以往依靠经验来完成一些简单的增删改查的做法已经行不通了.特别是用户的要求越来越高,希望添加的功能越来多,目前这种开发模式,已经捉襟见肘.我很难想象如何在现有的模式下进行多系统的持续集成并添加一些新的特性. 开发一个系统时,我们不可避免

  • 详解ABP框架中Session功能的使用方法

    如果一个应用程序需要登录,则它必须知道当前用户执行了什么操作.因此ASP.NET在展示层提供了一套自己的SESSION会话对象,而ABP则提供了一个可以在任何地方 获取当前用户和租户的IAbpSession接口. 关于IAbpSession 需要获取会话信息则必须实现IAbpSession接口.虽然你可以用自己的方式去实现它(IAbpSession),但是它在module-zero项目中已经有了完整的实现. 注入Session IAbpSession通常是以属性注入的方式存在于需要它的类中,不需

  • 解析ABP框架中的数据传输对象与应用服务

    数据传输对象(DTOs) 数据传输对象(Data Transfer Objects)用于应用层和展现层的数据传输. 展现层传入数据传输对象(DTO)调用一个应用服务方法,接着应用服务通过领域对象执行一些特定的业务逻辑并且返回DTO给展现层.这样展现层和领域层被完全分离开了.在具有良好分层的应用程序中,展现层不会直接使用领域对象(仓库,实体). 1.数据传输对象的作用: 为每个应用服务方法创建DTO看起来是一项乏味耗时的工作.但如果你正确使用它们,这将会解救你的项目.为啥呢? (1)抽象领域层 (

  • ABP框架的基础配置及依赖注入讲解

    配置ABP 配置是通过在自己模块的PreInitialize方法中来实现的 代码示例如下: public class SimpleTaskSystemModule : AbpModule { public override void PreInitialize() { //在你的应用中添加语言包,这个是英语和作者的土耳其语. Configuration.Localization.Languages.Add(new LanguageInfo("en", "English&quo

  • 解析ABP框架中的事务处理和工作单元

    通用连接和事务管理方法 连接和事务管理是使用数据库的应用程序最重要的概念之一.当你开启一个数据库连接,什么时候开始事务,如何释放连接...诸如此类的. 正如大家都知道的,.Net使用连接池(connection pooling).因此,创建一个连接实际上是从连接池中取得一个连接,会这么做是因为创建新连接会有成本.如果没有任何连接存在于连接池中,一个新的连接对象会被创建并且添加到连接池中.当你释放连接,它实际上是将这个连接对象送回到连接池.这并不是实际意义上的释放.这个机制是由.Net所提供的.因

  • 详解ABP框架中的日志管理和设置管理的基本配置

    日志管理 Server side(服务器端) ASP.NET Boilerplate使用Castle Windsor's logging facility日志记录工具,并且可以使用不同的日志类库,比如:Log4Net, NLog, Serilog... 等等.对于所有的日志类库,Castle提供了一个通用的接口来实现,我们可以很方便的处理各种特殊的日志库,而且当业务需要的时候,很容易替换日志组件. 译者注释:Castle是什么:Castle是针对.NET平台的一个开源项目,从数据访问框架ORM到

随机推荐