上一篇介紹了工作單元層超類型的封裝演化過程,本文將介紹對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/
下載地址:http://files.cnblogs.com/xiadao521/Util.2014.12.8.1.rar