zoukankan      html  css  js  c++  java
  • 使用表达式目录树实现SqlDataReader到实体的映射

    SqlDataReader映射实体,是ORM的基础功能,常见的实现方式有反射、表达式目录树和emit,这里要说的就是用表达式目录树生成实体的方法。

    先分析下思路:

    假设有个数据实体类,Student

    public class Student
    {
        public int Id { get; set; }
    
        public string Name { get; set; }
    
        public int ClazzId { get; set; }
    }

    获取到SqlDataReader,手撸代码的话,我们可能是这样做的

    public Student ConvertToStudent(SqlDataReader sdr)
    {
        var entity = new Student
        {
            Id = sdr.GetInt32(0),
            Name = sdr.GetString(1),
            ClazzId = sdr.GetInt32(2)
        };
    
        return entity;
    }

    无疑,这种效率是最高的,原因是:一、直接通过索引获取Reader内的数据;二、直接为属性赋值,而无需通过PropertyInfo.SetValue,既避开了反射,又无需对数据类型进行转换,减少了装箱拆箱的操作。

    但是,我们需要的是一个通用方法,上面的写法只能针对具体类型,所以我们真正需要的是这样一个类似的泛型版本

    public T ConvertToEntity<T>(SqlDataReader reader) where T : new()
    {
        var t = new T();
    
        t.a = reader.GetInt32(0);
        t.b = reader.GetString(1);
    
        return t;
    }

    问题来了,我们并不知道实体的具体定义,所以上面的代码根本无法实现。所以现在需要解决两个问题:

    1、要获取到T的所有属性,这里假设实体是个POCO模型

    2、为T专门构建一个方法,实现类似ConvertToStudent()那样的功能

    第一个问题很简单,反射,要动态获取实体的属性,这一步是不可避免的,通用做法就是先做一次反射,然后把实体的PropertyInfo缓存起来,这样只要反射一次,这点性能损耗还是可以承受的

    第二个问题比较麻烦,动态生成方法,在我的知识体系内是没有办法的,但恰巧知道一个动态生成委托的方法,那就是表达式目录树,我们知道,委托delegate 就是对方法的封装,动态生成委托,正好可以解决眼下的问题

    现在将ConvertToStudent改造成委托,这段代码就是我们构造表达式目录树的模板

    private Func<SqlDataReader, Student> ConvertToStudentFunc = (reader) =>
    {
        var entity = new Student();
        entity.Id = reader.GetInt32(0);
        entity.Name = reader.GetString(1);
        entity.ClazzId = reader.GetInt32(2);
        return entity;
    };

    因为没有找到系统的表达式目录树的资料,所以对语法不甚了解,下面的代码是结合网上的资料一点一点摸索出来的,具体不解释了,怕说错了误人子弟,直接看代码吧:

    private static Func<SqlDataReader, T> Converter<T>(IDataReader reader)
    {
        var sdrParameter = Expression.Parameter(typeof(SqlDataReader), "sdr");
    
        var memberBindings = new List<MemberBinding>();
    
        var properties = typeof(T).GetProperties().Where(p => p.CanWrite);
    
        for (var i = 0; i < reader.FieldCount; i++)
        {
            var fieldName = reader.GetName(i);
            var property =
                properties.SingleOrDefault(p => p.Name == fieldName && p.PropertyType == reader.GetFieldType(i));
    
            if (property == null) continue;
    
            var methodName = "GetValue";
            if (property.PropertyType == typeof(string))
            {
                methodName = "GetString";
            }
            else if (property.PropertyType == typeof(int))
            {
                methodName = "GetInt32";
            }
            else if (property.PropertyType == typeof(DateTime))
            {
                methodName = "GetDateTime";
            }
            else if (property.PropertyType == typeof(decimal))
            {
                methodName = "GetDecimal";
            }
            else if (property.PropertyType == typeof(Guid))
            {
                methodName = "GetGuid";
            }
            else if (property.PropertyType == typeof(bool))
            {
                methodName = "GetBoolean";
            }
            else
            {
                continue;
            }
    
            var methodCall = Expression.Call(sdrParameter,
                typeof(SqlDataReader).GetMethod(methodName) ?? throw new InvalidOperationException(),
                Expression.Constant(i));
    
            memberBindings.Add(Expression.Bind(property, methodCall));
        }
    
        var initExpression = Expression.MemberInit(Expression.New(typeof(T)), memberBindings);
        return Expression.Lambda<Func<SqlDataReader, T>>(initExpression, sdrParameter).Compile();
    }

    代码并不严谨,很多东西没做,只是个框框,理解原理用的,下面是使用方法:

    public static List<T> Fetch<T>(int count)
    {
        var list = new List<T>();
        Func<SqlDataReader, T> func = null;
        var topStr = count <= 0 ? "" : $"TOP {count}";
        using (var conn = new SqlConnection(ConfigurationManager.AppSettings["DbConnection"]))
        {
            using (var command = new SqlCommand($"SELECT {topStr} * FROM {typeof(T).Name}", conn))
            {
                conn.Open();
                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        if (func == null)
                        {
                            func = ExpressionShow.ConvertEntity<T>(reader);
                            list.Add(func(reader));
                        }
                        else
                        {
                            list.Add(func(reader));
                        }
                    }
                    reader.Close();
                }
            }
        }
    
        return list;
    }

    到这里,基本功能就完成了。

  • 相关阅读:
    html5 canvas雨点打到窗玻璃动画
    html5跟随鼠标炫酷网站引导页动画特效
    如何实现复选框的全选和取消全选效果
    CSS3透明属性opacity
    jQuery实现方式不一样的跳转到底部
    ul li设置横排,并除去li前的圆点
    jQuery美女幻灯相册轮播源代码
    微软modern.IE网站,多版本IE免费测试工具集
    css中position与z-index
    C#一个方法返回多个值
  • 原文地址:https://www.cnblogs.com/diwu0510/p/10199996.html
Copyright © 2011-2022 走看看