背景
在RESTful Api模式开发Api,Patch更新时,频繁的遇到不同类型但属性相同的Dto到Model字段赋值的情况,
网上通用的类库是AutoMapper,但遇到了问题,查了Git也提出了问题,并未能解决。
问题
Post、Get等覆盖式传值时都能满足需求,唯独当Dto字段的值类型设为Nullable(如int?、bool?)时会被覆盖为默认值。
相关技术
1、Lambda表达式
2、反射
解决方案
利用反射查找类的字段名称与字段类型,并将具备相同属性名的源Class属性值赋值到目标Class,
如遇到源Class属为Null(引用空 或 Nullable泛型类型Null)跳过该属赋值。
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace Zhiqing.Helpers.Lambdas
{
/// <summary>
/// 快速复制,并忽略NULL值
/// </summary>
public static class FastCopy
{
static Action<S, T> CreateCopier<S, T>()
{
// 源
var source = Expression.Parameter(typeof(S));
// 目标
var target = Expression.Parameter(typeof(T));
// 源 所有属性
var props1 = typeof(S).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.CanRead).ToList();
// 目标 所有属性
var props2 = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.CanWrite).ToList();
// 共有属性
var props = props1.Where(x => props2.Any(y => y.Name == x.Name));
IEnumerable<Expression> assets = new List<Expression>();
foreach (var item in props)
{
// 目标
var tItem = Expression.Property(target, item.Name);
var tType = tItem.Type;
var tIsNullable = tType.IsGenericType && tType.GetGenericTypeDefinition() == typeof(Nullable<>);
// 源
var sItem = Expression.Property(source, item.Name);
var sType = sItem.Type;
var sIsNullable = sType.IsGenericType && sType.GetGenericTypeDefinition() == typeof(Nullable<>);
Debug.WriteLine(sIsNullable);
// ===================================
// 注释:Nullable实际是个泛型,赋值是需要转为实际类型才可赋值,否咋泛型给实际类型赋值引发异常
// 案例:int? s = 1;int t = s; 会引发异常
// 解决:int? s = 1;int t = Convert.ToInt32(s); 转换后解决
// 另外:Lamnda表达式应使用 Expression.Convert(); 转换
// 源是可为空类型
if (sIsNullable)
{
// 目标可为空
if (tIsNullable)
{
// 赋值表达式
var asset = Expression.Assign(tItem, sItem);
// 当源不为空的时候赋值
var notNull = Expression.IfThen(Expression.NotEqual(sItem, Expression.Constant(null)), asset);
// 加入表达式树
assets = assets.Append(notNull);
}
// 目标不可为空
else
{
// 转换源为实际类型
var sItemConverted = Expression.Convert(sItem, sType.GetGenericArguments().First());
// 赋值表达式
var asset = Expression.Assign(tItem, sItemConverted);
// 当源不为空的时候赋值
var notNull = Expression.IfThen(Expression.NotEqual(sItem, Expression.Constant(null)), asset);
// 加入表达式树
assets = assets.Append(notNull);
}
}
// 源不是可为空类型
else
{
// 源是否值类型
var sIsValueType = sType.IsValueType;
if (sIsValueType)
{
// 赋值表达式
var asset = Expression.Assign(tItem, sItem);
// 加入表达式树
assets = assets.Append(asset);
}
// 不是值类型
else
{
// 赋值表达式
var asset = Expression.Assign(tItem, sItem);
// 当源不为空的时候赋值
var notNull = Expression.IfThen(Expression.NotEqual(sItem, Expression.Constant(null)), asset);
// 加入表达式树
assets = assets.Append(notNull);
}
}
}
// 赋值
var tempBlock = Expression.Block(assets);
return Expression.Lambda<Action<S, T>>(tempBlock, source, target).Compile();
}
static ConcurrentDictionary<string, object> actions = new ConcurrentDictionary<string, object>();
/// <summary>
/// 快速的拷贝同名公共属性。忽略差异的字段。
/// </summary>
/// <typeparam name="S"></typeparam>
/// <typeparam name="T"></typeparam>
/// <param name="from"></param>
/// <param name="to"></param>
public static void Copy<S, T>(S from, T to)
{
string name = string.Format("{0}_{1}", typeof(S), typeof(T));
if (!actions.TryGetValue(name, out object obj))
{
var ff = CreateCopier<S, T>();
actions.TryAdd(name, ff);
obj = ff;
}
var act = (Action<S, T>)obj;
act(from, to);
}
}
}
使用案例
FastCopy.Copy<Card_UpdateDto, Commons.Entities.Card>(updateDto, modelCard);