应用程序框架实战二十:映射层超类型
上一篇介绍了工作单元层超类型的封装演化过程,本文将介绍对Entity Framework映射层超类型的封装。
使用Entity Framework一般需要映射三种类型的对象,即实体、聚合、值对象。
聚合与实体映射的主要区别是:聚合映射单属性标识Id,并需要映射乐观离线锁Version,而实体的标识往往需要映射成复合属性,这样方便物理删除聚合中的实体。Entity Framework通过EntityTypeConfiguration进行实体映射。
值对象以嵌入值模式映射,这需要使用ComplexTypeConfiguration。
封装映射配置并不是必须的,但封装以后可以获得如下好处。
1. 辅助记忆。
如果你跟我一样记忆力很差,记不住上面两个类名,那么通过封装一个自定义的类型可以帮助你进行记忆。一旦封装完成,你就可以把系统或第三方的Api扔到一边。
2. 划分逻辑结构。
把所有映射代码放到一个方法,不方便阅读,我把它们划分成不同的方法,可以获得更清晰的结构。
3. 减少代码冗余。
对于聚合而言,可以把Id标识和Version乐观离线锁封装到层超类型,从而减少代码冗余。
映射层超类型实现
实体映射基类EntityMapBase
EntityMapBase从EntityTypeConfiguration继承,泛型参数TEntity使用IEntity接口约束,构造方法将映射配置从逻辑上分离到4个方法中,即映射表、映射标识、映射属性、映射导航属性。
在构造方法中调用虚方法有时候可能导致意想不到的错误,这种情况发生在子类构造方法的代码依赖某些虚方法,由于调用顺序混乱可能导致失败,不过这种情况还是比较少见,如果你碰到上述问题,请果断扔掉该映射基类,直接从EntityTypeConfiguration派生。
EntityMapBase用于映射实体,代码如下。
using System.Data.Entity.ModelConfiguration;
using Util.Domains;
namespace Util.Datas.Ef {
/// <summary>
/// 实体映射
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
public abstract class EntityMapBase<TEntity> : EntityTypeConfiguration<TEntity> where TEntity : class, IEntity {
/// <summary>
/// 初始化映射
/// </summary>
protected EntityMapBase() {
MapTable();
MapId();
MapProperties();
MapAssociations();
}
/// <summary>
/// 映射表
/// </summary>
protected abstract void MapTable();
/// <summary>
/// 映射标识
/// </summary>
protected abstract void MapId();
/// <summary>
/// 映射属性
/// </summary>
protected virtual void MapProperties() {
}
/// <summary>
/// 映射导航属性
/// </summary>
protected virtual void MapAssociations() {
}
}
}
聚合映射基类AggregateMapBase
AggregateMapBase继承于EntityMapBase,并重写了MapId和MapProperties,对标识Id和乐观锁进行映射。
另外,提供了两个泛型版本的AggregateMapBase, 提供AggregateMapBase<TEntity>的目的是使聚合映射更易用,因为我的大多数聚合都使用Guid类型,这样可以省一个参数。
AggregateMapBase用于映射聚合,代码如下。
using System;
using System.ComponentModel.DataAnnotations.Schema;
using Util.Domains;
namespace Util.Datas.Ef {
/// <summary>
/// 聚合根映射
/// </summary>
/// <typeparam name="TEntity">聚合根类型</typeparam>
/// <typeparam name="TKey">实体标识类型</typeparam>
public abstract class AggregateMapBase<TEntity, TKey> : EntityMapBase<TEntity> where TEntity : AggregateRoot<TKey> {
/// <summary>
/// 映射标识
/// </summary>
protected override void MapId() {
HasKey( t => t.Id );
}
/// <summary>
/// 映射属性
/// </summary>
protected override void MapProperties() {
Property( t => t.Version ).HasColumnName( "Version" ).IsRowVersion().HasDatabaseGeneratedOption( DatabaseGeneratedOption.Computed ).IsOptional();
}
}
/// <summary>
/// 聚合根映射
/// </summary>
/// <typeparam name="TEntity">聚合根类型</typeparam>
public abstract class AggregateMapBase<TEntity> : AggregateMapBase<TEntity, Guid> where TEntity : AggregateRoot<Guid> {
}
}
值对象映射基类ValueObjectMapBase
ValueObjectMapBase从ComplexTypeConfiguration继承,它唯一需要的就是映射属性,创建这个类只有一个原因——帮助你记忆。
ValueObjectMapBase用于映射值对象,代码如下。
using System.Data.Entity.ModelConfiguration;
namespace Util.Datas.Ef {
/// <summary>
/// 值对象映射
/// </summary>
/// <typeparam name="TValueObject">值对象类型</typeparam>
public abstract class ValueObjectMapBase<TValueObject> : ComplexTypeConfiguration<TValueObject> where TValueObject : class {
/// <summary>
/// 初始化值对象映射
/// </summary>
protected ValueObjectMapBase() {
MapProperties();
}
/// <summary>
/// 映射属性
/// </summary>
protected abstract void MapProperties();
}
}
之所以说映射基类不是必须的,是因为映射配置一般由代码生成器创建,所以能够从基类获得的好处不是非常明显。另外,很多人会觉得这导致过度封装。创建这几个类在很大程度上属于我个人习惯问题,介绍它们的目的是想告诉你,如果不想动脑筋记忆,就自己封装一层。
.Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。
谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/xiadao521/
下载地址:https://files.cnblogs.com/xiadao521/Util.2014.12.8.1.rar