Linq to Entity经验:表达式转换
最近一年的项目,我主要负责一些小型项目(就是指企业内部的小项目),在数据库操作方面我们采用了以开发速度快为特点的Linq to Entity,这里我不多讲它的使用方法,先来分享下我认为还不错的几个地方:
1:某种情况下可以完全代替传统SQL开发
这里是指比较简单的数据库处理,比如查询,几个表关联查询及添加数据,更新数据,删除数据等,不包含特别复杂的事务处理或者业务逻辑特别复杂的小型报表之类的处理,为此不需要员工一定会SQL语句的开发。
2:性能上也是能接受的
现在的Entity Framework5,微软已经做出大量的优化工作,专门针对如何生成查询更优的SQL语句做了大量努力,且一般小型项目也不会存在特别大的性能问题。大家发现问题了可以看下生成的SQL做特殊优化即可。
3:简化了开发过程
一般情况下,我们首先需要将数据库表数据加载成DataReader或者是DataTable,然后再转换成我们的业务数据Model,有了EntityFrame work,它已经自动完成了此部分的转换。
问题:
如何处理数据库Model以及业务数据Model表达式之间的转换?
什么是数据库Model?
当我们采用数据库优先方式创建了一个edmx文件后,它会生成和数据库表名一样的数据库Model,比如我这里的DataAccess.ActionInfo
什么是业务数据Model?
真正的业务系统中,是不能直接使用系统自动生成的Model的,原因如下:
1:这些代码是生动生成的,随时会发生改变,稳定性太差。
2:数据库Model完全和数据库表对应,而业务数据Model有可能和数据库模型不完全一致,或者完全不一样。业务数据Model是处理业务逻辑的一个数据载体,由它解析成不同的数据库Model,最近进行数据库操作。比如我这里的ObjectModel.ActionInfo
前几篇文章中(我所理解的IRepository(续) ,我所理解的IRepository ),我提到我们项目中定义了如下仓储接口用于数据库查询。
它的条件接受一个表达式,由于UI屋以及业务逻辑层均只能调用业务数据Model,当进行数据库查询时,EntityFramwork只能接受参数类型为数据库Model的对象,所以这里我们需要将类型为业务数据Model的表达式能够自动的转换成数据库Model类型的表达式。
比如我们在UI层可以这样查询数据,这里p的类型是业务数据Model
下面是我们的业务数据Model,包含一些MVC的数据验证特性标签,同时字段数量上也和表字段不相同。
{
#region Primitive Properties
[Required(ErrorMessageResourceName="ValidationMessageNameRequired", ErrorMessageResourceType=typeof(ActionInfoResources))]
public virtual string Name
{
get;
set;
}
[Required(ErrorMessageResourceName="ValidationMessageIsActiveRequired", ErrorMessageResourceType=typeof(ActionInfoResources))]
public virtual bool IsActive
{
get;
set;
}
public virtual int Id
{
get;
set;
}
[Required(ErrorMessageResourceName="ValidationMessageFunctionInfoNameRequired", ErrorMessageResourceType=typeof(ActionInfoResources))]
public virtual int FunctionInfoId
{
get;
set;
}
[Required(ErrorMessageResourceName="ValidationMessageActionNameRequired", ErrorMessageResourceType=typeof(ActionInfoResources))]
public virtual string ActionName
{
get;
set;
}
#endregion
#region Navigation Properties
public virtual FunctionInfo FunctionInfo
{
get;
set;
}
#endregion
}
下面是生成的数据库Model,代码较多,这里就贴一个属性内容,主要都是些和数据相关的内容,比如外键关系等:
[DataMemberAttribute()]
public global::System.String Name
{
get
{
return _Name;
}
set
{
OnNameChanging(value);
ReportPropertyChanging("Name");
_Name = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("Name");
OnNameChanged();
}
}
我们需要一个工具类能够完成这两种不同类型的表达式之间的类型转换,需要满足如下需求:
1:最简单的表达式条件
对属性做直接的条件比较,比如:等于,不等于,大于,小于,或 这里二元操作符。
比如:p=>p.Name==1, p=>p.Name==1||p.Name==2
2:需要支持部分函数
如果我们想做某个字段的模糊查询,在SQL中是用Like语句,在.net中我们可以调用StartWith,EndWith,Contains方法来完成
比如:p=>p.Name.StartWith("1")
解决思路:
表达式树一旦创建了它是不能修改其内容的,我们只能重新创建一个表达式树,所以我们的工作就是解析源表达式树,然后构造自己的表达式树,在构建过程后也就完成类型的转换。
首先需要创建一个工具类用于转换:
TToB:指最终转换成的类型,我们这里就直接理解为数据库Model
TR:就是表达式的返回值
TFrom:指源对象类型
核心就是调用ConvertNode方法进入表达式的解析。
{
public static Expression<Func<TToB, TR>> Convert<TFrom, TR>(Expression<Func<TFrom, TR>> expr)
{
Dictionary<Expression, Expression> substitutues = new Dictionary<Expression, Expression>();
var oldParam = expr.Parameters[0];
var newParam = Expression.Parameter(typeof(TToB), oldParam.Name);
substitutues.Add(oldParam, newParam);
Expression body = ConvertNode(expr.Body, substitutues);
return Expression.Lambda<Func<TToB, TR>>(body, newParam);
}
}
需求1的解决方案:
对于普通的条件,无非就是一些二元操作,所以我们只要了解下ExpressionType的内容,然后根据不同的类型做不同的解析即可,比如处理二元表达式:下面文章中列表出各种ExpressionType的详细解释: http://technet.microsoft.com/zh-cn/library/bb361179(en-us,VS.90).aspx
{
if (node == null) return null;
if (subst.ContainsKey(node)) return subst[node];
switch (node.NodeType)
{
case ExpressionType.Equal:
case ExpressionType.And:
case ExpressionType.AndAlso:
case ExpressionType.LessThan:
case ExpressionType.NotEqual:
case ExpressionType.GreaterThan:
//case ExpressionType.:
case ExpressionType.Or:
{
var be = (BinaryExpression)node;
return Expression.MakeBinary(be.NodeType, ConvertNode(be.Left, subst), ConvertNode(be.Right, subst), be.IsLiftedToNull, be.Method);
}
default:
throw new NotSupportedException(node.NodeType.ToString());
}
}
需求2解决方案:
对于这部分有函数调用的地方,我们需要做特殊处理,目前只处理常用的Contains,StartWith,EndWith,其它的函数也可以继续做解析。
{
var be = (MethodCallExpression)node;
var expression = GetMethodExpression((MemberExpression)be.Object, ((ConstantExpression)be.Arguments[0]).Value.ToString(), be.Method.Name, subst);
return expression.Body;
}
static Expression<Func<TToB, bool>> GetMethodExpression(
MemberExpression propertyExp,
string propertyValue,
string MethodName,
IDictionary<Expression, Expression> subst)
{
//var parameterExp = Expression.Parameter(typeof(T), ((ParameterExpression)propertyExp.Expression).Name);
var newParameterExp = (ParameterExpression)ConvertNode((ParameterExpression)propertyExp.Expression, subst);
var newPropertyExp = ConvertNode(propertyExp, subst);
MethodInfo method = typeof(string).GetMethod(MethodName, new[] { typeof(string) });
var someValue = Expression.Constant(propertyValue, typeof(string));
var containsMethodExp = Expression.Call(newPropertyExp, method, someValue);
return Expression.Lambda<Func<TToB, bool>>(containsMethodExp, newParameterExp);
}
补充:
除了上面的二元表达式,方法调用表达式外,还有几个表达式也是重点:
1:ParameterExpression,即参数表达式,比如表达式p=>p.IsActive 这里的{p}就是一个参数表达式
2:ConstantExpression,即常量表达式,比如表达式p=>p.Name==1 这里的1就是常量表达式
3:MemberExpression,即成员表达式,比如p.Name,这一步的操作我们能够实现业务数据Model与数据库Model之间的类型转换,它的原理就是比较成员是否一样,这里包括属性名称,属性类型等,有点类似反射,一个一个比,遇到相同的就赋值。
总结:
表达式树虽然看起来不太容易使用,但只要明白它的一些基本用法就能解决你的大部分问题,重点就是需要了解如何解析表达式树。