相信EF大家都不陌生了,因为数据库表跟程序实体是一一对应的原因,我们能够通过lambda这种函数式的编程方式进行操作数据库,感觉非常清晰明了。与我们直接写SQL相比,lambda是强类型,拥有更好的扩展性,伸缩性,而且编程更加的方便,快捷。下面我们就基于Expression和lambda来与大家构建一个属于自己的ORM框架。
思路的话很简单,就是将lambda转换成我们对应的数据库所需的查询条件,然后执行查询,再将结果以反射的方式封装成List返回出去。
Expression
大家使用EF的时候多多少少会留意到有Expression这个东西。特别是查询时会看到要你传入Expression>这样类型的参数,它又和Func有什么比同呢?
Expression>是表达式树,我们可以通过它来分析我们的委托中的函数。当调用Compile方法后就会变成委托,才能执行。
Func只是一个普通的委托。
例如我们现在有个实体类Staff
1
2
3
4
5
6
7
8
|
public class Staff
{
public string Name { get; set; }
public int Age { get; set; }
public string Code { get; set; }
public DateTime? Birthday { get; set; }
public bool Deletion { get; set; }
}
|
我们还有一个这样的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class Program
{
static void Main(string[] args)
{
FindAs(x => x.Code == "张三" & x.Name.Contains("张"));
}
public static List FindAs(Expressionbool>> func)
{
//将func转换成对应数据库的查询条件,然后执行查询
return null;//将结果返回
}
}
|
我们希望通过 FindAs(x => x.Age
从上面的图我们可以看到当前的Expression是一个lambda表达式,我们点开它的body看看。
我们可以看到body里分为左边和右边,还有NodeType。和我们的lambda对比下看看’x => x.Code ==”张三” && x.Name.Contains(“张”)’是不是找到点灵感了?我们再继续把左边和右边拆开看看。
可以看到我们需要的信息都有了,看来转换成SQL已经不是什么难事了,动手开搞了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
class Program
{
static void Main(string[] args)
{
FindAs(x => x.Code == "张三" & x.Name.Contains("张"));
FindAs(x => x.Age 12 & x.Name.Contains("张"));
Console.ReadKey();
}
public static List FindAs(Expressionbool>> func)
{
BinaryExpression Binary = func.Body as BinaryExpression;
string left = ResovleFunc(Binary.Left);
string right = ResovleLinqToObject(Binary.Right);
string oper = GetOperator(Binary.NodeType);
string sql = string.Format("select * from {0} where {1}", typeof(T).Name, left + oper + right);
Console.WriteLine(sql);
return null;//将结果返回
}
//解析一般的条件,例如x=>x.name==xxxx x.age==xxx
public static string ResovleFunc(Expression express)
{
var inner = express as BinaryExpression;
string Name = (inner.Left as MemberExpression).Member.Name;
object Value = (inner.Right as ConstantExpression).Value;
var Operator = GetOperator(inner.NodeType);
string Result = string.Format("({0} {1} '{2}')", Name, Operator, Value);
return Result;
}
//解析linq to object这类扩展方法
public static string ResovleLinqToObject(Expression expression)
{
var MethodCall = expression as MethodCallExpression;
var MethodName = MethodCall.Method.Name;
if (MethodName == "Contains")
{
object Temp_Vale = (MethodCall.Arguments[0] as ConstantExpression).Value;
string Value = string.Format("%{0}%", Temp_Vale);
string Name = (MethodCall.Object as MemberExpression).Member.Name;
string Result = string.Format("{0} like '{1}'", Name, Value);
return Result;
}
return null;
}
public static string GetOperator(ExpressionType expressiontype)
{
switch (expressiontype)
{
case ExpressionType.And:
return "and";
case ExpressionType.AndAlso:
return "and";
case ExpressionType.Or:
return "or";
case ExpressionType.OrElse:
return "or";
case ExpressionType.Equal:
return "=";
case ExpressionType.NotEqual:
return "";
case ExpressionType.LessThan:
return "";
case ExpressionType.LessThanOrEqual:
return "";
case ExpressionType.GreaterThan:
return ">";
case ExpressionType.GreaterThanOrEqual:
return ">=";
default:
throw new Exception(string.Format("不支持{0}此种运算符查找!" + expressiontype));
}
}
}
|
已经初步的达到了我们的目的了,但是我们的查询条件不可能固定是2个,有可能是N个,这时左边和右边又要继续再分下去,直到无法再分(想到递归了吧?)。而且我们还需要将查询条件参数化。而且我们的条件删除时也会用到。所以我们应该把它独立出来。传入一个lambda,生成sql where部分的语句,生成sqlparameter[]。这才是关键。。于是我们来构建一个解析Expresstion的类。。下面我就直接给出我自己写的实现代码了。。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
|
public class ResolveExpress
{
public Dictionarystring, object> Argument;
public string SqlWhere;
public SqlParameter[] Paras;
///
/// 解析lamdba,生成Sql查询条件
///
///
///
public void ResolveExpression(Expression expression)
{
this.Argument = new Dictionarystring, object>();
this.SqlWhere = Resolve(expression);
this.Paras = Argument.Select(x => new SqlParameter(x.Key, x.Value)).ToArray();
}
private string Resolve(Expression expression)
{
if (expression is LambdaExpression)
{
LambdaExpression lambda = expression as LambdaExpression;
expression = lambda.Body;
return Resolve(expression);
}
if (expression is BinaryExpression)
{
BinaryExpression binary = expression as BinaryExpression;
if (binary.Left is MemberExpression & binary.Right is ConstantExpression)//解析x=>x.Name=="123" x.Age==123这类
return ResolveFunc(binary.Left, binary.Right, binary.NodeType);
if (binary.Left is MethodCallExpression & binary.Right is ConstantExpression)//解析x=>x.Name.Contains("xxx")==false这类的
{
object value = (binary.Right as ConstantExpression).Value;
return ResolveLinqToObject(binary.Left, value, binary.NodeType);
}
if (binary.Left is MemberExpression & binary.Right is MemberExpression)//解析x=>x.Date==DateTime.Now这种
{
LambdaExpression lambda = Expression.Lambda(binary.Right);
Delegate fn = lambda.Compile();
ConstantExpression value = Expression.Constant(fn.DynamicInvoke(null), binary.Right.Type);
return ResolveFunc(binary.Left, value, binary.NodeType);
}
}
if (expression is UnaryExpression)
{
UnaryExpression unary = expression as UnaryExpression;
if (unary.Operand is MethodCallExpression)//解析!x=>x.Name.Contains("xxx")或!array.Contains(x.Name)这类
return ResolveLinqToObject(unary.Operand, false);
if (unary.Operand is MemberExpression & unary.NodeType == ExpressionType.Not)//解析x=>!x.isDeletion这样的
{
ConstantExpression constant = Expression.Constant(false);
return ResolveFunc(unary.Operand, constant, ExpressionType.Equal);
}
}
if (expression is MemberExpression & expression.NodeType == ExpressionType.MemberAccess)//解析x=>x.isDeletion这样的
{
MemberExpression member = expression as MemberExpression;
ConstantExpression constant = Expression.Constant(true);
return ResolveFunc(member, constant, ExpressionType.Equal);
}
if (expression is MethodCallExpression)//x=>x.Name.Contains("xxx")或array.Contains(x.Name)这类
{
MethodCallExpression methodcall = expression as MethodCallExpression;
return ResolveLinqToObject(methodcall, true);
}
var body = expression as BinaryExpression;
if (body == null)
throw new Exception("无法解析" + expression);
var Operator = GetOperator(body.NodeType);
var Left = Resolve(body.Left);
var Right = Resolve(body.Right);
string Result = string.Format("({0} {1} {2})", Left, Operator, Right);
return Result;
}
///
/// 根据条件生成对应的sql查询操作符
///
///
///
private string GetOperator(ExpressionType expressiontype)
{
switch (expressiontype)
{
case ExpressionType.And:
return "and";
case ExpressionType.AndAlso:
return "and";
case ExpressionType.Or:
return "or";
case ExpressionType.OrElse:
return "or";
case ExpressionType.Equal:
return "=";
case ExpressionType.NotEqual:
return "";
case ExpressionType.LessThan:
return "";
case ExpressionType.LessThanOrEqual:
return "";
case ExpressionType.GreaterThan:
return ">";
case ExpressionType.GreaterThanOrEqual:
return ">=";
default:
throw new Exception(string.Format("不支持{0}此种运算符查找!" + expressiontype));
}
}
private string ResolveFunc(Expression left, Expression right, ExpressionType expressiontype)
{
var Name = (left as MemberExpression).Member.Name;
var Value = (right as ConstantExpression).Value;
var Operator = GetOperator(expressiontype);
string CompName = SetArgument(Name, Value.ToString());
string Result = string.Format("({0} {1} {2})", Name, Operator, CompName);
return Result;
}
private string ResolveLinqToObject(Expression expression, object value, ExpressionType? expressiontype = null)
{
var MethodCall = expression as MethodCallExpression;
var MethodName = MethodCall.Method.Name;
switch (MethodName)//这里其实还可以改成反射调用,不用写switch
{
case "Contains":
if (MethodCall.Object != null)
return Like(MethodCall);
return In(MethodCall, value);
case "Count":
return Len(MethodCall, value, expressiontype.Value);
case "LongCount":
return Len(MethodCall, value, expressiontype.Value);
default:
throw new Exception(string.Format("不支持{0}方法的查找!", MethodName));
}
}
private string SetArgument(string name, string value)
{
name = "@" + name;
string temp = name;
while (Argument.ContainsKey(temp))
{
int code = Guid.NewGuid().GetHashCode();
if (code 0)
code *= -1;
temp = name + code;
}
Argument[temp] = value;
return temp;
}
private string In(MethodCallExpression expression, object isTrue)
{
var Argument1 = (expression.Arguments[0] as MemberExpression).Expression as ConstantExpression;
var Argument2 = expression.Arguments[1] as MemberExpression;
var Field_Array = Argument1.Value.GetType().GetFields().First();
object[] Array = Field_Array.GetValue(Argument1.Value) as object[];
Liststring> SetInPara = new Liststring>();
for (int i = 0; i )
{
string Name_para = "InParameter" + i;
string Value = Array[i].ToString();
string Key = SetArgument(Name_para, Value);
SetInPara.Add(Key);
}
string Name = Argument2.Member.Name;
string Operator = Convert.ToBoolean(isTrue) ? "in" : " not in";
string CompName = string.Join(",", SetInPara);
string Result = string.Format("{0} {1} ({2})", Name, Operator, CompName);
return Result;
}
private string Like(MethodCallExpression expression)
{
object Temp_Vale = (expression.Arguments[0] as ConstantExpression).Value;
string Value = string.Format("%{0}%", Temp_Vale);
string Name = (expression.Object as MemberExpression).Member.Name;
string CompName = SetArgument(Name, Value);
string Result = string.Format("{0} like {1}", Name, CompName);
return Result;
}
private string Len(MethodCallExpression expression, object value, ExpressionType expressiontype)
{
object Name = (expression.Arguments[0] as MemberExpression).Member.Name;
string Operator = GetOperator(expressiontype);
string CompName = SetArgument(Name.ToString(), value.ToString());
string Result = string.Format("len({0}){1}{2}", Name, Operator, CompName);
return Result;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
static void Main(string[] args)
{
string[] Names = { "Andy", "Amy", "Mike" };
Expressionbool>> func = x => (!Names.Contains(x.Name) & (x.Name == "A" || x.Name.Count() > 5));
ResolveExpress resolve = new ResolveExpress();
resolve.ResolveExpression(func);
Console.WriteLine(resolve.SqlWhere);
foreach (var item in resolve.Paras)
{
Console.WriteLine(item.ParameterName + ":" + item.Value);
}
Console.ReadKey();
}
|
结果:
这里有几个重要的东西要给大家讲下
string[] Names={“Andy”,”Amy”,”Mike”};
1.)x => Names.Contains(x.Name);
2.)x => Names.Contains(x.Name)==false;
3.)x => !Names.Contains(x.Name);
这3种在Expression中的表现都不一样
1的话会看成是一个静态方法(MethodCallExpression)
2的话会看成是一个2元运算(BinaryExpression)
3的话会看成是一个1元运算(UnaryExpression)
所以我们都要支持,处理都有所不同。
还有
x=>x.Birthday
string name=”123″;
x=>x.Name==name;
和
x=>x.Name==”123″
的处理也不一样。大家可以在例子中细细的看看。
这样的构造使得我们切换数据库变得非常简单。因为我们程序中的查询都是基于lambda。换了数据库只要添加一个对应的lamdba转数据库查询条件的实现就可以了。写得够多了。至于数据层怎么封装,到了这一步它已经变得没什么难度了。希望大家能从文章中有所启发和帮助
下篇文章将结合解析Expression和IQueryable来实现延迟加载
补充点东西
IEnumerable和IQueryable有什么不同?
为什么EF查询后返回的是IQueryable而不是IEnumerable。我们对着IQueryableF12去看看。
啥都没,就继承了几个接口。鼠标移到IQueryable上。F12
IQueryable中有3个属性。
Type是类型。
Expresstion是表达式。
那IQueryProvider是什么?
再看看IQueryProvider接口的定义。
CreateQuery是创建查询条件
Execute是执行查询(通常在GetEnumerator()中调用)
当我们IQueryable.Where(x=>x.xxx==”123″)时。其实Where方法内部应该是调用了IQueryable接口中的IQueryProvider属性的CreateQuery(Expresstion expresstion)方法,然后将方法的返回值又返回出来。
而参数(Expresstion )呢?则是IQueryable.Where(x=>x.xxx==”123″)2部分的Expresstion相并。所以IQueryable只是创建条件。所以51楼的朋友说得非常对。
那什么时候执行呢?因为我们的IQueryable继承了IEnumabler,所以我们必须实现GetEnumerator()。我们ToList或foreach时,其实就会调用GetEnumerator()。这时我们就调用Execute进行解析Expresstion,从而得到我们想要的结果。
总结就是IQueryable只是创建条件,当我们调用a.Where(x=>xxx)时,其实是将a与后面的条件相并,生成一个新的IQueryable。当我们foreach时就会调用GetEnumerator()。这时我们一般会调用IQueryProvider里的Execute去解析Expresstion并查询出我们想要的结果
我也知道这篇文章介绍的和我们所说的“ORM”相差很远,但是所谓的ORM最复杂的莫非查询部分了,而依照我这思路走下去,我觉得是可以自己完成一个的。。
我刚开始写博客第二天,没想到这文章反响这么大。我承认有点重复造轮子,也非常不成熟,但我还是想通过自己的思考去构造属于自己的东西。
不知道大家有没看过头文字D,里头有个组织叫东堂垫,他们里面的人是拆掉ABS的。因为他们会长说,你要先学会不使用ABS进行刹车才知道ABS的真谛