ABP框架中使用了數據傳輸對象和實體的概念,而實體一般又是與數據庫的表結構相對應的。在ABP框架中可以分別爲數據傳輸對象DTO與實體Entity、實體Entity與數據庫表Table建立映射關係,既可以減少一定的編碼工作量,也能降低數據傳輸對象、實體與數據庫表之間的耦合性。
一.對象映射–數據傳輸對象與實體之間的映射
在ABP框架中,數據傳輸對象(Data Transfer Objects)也就是DTO用於應用層和展現層的數據傳輸,也就是展現層和應用層的使用者看到的都是DTO,但同時應用層與基礎設施層之間的交互都是以領域層中的實體Entity爲橋樑進行的,因此必然存在着數據傳輸對象與實體之間的轉換。
以應用層一個新增職員的方法爲例,我們在應用服務接口中定義如下方法。
DetailEmployeeDto Create(CreateEmployeeDto input);
輸入爲CreateEmployeeDto新增數據傳輸對象,輸出爲DetailEmployeeDto詳情數據傳輸對象。
/// <summary>
/// 新增數據傳輸對象
/// </summary>
public class CreateEmployeeDto
{
/// <summary>
/// 人員姓名
/// </summary>
public virtual string EmployeeName { get; set; }
/// <summary>
/// 性別
/// </summary>
public virtual int EmployeeSex { get; set; }
/// <summary>
/// 職位/頭銜
/// </summary>
public virtual string EmployeeTitle { get; set; }
/// <summary>
/// 聯繫電話
/// </summary>
public virtual string EmployeePhone { get; set; }
}
/// <summary>
/// 詳情數據傳輸對象
/// </summary>
public class DetailEmployeeDto : EntityDto<string>
{
/// <summary>
/// 人員姓名
/// </summary>
public virtual string EmployeeName { get; set; }
/// <summary>
/// 性別
/// </summary>
public virtual int EmployeeSex { get; set; }
/// <summary>
/// 職位/頭銜
/// </summary>
public virtual string EmployeeTitle { get; set; }
/// <summary>
/// 聯繫電話
/// </summary>
public virtual string EmployeePhone { get; set; }
}
將接口實現後,就涉及到數據對象與實體之間的轉換;
public DetailEmployeeDto Create(CreateEmployeeDto input)
{
Employee entity=new Employee()
{
Id=Guid.NewGuid().ToString(),
EmployeeName = input.EmployeeName,
EmployeeTitle = input.EmployeeTitle,
EmployeeSex = input.EmployeeSex,
EmployeePhone = input.EmployeePhone
};
var result = _repository.Insert(entity);
DetailEmployeeDto output=new DetailEmployeeDto()
{
Id=result.Id,
EmployeeName = result.EmployeeName,
EmployeeTitle = result.EmployeeTitle,
EmployeePhone = result.EmployeePhone,
EmployeeSex = result.EmployeeSex
};
return output;
}
其中實體Employee爲
/// <summary>
/// 職員
/// </summary>
public class Employee : Entity<string>
{
/// <summary>
/// 人員姓名
/// </summary>
public virtual string EmployeeName { get; set; }
/// <summary>
/// 性別
/// </summary>
public virtual int EmployeeSex { get; set; }
/// <summary>
/// 職位/頭銜
/// </summary>
public virtual string EmployeeTitle { get; set; }
/// <summary>
/// 聯繫電話
/// </summary>
public virtual string EmployeePhone { get; set; }
}
可以看到在這個過程中需要將CreateEmployeeDto轉換爲Employee,再將Employee轉換爲DetailEmployeeDto。如果每個方法都這樣進行手動賦值,恐怕手敲斷了項目也完成不了。好在ABP框架提供了對象自動映射的組件,能夠大大簡化這一過程。
1.1.集成AutoMapper
ABP框架集成了AutoMapper組件,從字面意思上就可以看出來是自動映射的。Abp.AutoMapper提供了兩種代碼寫法來實現對象映射。
1.1.1. 屬性標記形式
在數據傳輸對象DTO或實體Entity的頭部可以添加屬性標記對映射關係進行定義。常見的有3種屬性:
- AutoMapTo(A):將對象B轉換爲對象A
- AutoMapFrom(A):將對象A轉換爲對象B
- AutoMap:對象A、B可以互相轉換
[AutoMapTo(typeof(Employee))]
public class CreateEmployeeDto
{
///
}
[AutoMapFrom(typeof(Employee))]
public class DetailEmployeeDto : EntityDto<string>
{
///
}
1.1.2. 單獨定義形式
除了屬性標記的形式外,也可以單獨創建類對映射關係進行管理,同樣也是來源於AutoMapper的用法。
public class EmployeeMapper
{
/// <summary>
/// 數據傳輸對象映射
/// </summary>
public static void CreateMapper(IMapperConfigurationExpression mapper)
{
mapper.CreateMap<CreateEmployeeDto, Employee>();
mapper.CreateMap<Employee, DetailEmployeeDto>();
}
}
同時在模塊初始化時載入上述映射關係。
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(typeof(MyApplicationModule).GetAssembly());
//對象映射
Configuration.Modules.AbpAutoMapper().Configurators.Add(EmployeeMapper.CreateMapper);
}
這樣做的好處是將對象映射關係統一管理,後面增加數據傳輸對象時也不用來回翻代碼,而且可以利用AutoMapper提供的方法進行更加複雜的配置。
mapper.CreateMap<Source, Target>()
.ForMember(d => d.TargetName, o => o.MapFrom(s => s.SourceName))//特定屬性映射
.ForMember(d => d.TargetNum, o => o.MapFrom((s => s.SourceNum > 20)))//屬性過濾映射
.ForMember(d => d.Id, o => o.Ignore());//忽略映射
1.1.3. 對象映射的使用
通過對象的映射可以將前面的代碼簡化爲:
public DetailEmployeeDto Create(CreateEmployeeDto input)
{
Employee entity = input.MapTo<Employee>();
var result = _repository.Insert(entity);
DetailEmployeeDto output = result.MapTo<DetailEmployeeDto>();
return output;
}
可以看到相當一部分的代碼都被縮減了。
1.2.IObjectMapper接口
上面提到的AutoMap屬性及MapTo擴展方法都來自於AutoMapper,ABP框架又做了進一步的封裝,主要功能並沒有變化。而ABP還提供了IObjectMapper接口,通過依賴注入引入到應用服務中,使用其Map方法同樣能實現對象之間的映射。
public class EmployeeAppService : ApplicationService
{
private readonly IRepository<Employee,string> _employeeRepository;
private readonly IObjectMapper _objectMapper;
public EmployeeAppService(IRepository<Employee,string> employeeRepository, IObjectMapper objectMapper)
{
_employeeRepository = employeeRepository;
_objectMapper = objectMapper;
}
public DetailEmployeeDto Create(CreateEmployeeDto input)
{
Employee entity = _objectMapper.Map<Employee>(input);
var result = _repository.Insert(entity);
DetailEmployeeDto output = _objectMapper.Map<DetailEmployeeDto>(result);
return output;
}
}
二.ORM映射–實體與數據庫表之間的映射
上一部分中數據傳輸對象與實體之間的映射本質上就類對象之間的轉換,而這一部分中實體與數據表之間的映射則是截然不同的東西。只是從一個更抽象的層面考慮兩部分都是一種對應關係的描述,所以勉強可以放在一起。
2.1 EFCore屬性映射的基本用法
這裏就以EntityFrameworkCore爲例。下面就是一個簡單的例子,可以通過實現IEntityTypeConfiguration接口對實體所對應的數據集表名、列名、數據類型、長度、默認值進行設置。此外也可以爲數據庫表添加初始化數據,以確保CodeFirst模式下數據庫有初始化數據。
/// <summary>
/// Employee實體映射
/// </summary>
public class EmployeeMap : IEntityTypeConfiguration<Employee>
{
public void Configure(EntityTypeBuilder<Employee> builder)
{
builder.ToTable("table_employee");
builder.HasKey(m => m.Id);//主鍵
builder.Property(m => m.Id).HasColumnName("RowGuid");//列名
builder.Property(m => m.EmployeeName).HasMaxLength(50);
builder.Property(m => m.EmployeeSex).HasMaxLength(10);
builder.Property(m => m.EmployeeTitle).HasMaxLength(50).HasDefaultValue("職員");
builder.Property(m => m.EmployeePhone).HasMaxLength(30);
#region 初始化數據
List<Employee> employees=new List<Employee>();
employees.Add(new Employee(){Id="p1",EmployeeName = "小紅", EmployeePhone = "13333333333",EmployeeTitle = "經理"});
employees.Add(new Employee() { Id = "p2", EmployeeName = "小明", EmployeePhone = "13333333334", EmployeeTitle = "主管", });
employees.Add(new Employee() { Id = "p3", EmployeeName = "小剛", EmployeePhone = "13333333335", EmployeeTitle = "組長" });
builder.HasData(employees.ToArray());
#endregion
}
}
以上的屬性映射關係最終都應包含在DbContext中的OnModelCreating方法中才能有效。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new EmployeeMap());
base.OnModelCreating(modelBuilder);
}
2.2 EFCore屬性映射的更多用法
- 設置表名
builder.ToTable(“table_name”);
- 設置主鍵
builder.HasKey(m => m.Id);
- 設置聯合主鍵
builder.HasKey(t =>new{t.EmployeeName,t.Id} );
- 取消數據庫字段標識(取消自動增長)
builder.Property(t=>t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
- 設置數據庫字段標識(自動增長)
builder.Property(t =>t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
- 設置字段最大長度
builder.Property(t => t.EmployeeName).HasMaxLength(50);
- 設置字段爲必需
builder.Property(t =>t.Id).IsRequired();
- 屬性不映射到數據庫
builder.Ignore(t => t.EmployeeName);
- 將屬性指定數據庫列名:
builder .Property(t => t.EmployeeName) .HasColumnName(“EmployeeName”);
- 級聯刪除(數據庫默認是不級聯刪除的)
builder.HasRequired(t => t.Company).WithMany(t => t.Employee).HasForeignKey(d => d.CompanyId).WillCascadeOnDelete();
- 設置爲Timestamp
builder.Property(t => t.Timestamp) .IsRowVersion();
- 1對0關係…(實體A可以包含零個或一個實體B)
builder.HasRequired(t => t.Instructor).WithOptional(t => t.OfficeAssignment);
- 1對1關係
builder.HasRequired(t => t.OfficeAssignment).WithRequiredPrincipal(t => t.Instructor);
- 1對多關係
builder.HasRequired(c => c.Company) .WithMany(t => t.Employee)
- 指定外鍵名(指定表Staff中的字段DepartmentID爲外鍵)
builder .HasRequired(c => c.Company) .WithMany(t => t.Employee) .Map(m => m.MapKey(“CompanyId”));
- 多對多關係
builder.HasMany(t => t.Instructors).WithMany(t => t.Courses)
- 多對多關係並指定連接表名及列名
builder.HasMany(t => t.Instructors)
.WithMany(t => t.Courses)
.Map(m =>
{
m.ToTable(“CourseInstructor”);
m.MapLEFtKey(“CourseID”);
m.MapRightKey(“InstructorID”);
});
如果使用NHibernate或則其他ORM框架,可能就是另一種寫法了,但基本思路是不變的。
通過對以上兩部分映射關係的合理運用,能將項目代碼變得更加靈活,對於開發效率的提升也是很有幫助的。