.NetCore 利用反射處理RESTful的更新自動賦值

背景

在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);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章