.net反射存在的问题及优化技术--反射之动态为ORM的实体的属性赋值的改进

.net反射存在的问题及优化技术--反射之动态为ORM的实体的属性赋值的改进

2945发表于2015-03-16

在所有的ORM框架都会涉及到使用反射来读取与数据库表对应实体类的属性,并根据从数据库读取的结果和依次为每个实体对象属性赋上相应列的值。这个操作同样也是非常耗性能的,这里也可以选择场景三中的有动态方法的技术来改善性能,但是我选择.net 3.5叫做表达式树(Expression Trees)的另一种技术来处理。

实体类Employees:

public class Employees
{
    public int EmployeeID { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public DateTime BirthDate { get; set; }
}

读取并填充数据到对象集合empList:

List<Employees> empList = new List<Employees>();

using(SqlConnection con = new SqlConnection(@"Data Source=localhost\SQLEXPRESS;
   Initial Catalog=Northwind;Integrated Security=True"))
{
    SqlCommand cmd = new SqlCommand("Select * from employees");
    cmd.Connection = con;
    con.Open();

    using (SqlDataReader reader = cmd.ExecuteReader())
    {
        empList = ReadList<Employees>(reader);
    }
}

利用反射来为实体类的属性依次赋值:

public List<T> ReadList<T>(SqlDataReader reader) where T : new()
{
    var list = new List<T>();

    while (reader.Read())
    {
        T entity = new T();
        Type entityType = typeof(T);
        foreach(var entityProperty in entityType.GetProperties())
        {
            entityProperty.SetValue(entity, reader[entityProperty.Name], null);
        }

        list.Add(entity);
    }
    return list;
}

上面方案的缺点&劣势

1、利用反射来为对象的属性赋值是一个非常重量级,开销很大的操作,尤其是返回数据量比较大的时候。
2、程序员必须要注意赋值是的类型安全,不然就会报错。

优化方案

使用.NET的表达式树的优化方法:

public Func<SqlDataReader, T> GetReader<T>()
{
    Delegate resDelegate;
    if (!ExpressionCache.TryGetValue(typeof(T), out resDelegate))
    {
        // Get the indexer property of SqlDataReader 
        var indexerProperty = typeof(SqlDataReader).GetProperty("Item",
        new[] { typeof(string) });
        // List of statements in our dynamic method 
        var statements = new List<Expression>();
        // Instance type of target entity class 
        ParameterExpression instanceParam = Expression.Variable(typeof(T));
        // Parameter for the SqlDataReader object
        ParameterExpression readerParam =
            Expression.Parameter(typeof(SqlDataReader));

        // Create and assign new T to variable. Ex. var instance = new T(); 
        BinaryExpression createInstance = Expression.Assign(instanceParam,
            Expression.New(typeof(T)));
        statements.Add(createInstance);

        foreach (var property in typeof(T).GetProperties())
        {
            // instance.Property 
            MemberExpression getProperty =
            Expression.Property(instanceParam, property);
            // row[property] The assumption is, column names are the 
            // same as PropertyInfo names of T 
            IndexExpression readValue =
                Expression.MakeIndex(readerParam, indexerProperty,
                new[] { Expression.Constant(property.Name) });

            // 为属性赋值
            BinaryExpression assignProperty = Expression.Assign(getProperty,
                Expression.Convert(readValue, property.PropertyType));

            statements.Add(assignProperty);
        }
        var returnStatement = instanceParam;
        statements.Add(returnStatement);

        var body = Expression.Block(instanceParam.Type,
            new[] { instanceParam }, statements.ToArray());

        var lambda =
        Expression.Lambda<Func<SqlDataReader, T>>(body, readerParam);
        resDelegate = lambda.Compile();

        // 将动态方法保存到缓存中
        ExpressionCache[typeof(T)] = resDelegate;
    }
    return (Func<SqlDataReader, T>)resDelegate;
}   

上面了ExpressionCache同样是一个线程安全的字典集合,用来存一个类及对应分别为这个类的字段赋值的委托。

最后调用的使用方法:

public List<T> ReadList<T>(SqlDataReader reader)
{
    var list = new List<T>();  
    Func<SqlDataReader, T> readRow = GetReader<T>(); 

    while (reader.Read())
    {
        list.Add(readRow(reader));
    }
    return list;
}  

小编蓝狐