[Linq Expression]练习自己写绑定

源代码TypeMapper.zip

背景

  项目中,我们会经常用到各种赋值语句,比如把模型的属性赋值给UI,把视图模型的属性拷贝给Entity。如果模型属性太多,赋值也会变成苦力活。所以,框架编程的思维中,出现了”绑定“。绑定不仅可以简化赋值,还可以结合验证,简化绑定过程中的验证。

  能实现绑定的框架很多,如AutoMapper,.Net自带的绑定机制,微软官方上还有一个利用绑定的Sample,等。

  那些成熟的框架一般功能全面,考虑周全,一般推荐首选。但对于一些小项目个别情况,或许它们就会显得有些笨重了。前些时候读Demo研究了一下Linq Expression , 自己写一个,练练手巩固下知识吧。

  代码主要使用了基本的LinqExpression中的赋值操作,代码也较短,可以作为学习示例也可以作为工具使用(可能要加一些功能了)。

介绍

  先看下效果:

  并没什么奇特的。

  再看看后台,没有赋值语句,只是简单告诉程序:谁该绑定给谁,怎么绑而已:

 public Form1()
        {
            InitializeComponent();

            var typeMapper = new cp.lib.TypeMapper<ViewModel, Form1>()
                .AddMapItem(vm => vm.Name, form => form.txtName.Text)
                .AddMapItem(vm => vm.Amount, form => form.txtAmount.Text, a => a.ToString("#,###"))
                .AddMapItem(vm => vm.IsValid, form => form.chkIsValid.Checked)
                .AddMapItem(vm => vm.Type, form => form.cboType.SelectedIndex)
                .AddMapItem(vm => vm.CreatedTime, form => form.dtpCreateTime.Value);
            var model = new ViewModel()
            {
                Name = "How Are You",
                Amount = 123456789,
                IsValid = true,
                Type = 3,
                CreatedTime = new DateTime(2000, 10, 10)
            };
            typeMapper.Map(model,this);
        }

  那么,你可能会问了,你写这么长一段“诡异”的代码,有什么好处呢?

  1 代码量变少。如果用单纯的赋值语句,和目前代码行数一样。一般而言,我们会实现双向绑定,即界面操作完成后,会把更新的值写回模型,然后重新存储。这意味着那时,你不得不把这些赋值语句反向重一遍。这样写,可以很方便的实现反向赋值。假想20个模型,每个20个属性,可以节省400行的代码量。

  2 模块化。模块化后,这块功能有了边界,用以和其它模块交互。一般绑定会和验证,默认值,操作撤销,未保存提醒等结合。单就验证来说,如果加入了验证规则,我们可以在绑定的时候,依次查阅这些规则,自动校验。而不用在页面后重复的“if(xxx.Text==String.Empty{...}”。

  3 便于阅读。现在读代码的感觉就是”把xxx的XXx属性绑定到xxx的Text属性上",这比直接读赋值语句要直观一些。虽然赋值语句比较简单,但也要经过人脑的翻译才能形成更直观的自然语言。所以程序风格中,一般提倡方法拆小(方法名可以充当注释)。

  4 便于维护。便于维护往往意味着业务改变,代码改变很小甚至不用变。由于现在有专门的类负责在模型和界面间赋值,如果将这些绑定规则外置(放在配置文件中),如果发生改变,那么以后更新配置文件就行了。就算不规则外置,我们也可以把已经模块化的这部分代码放在单独的其它地方(内聚),如果改变绑定,测试也只用测这小部分。

  当然,说了那么多,其实我写它的目的比较纯粹:练手,再就是希望能给新鲜码友以帮助。

实现

  注释比较详细,就不多说了。如有不懂,可查阅MSDN或留言。如果硬要说明的话,那就是:以缓存委托的形式进行绑定,性能会比反射好一点点~。

  如果要进一步查看怎么使用,可以看下单元测试。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace cp.lib
{
    public class TypeMapper<T1,T2>
        where T1:class
        where T2:class,new()
    {
        #region 双向绑定,未实现。
        /// <summary>
        /// (预留属性)是否支持双向绑定。
        /// </summary>
        public bool IsTwoWay { get; set; }

        public void MapBack(T2 targetObj, T1 sourceObj)
        {
            //--todo
        }

        public T1 MapBack(T2 targetObj )
        {
            //--todo
            return default(T1);
        }
        #endregion

        //缓存的已经编译好的(从源对象属性到目标对象属性的)赋值语句。
        private List<Action<T1,T2>> _assginExpressions=new List<Action<T1,T2>>();

        public TypeMapper()
        {

        }

        /// <summary>
        /// 使用字典类型创建一个新TypeMapper实例
        /// </summary>
        /// <param name="mapDictionary"></param>
        public TypeMapper(Dictionary<string,string> mapDictionary)
        {
            foreach (var item in mapDictionary)
            {
                AddMapItem(item.Key, item.Value);
            }
        }

        /// <summary>
        /// 按设置好的映射项将源对象的属性或字段拷贝到目标对象。
        /// </summary>
        /// <param name="sourceObj">源对象</param>
        /// <param name="targetObj">目标对象</param>
        public void Map(T1 sourceObj,T2 targetObj)
        {
            foreach (var action in _assginExpressions)
            {
                action(sourceObj, targetObj);
            }
        }

        /// <summary>
        /// 按设置好的映射项从源对象的属性或字段创建新的目标对象
        /// </summary>
        /// <param name="sourceObj">源对象</param>
        /// <returns>新的目标对象(T2类型)</returns>
        public T2 Map(T1 sourceObj)
        {
            if (sourceObj == null)
            {
                return default(T2);
            }

            var targetObj = new T2();
            Map(sourceObj, targetObj);
            return targetObj;
        }

        /// <summary>
        /// 按设置好的映射项将源对象的属性或字段拷贝到目标对象。
        /// </summary>
        /// <param name="sourceObjs">源对象</param>
        /// <param name="targetObjs">目标对象</param>
        public void Map(IEnumerable<T1> sourceObjs, IEnumerable<T2> targetObjs)
        {
            if (sourceObjs.Count() != targetObjs.Count())
            {
                throw new ArgumentException("sourceObjs和targetObjs数量不一致!");
            }

            var sourceEmumerator = sourceObjs.GetEnumerator();
            var targetEnumerator = targetObjs.GetEnumerator();
            while (sourceEmumerator.MoveNext())
            {
                targetEnumerator.MoveNext();
                var sourceObj = sourceEmumerator.Current;
                var targetObj = targetEnumerator.Current;
                Map(sourceObj, targetObj);
            }
        }

        /// <summary>
        /// 按设置好的映射项从源对象的属性或字段创建新的目标对象
        /// </summary>
        /// <param name="sourceObj">源对象</param>
        /// <returns>新的目标对象(IEnumerable T2类型)</returns>
        public IEnumerable<T2> Map(IEnumerable<T1> sourceObjs)
        {
            foreach (var sourceObj in sourceObjs)
            {
                yield return Map(sourceObj);
            }
        }

        /// <summary>
        /// 增加映射项。映射项以Expression表式,如x=>x.Name。
        /// </summary>
        /// <exception>targetExp不为MemberAccess将引发参数异常。</exception>
        /// <returns>返回当前对象</returns>
        public TypeMapper<T1, T2> AddMapItem<TResult>(Expression<Func<T1,TResult>> sourceExp,Expression<Func<T2,TResult>> targetExp)
        {
            if (targetExp.Body.NodeType != ExpressionType.MemberAccess)
            {
                throw new ArgumentException("targetExp应该为MemberAccess类型!");
            }

            var assignExp = Expression.Assign(targetExp.Body, sourceExp.Body);
            var lambda = Expression.Lambda<Action<T1, T2>>(assignExp, sourceExp.Parameters[0], targetExp.Parameters[0]);
            _assginExpressions.Add(lambda.Compile());

            return this;
        }

        /// <summary>
        /// 增加映射项。映射项以Expression表式,如x=>x.Name。
        /// </summary>
        /// <exception>targetExp不为MemberAccess将引发参数异常。</exception>
        /// <returns>返回当前对象</returns>
        public TypeMapper<T1, T2> AddMapItem<TResult1, TResult2>(Expression<Func<T1, TResult1>> sourceExp, Expression<Func<T2, TResult2>> targetExp, Expression<Func<TResult1, TResult2>> formatFunc)
        {
            if (targetExp.Body.NodeType != ExpressionType.MemberAccess)
            {
                throw new ArgumentException("targetExp应该为MemberAccess类型!");
            }

            var formatExp = Expression.Invoke(formatFunc, sourceExp.Body);
            var assignExp = Expression.Assign(targetExp.Body, formatExp);
            var lambda = Expression.Lambda<Action<T1, T2>>(assignExp, sourceExp.Parameters[0], targetExp.Parameters[0]);
            _assginExpressions.Add(lambda.Compile());

            return this;
        }

        /// <summary>
        /// 使用属性(或字段)名增加映射项。
        /// </summary>
        /// <param name="sourceProperty">源对象属性(或字段)名称</param>
        /// <param name="targetProperty">目标对象属性(或字段)名称</param>
        /// <returns>返回当前对象</returns>
        public TypeMapper<T1, T2> AddMapItem(string sourceProperty,string targetProperty)
        {
            var parameter1 = Expression.Parameter(typeof(T1),"o1");
            var memeber1 = Expression.PropertyOrField(parameter1, sourceProperty);
            var parameter2 = Expression.Parameter(typeof(T2), "o2");
            var memeber2 = Expression.PropertyOrField(parameter2, targetProperty);

            var assignExp = Expression.Assign(memeber2, memeber1);
            var lambda = Expression.Lambda<Action<T1, T2>>(assignExp, parameter1, parameter2);
            _assginExpressions.Add(lambda.Compile());
            return this;
        }

        /// <summary>
        /// 按照类型T1,T2中相同名称和类型的属性或字段,自动添加所有映射项。
        /// </summary>
        /// <returns>返回当前项</returns>
        public TypeMapper<T1, T2> AutoBuildMap()
        {
            var p1s = typeof(T1).GetProperties();
            var p2s = typeof(T2).GetProperties();

            foreach (var p1 in p1s)
            {
                foreach (var p2 in p2s)
                {
                    //目前暂不处理Nullable<int> -> int的映射
                    if (p1.Name == p2.Name && p1.PropertyType == p2.PropertyType)
                    {
                        AddMapItem(p1.Name, p2.Name);
                    }
                }
            }

            var f1s = typeof(T1).GetFields();
            var f2s = typeof(T2).GetFields();

            foreach (var f1 in f1s)
            {
                foreach (var f2 in f2s)
                {
                    if (f1.Name == f2.Name && f1.FieldType == f2.FieldType)
                    {
                        AddMapItem(f1.Name, f2.Name);
                    }
                }
            }
            return this;
        }
    }
}

后话

  学而时习之。。。

时间: 2024-08-27 06:20:23

[Linq Expression]练习自己写绑定的相关文章

如何写绑定端口shellcode

前面<如何编写本地shellcode>一文介绍如何编写shellcode取得shell进行交互.本文介绍另一个例子,绑定端口的shellcode.攻击通过网络利用缓冲区溢出漏洞,注入该shellcode,那就可以能过shellcode打开的端口进行利用. Shellcode逻辑C代码 绑定端口shellcode的逻辑很简单:打开socket,然后绑定到端口,等待远程进行链接,链接到后将0/1/2描述符都复制该socket上,再启动一个shell. 代码如下: #include <unis

java 的 linq,不要再写令人讨厌的 for 了!

package com.ly.linq; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; public class Enm { public static <T> ArrayList<T> notNull(Iterable<T> source) { return where(source, new Pre<T>() { @Override pub

The specified LINQ expression contains references to queries that are associated with different contexts

今天在改写架构的时候,遇到这么个错误.当时单从字面意思,看上去错误是由join的两个不同的表来源不一致引起的. 其中的videoResult和deerpenList均来自与同一个edmx文件,所以两个表的来源不一致导致了错误的发生,这个猜想是不正确的. 正在左右为难之际,在stackoverflow上面发现了一个主题,正好解决了我的难题.这个问题的回答是这样的: This can happen if your Context property returns a new instance eve

.NET深入解析LINQ框架(一:LINQ优雅的前奏)

阅读目录: 1.LINQ简述 2.LINQ优雅前奏的音符 2.1.隐式类型 (由编辑器自动根据表达式推断出对象的最终类型) 2.2.对象初始化器 (简化了对象的创建及初始化的过程) 2.3.Lambda表达式 (对匿名方法的改进,加入了委托签名的类型推断并很好的与表达式树的结合) 2.4.扩展方法 (允许在不修改类型的内部代码的情况下为类型添加独立的行为) 2.5.匿名类型 (由对象初始化器推断得出的类型,该类型在编译后自动创建) 2.6.表达式目录树(用数据结构表示程序逻辑代码) 3.LINQ

Linq技术四:动态Linq技术 -- Linq.Expressions

前面介绍了Linq的三个方面应用:Linq to SQL, Linq to XML和Linq to Object,这篇介绍一下动态Linq的实现方式及应用场景. 命名空间: System.Linq; System.Linq.Expressions; 应用Linq的时候,我们都知道只需要Lambda表达式就行,但有些场景仅仅只使用Data Model的字段名操作是不够的或者不方便的. 场景1:假设我们需要拼接Where条件进行查询,一种方式可以拼接IQueryable的表达式.但我想像写SQL语句

Linq之Linq to XML

目录 写在前面 系列文章 linq to xml 总结 写在前面 在很多情况下,都可以见到使用xml的影子.例如,在 Web 上,在配置文件.Microsoft Office Word 文件(将word文档另存为xml文件,这也提供了一种通过操作xml,操作word的一种方式)以及数据库中,都可以看到 XML.而linq to xml提供了一种操作xml更便捷的方式. 系列文章 Linq之Lambda表达式初步认识 Linq之Lambda进阶 Linq之隐式类型.自动属性.初始化器.匿名类 Li

Linq之Linq to Objects

目录 写在前面 系列文章 linq to objects 总结 写在前面 上篇文章介绍了linq的延迟加载特性的相关内容,从这篇文章开始将陆续介绍linq to Objects,linq to xml,linq to sql等内容. 系列文章 Linq之Lambda表达式初步认识 Linq之Lambda进阶 Linq之隐式类型.自动属性.初始化器.匿名类 Linq之扩展方法 Linq之Expression初见 Linq之Expression进阶 Linq之Expression高级篇(常用表达式类

高仿Entity Framework?Linq to SQL也有春天!

开发这个框架,主要起源于有很多使用ALinq或者Linq to SQL的用户,经常向我抱怨,它们使用起来的确很爽,但是动态查询太让人纠结了.这个框架主要就是想解决客户碰到的动态查询的问题.至今为至,已经断断续续地写了一年多了,写这个框架,充满了挑战与乐趣.当然,现在更觉得,能把文档写好,更是挑战.大多数的程序员,一直对于写文档这码事,不大重视,我也是其中的一员.但是,长期地与客户打交道,让我认识到,文档是非常地重要的,而且,能把文档写好也不是件容易的事情.这次的发布,更多的精力放在了文档的编写上

.NET深入解析LINQ框架1

1.LINQ简述 2.LINQ优雅前奏的音符 2.1.隐式类型 (由编辑器自动根据表达式推断出对象的最终类型) 2.2.对象初始化器 (简化了对象的创建及初始化的过程) 2.3.Lambda表达式 (对匿名方法的改进,加入了委托签名的类型推断并很好的与表达式树的结合) 2.4.扩展方法 (允许在不修改类型的内部代码的情况下为类型添加独立的行为) 2.5.匿名类型 (由对象初始化器推断得出的类型,该类型在编译后自动创建) 2.6.表达式目录树(用数据结构表示程序逻辑代码) 3.LINQ框架的主要设