我们知道的在.net cf 2.0上没有对LINQ的支持,在.net cf 3.5上即使支持Linq To DataSet和Linq To XML,也还是没有Linq To SQL,在可能即将发布的.net cf 4.0中,好像也没提到Linq To SQL什么事儿,那么是不是基于.net cf 的开发就与Linq To SQL无缘呢?!基实也不尽然。
在前天我写过一篇《在 .NET 2.0上使用“LINQ”》中,提到通过移植mono代码,在.net 2.0上实现LINQ,那么能不能把它也移植到.net cf上哪?说干就干,跟随我一起来一次“冒险”吧!
第一关:Reflection.Emit
在.net cf 2.0 及 3.5上,没有Reflection.Emit相关类库,然而,LINQ Expression的Compile离可它就玩不转了。不过,很幸运地,我们可以找到个EmitCF项目,它基本上实现了Linq Expression用到的功能,第一关算过了。
第二关:GetCurrentMethod
精简版就是精简版,经常会碰到函数缺失,多数比较简单的函数基本可以自已实现,可是,一但碰到“内核级”的函数,本人就无能为力了,不可硬攻,就来点“旁门左道”,网上比较流行的方法是通过在Stack中查找最近一次函数调用,可是这种方法无法区分同名的函数,我们不能用,又经过一通琢磨,我发现可以通过函数参数名来区分不同的函数,这一关也过了。
private static Dictionary<string, Dictionary<string, MethodInfo>> methods = null; private static void InitializeMethods() { methods = new Dictionary<string, Dictionary<string, MethodInfo>>(); MethodInfo[] methodInfoArray = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (var methodInfo in methodInfoArray) { string methodName = methodInfo.Name; if (!methods.ContainsKey(methodName)) { methods.Add(methodName, new Dictionary<string, MethodInfo>()); } Dictionary<string, MethodInfo> methodDictionary = methods[methodName]; string[] parameterNames = GetParameterNames(methodInfo); string parameterNameKey = string.Join(",", parameterNames); if (!methodDictionary.ContainsKey(parameterNameKey)) { methodDictionary.Add(parameterNameKey, methodInfo); } } } private static string[] GetParameterNames(MethodInfo methodInfo) { if (methodInfo.IsGenericMethodDefinition) { System.Type[] genericArguments = methodInfo.GetGenericArguments(); System.Type[] types = new System.Type[genericArguments.Length]; for (int i = 0; i < types.Length; i++) { types[i] = typeof(int); } methodInfo = methodInfo.MakeGenericMethod(types); } ParameterInfo[] parameterInfoArray = methodInfo.GetParameters(); string[] parameterNames = new string[parameterInfoArray.Length]; for (int i = 0; i < parameterInfoArray.Length; i++) { parameterNames[i] = parameterInfoArray[i].Name; } return parameterNames; } private static MethodBase GetCurrentMethod(string methodName, string[] parameterNames) { if (methods == null) { InitializeMethods(); } if (methods.ContainsKey(methodName)) { Dictionary<string, MethodInfo> methodDictionary = methods[methodName]; string parameterNameKey = string.Join(",", parameterNames); if (methodDictionary.ContainsKey(parameterNameKey)) { return methodDictionary[parameterNameKey]; } } return null; }
第三关:inner class
前面提到的EmitCF有个问题,因为它生成独立的程序集来执行Emit的动态代码,而Linq表达式内的支匿名对象又被编译成inner类型,这样EmitCF生成的动态代码就访问不到这些对象,这种情况下我们通过会第一时间想到"反射",那我们就用Emit生成反射调用代码来访问这些对象,调试起来确实麻烦了许多,不过终归还是执行过去了。
第四关:DbLinq
说到这儿,离LINQ TO SQL很近了,但也不就说,这样就很容易现实,因为我想移植DbLinq过程中可能还会碰到更多问题,很遗憾我没机会走下去了,因为我最初的目的是要完成.net cf 上的"Linq to DAC",我的想法基本上实现了,虽然还有些问题(见注1),但今天也就到这儿了。
[Test] public void TestMethod1() { QueryableData<Issue> issues = new QueryableData<Issue>(dc); var il = from s in issues where s.IssueID > 0 && s.Title == "a" select s; foreach (var i in il) { Assert.IsTrue(i.IssueID > 0); Assert.IsTrue(i.Title == "a"); } }
注1:
1. 动态程序集没法的内存中直接加载,一定要生成物理文件。
2. 在Linq表达式中,不能使用数组的元素作为数据值,如:s.IssueID == issueList[0], 会出错。
3. 不能使用select new {...} 返回新的动态对象。