場景
在業務開發中,從倉儲至應用,中間一般還有一層模型對映服務,其中的核心主鍵俺管他叫對映器(Mapper)。現在業界已經有很多Mapper
的實現方案了,多為自實現如反射同名對映,或者使用大名鼎鼎的AutoMapper
。
AutoMapper
底層是有一些最佳化的,比很多自實現的反射同名對映要快,反射會佔用大量時間。但是不管AutoMapper再快,肯定是沒有直接賦值快( 直接new Data(){ Name = dataDto.Name }
)
一般來說,除非動態動編譯層,否則除了Lambda之外,很難有比原生賦值還快的方法了。
Lambda Mapper
我的LambdaMapper
的實現是基於我領域模型與檢視模型的基本定義來編寫的,讀者可能需要按照各自的業務進行修改。
如我的領域模型:
public class ApplicationMenuItem : EntityBase<Guid>
{
/// <summary>
/// 訪問時的預設引數min
/// </summary>
public string Parameter { get; set; }
/// <summary>
/// 導航地址
/// </summary>
[StringLength(150)]
public string Path { get; set; }
/// <summary>
/// 圖示
/// </summary>
public string Icon { get; set; }
/// <summary>
/// 上級選單
/// </summary>
public ApplicationMenuItem Parent { get; set; }
/// <summary>
/// 是否不顯示在導航欄
/// </summary>
public bool IsHidden { get; set; }
/// <summary>
/// 歸屬的客戶端
/// </summary>
public CddApplication CddApplication { get; set; }
}
Dto:
public class ApplicationMenuItemDto : DtoBase<Guid>
{
/// <summary>
/// 訪問時的預設引數
/// </summary>
public string Parameter { get; set; }
/// <summary>
/// 導航地址
/// </summary>
public string Path { get; set; }
/// <summary>
/// 圖示
/// </summary>
public string Icon { get; set; }
/// <summary>
/// 上級選單
/// </summary>
public string ParentId { get; set; }
public string ParentName { get; set; }
/// <summary>
/// 歸屬的客戶端
/// </summary>
public string CddApplicationId { get; set; }
public string CddApplicationName { get; set; }
兩種模型相互的對映的規則
-
領域模型 => 檢視模型
1.1 同名同型別屬性對映,同時提供不同型別但是同名型別的屬性型別轉換後賦值
1.2 導航屬性對映,如上面ApplicationMenuItem
的Parent.Id
和Parent.Name
會對映到ApplicationMenuItemDto
的ParentId
和ParentName
(當然這裡面還有一些空值判斷,如果Parent為空則不對映)
1.3 層級對映,類似於使用AutoMapper的時候,會將Parent.Parent.Name對映至ParentParentName -
檢視模型 => 領域模型
1.1 同名同型別屬性對映,同時提供不同型別但是同名型別的屬性型別轉換後賦值
1.2 從檢視模型的屬性中,拼接回導航屬性,如上面ApplicationMenuItemDto
的ParentId
和ParentName
會變成語句Parent = new ApplicationMenuItem(){ Id = applicationMenuItemDto.ParentId, Name = applicationMenuItemDto.ParentName}
程式碼實現
public class Mapper<TEntity, TEntityVM>
where TEntity : class, new()
where TEntityVM : class, new()
{
/// <summary>
/// 對於名稱空間的約束
/// </summary>
private const string configNamespace = "您的名稱空間";
/// <summary>
/// 轉檢視模型的lambda表示式快取
/// </summary>
private static Func<TEntity, TEntityVM> _mapToEntityVMCache;
/// <summary>
/// 轉實體模型的Lambda表示式快取
/// </summary>
private static Func<TEntityVM, TEntity> _mapToEntityCache;
static Mapper()
{
_mapToEntityVMCache = GetMapToEntityVMFunc();
_mapToEntityCache = GetMapToEntityFunc();
}
/// <summary>
/// 判斷型別是否為可操作的列表型別
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private static bool _isList(Type type)
{
if (typeof(System.Collections.IList).IsAssignableFrom(type))
{
return true;
}
foreach (var it in type.GetInterfaces())
{
if (it.IsGenericType && typeof(IList<>) == it.GetGenericTypeDefinition())
return true;
}
return false;
}
/// <summary>
/// 生成對映成檢視模型的Lambda表示式
/// </summary>
/// <returns></returns>
private static Func<TEntity, TEntityVM> GetMapToEntityVMFunc()
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(TEntity), "x");
// 一組用於對映的模型繫結表示式
List<MemberBinding> memberBindingList = new List<MemberBinding>();
foreach (var entityProperty in typeof(TEntity).GetProperties())
{
// 這裡的item是檢視模型對應的屬性
var item = typeof(TEntityVM).GetProperty(entityProperty.Name);
if (item == null)
{
// 如果是自定義型別
if (entityProperty.PropertyType.FullName.Contains(configNamespace) && entityProperty.PropertyType.IsClass
&& _isList(entityProperty.PropertyType) == false)
{
var rootName = "";
var nextLevelMemberExpression = Expression.Property(parameterExpression, entityProperty);
var res = GetSubPropertyMapConfig<TEntityVM>(memberBindingList, rootName, nextLevelMemberExpression, entityProperty,
new List<MemberExpression>() { nextLevelMemberExpression });
memberBindingList = res.Item1;
}
// 否則就是真的沒有
else
{
continue;
}
}
else if (item.PropertyType == entityProperty.PropertyType)
{
// 如果是不能寫的屬性
if (!item.CanWrite)
continue;
MemberExpression eProperty = Expression.Property(parameterExpression, entityProperty.Name);
MemberBinding memberBindingResult = Expression.Bind(item, eProperty);
memberBindingList.Add(memberBindingResult);
if (entityProperty.PropertyType.IsEnum)
{
var enumNameProp = typeof(TEntityVM).GetProperty(entityProperty.Name + "Name");
// 搜尋一下是否有字尾加Name的字串值
if (enumNameProp != null)
{
MethodInfo methodInfo = entityProperty.PropertyType.GetMethod("ToString", new Type[] { });
var resultExpression = Expression.Call(eProperty, methodInfo);
MemberBinding enumNameMemberBindingResult = Expression.Bind(enumNameProp, resultExpression);
memberBindingList.Add(enumNameMemberBindingResult);
}
}
}
// 其他型別轉string
else if (item.PropertyType != entityProperty.PropertyType && item.PropertyType == typeof(string))
{
// 如果是不能寫的屬性
if (!item.CanWrite)
continue;
MemberExpression memberExp = Expression.Property(parameterExpression, entityProperty.Name);
// 獲取ToString方法,一般來說,都應該是String型別
MethodInfo methodInfo = memberExp.Type.GetMethod("ToString", new Type[] { });
var resultExpression = Expression.Call(memberExp, methodInfo);
// ToString()之後再傳輸過去
MemberBinding memberBindingResult = Expression.Bind(item, resultExpression);
memberBindingList.Add(memberBindingResult);
}
// string轉其他型別
else if (item.PropertyType != entityProperty.PropertyType && entityProperty.PropertyType == typeof(string))
{
// 如果是不能寫的屬性
if (!item.CanWrite)
continue;
MemberExpression property = Expression.Property(parameterExpression, typeof(TEntity).GetProperty(entityProperty.Name));
MethodInfo parseMethod = item.PropertyType.GetMethod("Parse", new Type[] { typeof(string) });
MethodCallExpression resultExpression = Expression.Call(parseMethod, new List<Expression>() { property });
// p=> p.item.Name = p.item.Name
MemberBinding memberBindingResult = Expression.Bind(item, resultExpression);
memberBindingList.Add(memberBindingResult);
}
}
// p => new TOut(){item.Name = p.item.Name}
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TEntityVM)), memberBindingList.ToArray());
Expression<Func<TEntity, TEntityVM>> lambda = Expression.Lambda<Func<TEntity, TEntityVM>>(memberInitExpression, new ParameterExpression[] { parameterExpression });
return lambda.Compile();
}
/// <summary>
/// 二級型別對映處理
/// </summary>
/// <typeparam name="Target"></typeparam>
/// <param name="memberBindings"></param>
/// <param name="sourceType"></param>
/// <returns></returns>
private static Tuple<List<MemberBinding>, string, MemberExpression, List<MemberExpression>> GetSubPropertyMapConfig<Target>(List<MemberBinding> memberBindings, string rootName,
MemberExpression nowLevelMemberExpression,
PropertyInfo sourceSubTypeInfo,
List<MemberExpression> historyNullValueValidateMembers)
{
var targetModelProps = typeof(Target).GetProperties();
var sourceSubTypeProps = sourceSubTypeInfo.PropertyType.GetProperties();
// 根名稱,根據層級不斷疊加
rootName += sourceSubTypeInfo.Name;
if (historyNullValueValidateMembers == null)
{
historyNullValueValidateMembers = new List<MemberExpression>();
}
var subClassProps = sourceSubTypeProps.Where(x => x.PropertyType.FullName.Contains(configNamespace) && x.PropertyType.IsClass).ToList();
var baseClassProps = sourceSubTypeProps.Where(x => !x.PropertyType.FullName.Contains(configNamespace)).ToList();
// 二級的二級屬性對映
foreach (var baseClassProp in baseClassProps)
{
var item = targetModelProps.FirstOrDefault(x => x.Name == rootName + baseClassProp.Name);
if (item == null)
{
continue;
}
Expression nullValueValidateMemberExpression = null;
if (historyNullValueValidateMembers == null)
{
return new Tuple<List<MemberBinding>, string, MemberExpression, List<MemberExpression>>(memberBindings, rootName, nowLevelMemberExpression, new List<MemberExpression>());
}
// 空值判斷表示式拼接
else
{
for (var i = 0; i < historyNullValueValidateMembers.Count; i++)
{
if (i == 0)
{
nullValueValidateMemberExpression = Expression.Equal(historyNullValueValidateMembers[i], Expression.Constant(null));
}
else
{
nullValueValidateMemberExpression = Expression.OrElse(nullValueValidateMemberExpression, Expression.Equal(historyNullValueValidateMembers[i], Expression.Constant(null)));
}
}
}
// 型別一致才進行對映
if (item.PropertyType == baseClassProp.PropertyType)
{
MemberExpression memberExp = Expression.Property(nowLevelMemberExpression, baseClassProp);
Expression conditionExpr = Expression.Condition(
nullValueValidateMemberExpression,
Expression.Default(memberExp.Type),
memberExp
);
MemberBinding memberBindingResult = Expression.Bind(item, conditionExpr);
memberBindings.Add(memberBindingResult);
}
else if (item.PropertyType == typeof(string))
{
MemberExpression memberExp = Expression.Property(nowLevelMemberExpression, baseClassProp);
// 獲取ToString方法,一般來說,都應該是String型別
MethodInfo methodInfo = memberExp.Type.GetMethod("ToString", new Type[] { });
var resultExpression = Expression.Call(memberExp, methodInfo);
Expression conditionExpr = Expression.Condition(
nullValueValidateMemberExpression,
Expression.Default(typeof(string)),
resultExpression
);
MemberBinding memberBindingResult = Expression.Bind(item, conditionExpr);
memberBindings.Add(memberBindingResult);
}
else if (baseClassProp.PropertyType == typeof(string))
{
MemberExpression memberExp = Expression.Property(nowLevelMemberExpression, baseClassProp);
// 獲取ToString方法,一般來說,都應該是String型別
MethodInfo methodInfo = item.PropertyType.GetMethod("Parse", new Type[] { typeof(string) });
var resultExpression = Expression.Call(methodInfo, new List<Expression>() { memberExp });
Expression conditionExpr = Expression.Condition(
nullValueValidateMemberExpression,
Expression.Default(typeof(string)),
resultExpression
);
MemberBinding memberBindingResult = Expression.Bind(item, conditionExpr);
memberBindings.Add(memberBindingResult);
}
}
foreach (var subClassProp in subClassProps)
{
var nextLevelMemberExpression = Expression.Property(nowLevelMemberExpression, subClassProp);
if (!historyNullValueValidateMembers.Any(x => x.Type == nextLevelMemberExpression.Type))
{
historyNullValueValidateMembers.Add(nextLevelMemberExpression);
}
// 如果二級仍是當前型別,不繼續做處理,只對映一層
if(subClassProp.PropertyType == sourceSubTypeInfo.PropertyType)
{
continue;
}
var res = GetSubPropertyMapConfig<Target>(memberBindings, rootName, nextLevelMemberExpression, subClassProp, historyNullValueValidateMembers);
}
return new Tuple<List<MemberBinding>, string, MemberExpression, List<MemberExpression>>(memberBindings, rootName, nowLevelMemberExpression, historyNullValueValidateMembers);
}
/// <summary>
/// 生成對映成實體模型的Lambda表示式
/// </summary>
/// <returns></returns>
private static Func<TEntityVM, TEntity> GetMapToEntityFunc()
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(TEntityVM), "p");
List<MemberBinding> memberBindingList = new List<MemberBinding>();
// 遍歷實體模型
foreach (var item in typeof(TEntity).GetProperties())
{
// 同名的情況下
// 如果是不能寫的屬性
if (!item.CanWrite)
continue;
// 如果輸出型別沒有該屬性
if (typeof(TEntityVM).GetProperty(item.Name) == null)
{
// 如果是自定義型別
if (item.PropertyType.FullName.Contains(configNamespace) && !item.PropertyType.IsEnum && _isList(item.PropertyType) == false)
{
List<MemberBinding> tempMemberBindingList = new List<MemberBinding>();
// 獲取實體模型二級
// p=>p.item.Name,拿到實體模型巢狀屬性Id和Name
var theIdProp = typeof(TEntityVM).GetProperty(item.Name + "Id");
if (theIdProp != null)
{
MemberExpression idProperty = Expression.Property(parameterExpression, theIdProp);
if (idProperty == null) continue;
// TEntity.Id
var id = item.PropertyType.GetProperty("Id");
if (id == null) continue;
// 檢視模型string轉其他型別
if (id.PropertyType != theIdProp.PropertyType && theIdProp.PropertyType == typeof(string))
{
MethodInfo toGuid = id.PropertyType.GetMethod("Parse", new Type[] { typeof(string) });
MethodCallExpression expression = Expression.Call(toGuid, new List<Expression>() { idProperty });
Expression idConditionExpr = Expression.Condition(
Expression.Equal(Expression.Property(parameterExpression, item.Name + "Id"), Expression.Constant(null)),
Expression.Default(id.PropertyType),
expression
);
MemberBinding idMemberBinding = Expression.Bind(id, idConditionExpr);
tempMemberBindingList.Add(idMemberBinding);
}
else
{
MemberBinding idMemberBinding = Expression.Bind(item.PropertyType.GetProperty("Id"), idProperty);
tempMemberBindingList.Add(idMemberBinding);
}
}
var theNameProp = typeof(TEntityVM).GetProperty(item.Name + "Name");
if (theNameProp != null)
{
MemberExpression NameProperty = Expression.Property(parameterExpression, theNameProp);
MemberBinding NameMemberBinding = Expression.Bind(item.PropertyType.GetProperty("Name"), NameProperty);
tempMemberBindingList.Add(NameMemberBinding);
}
MemberInitExpression includeBoInitExpression = Expression.MemberInit(Expression.New(item.PropertyType), tempMemberBindingList.ToArray());
if (typeof(TEntityVM).GetProperty(item.Name + "Id").PropertyType == typeof(string))
{
Expression includeBoInitExpressionC = Expression.Condition(
Expression.Equal(Expression.Property(parameterExpression, item.Name + "Id"), Expression.Constant(null)),
Expression.Default(item.PropertyType),
includeBoInitExpression
);
MemberBinding memberString = Expression.Bind(item, includeBoInitExpressionC);
memberBindingList.Add(memberString);
}
else if (typeof(TEntityVM).GetProperty(item.Name + "Id").PropertyType == typeof(Guid)
|| typeof(TEntityVM).GetProperty(item.Name + "Id").PropertyType == typeof(int))
{
Expression includeBoInitExpressionC = Expression.Condition(
Expression.Equal(Expression.Property(parameterExpression, item.Name + "Id"), Expression.Default(typeof(TEntityVM).GetProperty(item.Name + "Id").PropertyType)),
Expression.Default(item.PropertyType),
includeBoInitExpression
);
MemberBinding memberString = Expression.Bind(item, includeBoInitExpressionC);
memberBindingList.Add(memberString);
}
continue;
}
// 如果是字串就繫結為""
else if (item.PropertyType == typeof(string))
{
// 將這個屬性繫結到""
MemberBinding memberString = Expression.Bind(item, Expression.Constant(""));
memberBindingList.Add(memberString);
}
continue;
}
else if (item.PropertyType == typeof(TEntityVM).GetProperty(item.Name).PropertyType)
{
MemberExpression property = Expression.Property(parameterExpression, typeof(TEntityVM).GetProperty(item.Name));
// p=> p.item.Name = p.item.Name
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
// 基本型別 轉string
else if (item.PropertyType != typeof(TEntityVM).GetProperty(item.Name).PropertyType
&& typeof(TEntityVM).GetProperty(item.Name).PropertyType != typeof(string))
{
MemberExpression property = Expression.Property(parameterExpression, typeof(TEntityVM).GetProperty(item.Name));
MethodInfo toString = property.Type.GetMethod("ToString", new Type[] { });
MethodCallExpression resultExpression = Expression.Call(property, toString);
// p=> p.item.Name = p.item.Name
MemberBinding memberBinding = Expression.Bind(item, resultExpression);
memberBindingList.Add(memberBinding);
}
// string轉 基本型別
else if (item.PropertyType != typeof(TEntityVM).GetProperty(item.Name).PropertyType
&& typeof(TEntityVM).GetProperty(item.Name).PropertyType == typeof(string))
{
MemberExpression property = Expression.Property(parameterExpression, typeof(TEntityVM).GetProperty(item.Name));
MethodInfo toGuid = item.PropertyType.GetMethod("Parse", new Type[] { typeof(string) });
MethodCallExpression resultExpression = Expression.Call(toGuid, new List<Expression>() { property });
Expression guidValueExp = Expression.Condition(
Expression.Equal(property, Expression.Constant(null)),
Expression.Default(item.PropertyType),
resultExpression
);
// p=> p.item.Name = p.item.Name
MemberBinding memberBinding = Expression.Bind(item, guidValueExp);
memberBindingList.Add(memberBinding);
}
}
// p => new TOut(){item.Name = p.item.Name}
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TEntity)), memberBindingList.ToArray());
Expression<Func<TEntityVM, TEntity>> lambda = Expression.Lambda<Func<TEntityVM, TEntity>>(memberInitExpression, new ParameterExpression[] { parameterExpression });
return lambda.Compile();
}
public static TEntityVM MapToApiEntity(TEntity tIn, Action<TEntity, TEntityVM> mapperExtensionAction = null)
{
if (tIn == null)
{
return null;
}
if (mapperExtensionAction == null)
{
return _mapToEntityVMCache(tIn);
}
else
{
var res = _mapToEntityVMCache(tIn);
mapperExtensionAction(tIn, res);
return res;
}
}
public static TEntity MapToEntity(TEntityVM tout, Action<TEntityVM, TEntity> mapperExtensionAction = null)
{
if (tout == null)
{
return null;
}
if (mapperExtensionAction == null)
{
return _mapToEntityCache(tout);
}
else
{
var res = _mapToEntityCache(tout);
mapperExtensionAction(tout, res);
return res;
}
}
public static List<TEntityVM> MapToApiEntity(IEnumerable<TEntity> tIns, Action<TEntity, TEntityVM> mapperExtensionAction = null)
{
if (tIns == null)
{
return null;
}
if (mapperExtensionAction == null)
{
return tIns.ToList().Select(x => _mapToEntityVMCache(x)).ToList();
}
else
{
return tIns.ToList().Select(x =>
{
var temp = _mapToEntityVMCache(x);
mapperExtensionAction(x, temp);
return temp;
}).ToList();
}
}
public static List<TEntity> MapToEntity(IEnumerable<TEntityVM> touts, Action<TEntityVM, TEntity> mapperExtensionAction = null)
{
if (touts == null)
{
return null;
}
if (mapperExtensionAction == null)
{
return touts.ToList().Select(x => _mapToEntityCache(x)).ToList();
}
else
{
return touts.ToList().Select(x =>
{
var temp = _mapToEntityCache(x);
mapperExtensionAction(x, temp);
return temp;
}).ToList();
}
}
}
單句解釋我就不做了,本質上就是表示式樹的拼接,多看就理解了,實際上最核心的就是:
MemberBinding memberBinding = Expression.Bind(item, resultExpression);
以及一些情況下,需要先做不同型別的同名屬性的對映後再進行Binding
MemberExpression property = Expression.Property(parameterExpression, typeof(TEntityVM).GetProperty(item.Name));
MethodInfo toString = property.Type.GetMethod("ToString", new Type[] { });
MethodCallExpression resultExpression = Expression.Call(property, toString);
// p=> p.item.Name = p.item.Name
MemberBinding memberBinding = Expression.Bind(item, resultExpression);
memberBindingList.Add(memberBinding);
或者導航屬性拆解對映時,需要外層做一個判斷導航屬性是否為空的判斷表示式
Expression includeBoInitExpressionC = Expression.Condition(Expression.Equal(Expression.Property(parameterExpression, item.Name + "Id"),
Expression.Default(typeof(TEntityVM).GetProperty(item.Name + "Id").PropertyType)),
Expression.Default(item.PropertyType),
includeBoInitExpression
);
MemberBinding memberString = Expression.Bind(item, includeBoInitExpressionC);
memberBindingList.Add(memberString);
最終生成Lambda Func,並且快取在靜態變數中,這樣在一個服務的執行週期中,每兩種實體的對映只需要編譯一次Lambda表示式,往後的對映速度都會是最接近原生的,因為本質上已經編譯成Func來執行了。
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TEntity)), memberBindingList.ToArray());
Expression<Func<TEntityVM, TEntity>> lambda = Expression.Lambda<Func<TEntityVM, TEntity>>(memberInitExpression, new ParameterExpression[] { parameterExpression });
return lambda.Compile();
好久前測過一次速度,找不到資料了,現在懶得搞了,有讀者自己測出來的話可以發一下出來,