映射层超类型

上一篇介绍了工作单元层超类型的封装演化过程,本文将介绍对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();
    }
}

之所以说映射基类不是必须的,是因为映射配置一般由代码生成器创建,所以能够从基类获得的好处不是非常明显。另外,很多人会觉得这导致过度封装。创建这几个类在很大程度上属于我个人习惯问题,介绍它们的目的是想告诉你,如果不想动脑筋记忆,就自己封装一层。

下载地址:http://files.cnblogs.com/xiadao521/Util.2014.12.8.1.rar

应用程序框架实战