LINQ 查询
var query = from r in Formula1.GetChampions() where r.Country == "Brazil" orderby r.Wins descending select r; foreach (Racer racer in query) { Console.WriteLine("{0:A}", racer); }
扩展方法
LINQ为IEnumerable<T>接口提供各种扩展方法,以便用户实现了该接口的任意集合上使用LINQ查询。扩展方法在静态类中声明,定义一个静态方法,第一参数定义扩展的类型。
扩展方法可以将方法写入最初没有提供该方法的类中,可以把方法添加到实现某个特定接口的任何类中,这样多个类可以使用相同的实现代码。
class Program { private static void Main(string[] args) { string s1 = "111111"; s1.Function(); string s2 = "2222222"; s2.Function(); } } public static class StringExtension { public static void Function(this string s) { Console.WriteLine("this string is " + s); } }
第一个参数 this 用来区分是扩展方法还是静态方法。
第二个参数 需要对应扩展的类。
注意 扩展方法里不能访问类型的私有成员。
还可以这样调用
StringExtension.Function(s1);
LINQ 扩展示例
var champions = new List<Racer>(Formula1.GetChampions()); IEnumerable<Racer> brazilChampions = champions.Where(r => r.Country == "Brazil"). OrderByDescending(r => r.Wins). Select(r => r); foreach (Racer r in brazilChampions) { Console.WriteLine("{0:A}", r); }
推迟查询的执行
var names = new List<string> { "Nino", "Alberto", "Juan", "Mike", "Phil" }; var namesWithJ = from n in names where n.StartsWith("J") orderby n select n; Console.WriteLine("First iteration"); foreach (string name in namesWithJ) { Console.WriteLine(name); } Console.WriteLine(); names.Add("John"); names.Add("Jim"); names.Add("Jack"); names.Add("Denny"); Console.WriteLine("Second iteration"); foreach (string name in namesWithJ) { Console.WriteLine(name); }
namesWithJ 一旦使用了ToArray、ToList之类的。就 names 了。如
var namesWithJ = (from n in names where n.StartsWith("J") orderby n select n).ToList();
标准的查询操作
Enumberable 类定义的标准查询操作符。
标准查询操作符 |
说明 |
Where OfType<TResult> |
称为筛选操作符定义返回元素的条件。 Where 使用谓词,返回符合条件的元素。 OfType<TResult> 返回符合类型的元素。 |
Select SelectMany |
投射操作符用于把对象转换为另一个类型的新对象。 Select 和 SelectMany 定义根据选择器函数选择结果值的投射。 |
OrderBy ThenBy OrderByDescending ThenByDescending Reverse |
排序操作符改变返回的元素的顺序。 Orderby 升序排序。 OrderBydescending 降序排序。 TheBy 和 ThenByDescending 二次排序。 Reverse 反转集合元素。 |
Join GroupJoin |
连接操作符。用于合并不直接相关的集合。 Join 根据键选择器函数连接两个集合。 GroupJoin 连接两个集合。 |
GroupBy ToLookup |
组合操作符把数据放在组中。 GroupBy 组合公共键的元素。 Tookup 创建一个一对多字典,组合元素。 |
Any All Contains |
限定操作符,元素满足指定的条件。 Any 满足谓词函数的函数。 All 所有元素是否都满足谓词函数。 Contains 检查某个元素是否在集合中。 |
Take Skip TakeWhile SkipWhile |
分区操作符返回集合的子集。 Take 从集合提取元素个数。 Skip 跳过指定的元素个数,提取其他元素。 TakeWhile 提取条件为真的元素。 |
Distinct Union Intersect Except Zip |
Set操作符返回一个集合。 Distinct 删除重复的元素。 Union 返回集合中唯一元素。 Intersect 返回两个集合都有的元素。 Except 只出现在一个集合中的元素。 Zip 两个集合合并为一个元素。 |
First FirstOrDefault Last LastOrDefault ElementAt ElementAtOrDefault Single SingleOrDefault |
元素操作符返回一个元素。 First 返回第一个满足条件的元素。 FirstOrDefault 类似First,如果未找到满足条件元素,返回类型的默认值。 Last 返回最后一个满足条件的元素。 ElementAt 返回元素的位置。 Single 返回一个满足条件的元素。如果有多个元素都满足条件,就抛出一个异常。 |
Count Sum Min Max Average Aggregate |
聚合操作符计算集合值。 Sum 总和。 Count 所有元素个数。 Min 最小元素。 Max 最大元素。 Average 平均值。 Aggregate 根据输入的表达式获取聚合值。 |
ToArray AsEnumerable ToList ToDictionary Cast<TResult> |
转换操作符。 |
Empty Range Repeat |
生成操作符。 Empty 空集合。 Range 返回一系列数字。 Repeat 返回始终重复一直的集合。 |
筛选
where子句合并多个表达式。
var racers = from r in Formula1.GetChampions() where r.Wins > 15 && (r.Country == "Brazil" || r.Country == "Austria") select r; foreach (var racer in racers) { Console.WriteLine("{0:A}", racer); }
下面代码有Where扩展方法Where和Select调用。
var racers = Formula1.GetChampions(). Where(r => r.Wins > 15 && (r.Country == "Brazil" || r.Country == "Austria")). Select(r => r);
索引器筛选
索引是筛选器返回的每个结果的计数器。
下面由Where扩展方法调用, 使用索引返回。
var racers = Formula1.GetChampions(). Where((r, index) => r.Wins > 15 && index % 2 != 0);
类型筛选
基于类型筛选,使用 OfType 扩展方法。
object[] data = {"one", 1, 2, "li"}; var query = data.OfType<int>(); foreach (var intValue in query) { Console.WriteLine("{0}", intValue); }
复合的from子句
var ferrariDrivers = from r in Formula1.GetChampions() from c in r.Cars where c == "Ferrari" orderby r.LastName select r.FirstName + " " + r.LastName; foreach (string name in ferrariDrivers) { Console.WriteLine("{0}", name); }
C#编译器把复合的from子句和LINQ查询转换为SelectMany扩展方法。SelectMany方法用于迭代序列的序列。
var ferrariDrivers = Formula1.GetChampions(). SelectMany( r => r.Cars , (r,c) => new { Racer = r, Car = c }). Where(r => r.Car == "Ferrari"). OrderBy(r => r.Racer.LastName). Select(r => r.Racer.FirstName + " " + r.Racer.LastName);
排序
对序列排序,前面使用了orderby 子句。使用两种方法。
var racers = from r in Formula1.GetChampions() where r.Country == "Brazil" orderby r.Wins descending select r; var racers2 = Formula1.GetChampions().Where(r => r.Country == "Brazil").OrderByDescending(r => r.Wins).Select(r => r);
OrderBy() 和 OrderByDescending() 方法返回 IOrderEnumerable<Tsource> 这个接口派生自IEnumerable<TSource>接口,包含一个额外的方法 CreateOrderedEnumerable<TSource>()。这个方法用于进一步非序列排序。根据关键字选择器来排序,可以使用ThenBy() 和 ThenByDescending() 方法继续排序。 这两个方法 需要 IOrderEnumerable<TSource> 接口才能工作,但也返回这个接口。所以添加任意多个ThenBy() 和 ThenByDescending() 方法,对集合排序。
Linq查询时,需要把所有用于排序的不同关键字 用逗号 分割开。 添加到 orderby 子句。 扩展方法 Take 提取前面 10 个元素。
var racers = (from r in Formula1.GetChampions() orderby r.Country, r.LastName, r.FirstName select r).Take(10); foreach (var item in racers) { Console.WriteLine(item); }
也可以使用 OrderBy() 和 ThenBy() 扩展方法执行相同的操作:
var racers = (from r in Formula1.GetChampions() orderby r.Country, r.LastName, r.FirstName select r).Take(10); foreach (var item in racers) { Console.WriteLine(item); } Console.WriteLine("***********"); var racers2 = Formula1.GetChampions(). OrderBy(r => r.Country). ThenBy(r => r.LastName). ThenBy(r => r.FirstName). Take(10); foreach (var item in racers2) { Console.WriteLine(item); }
分组
根据一个关键字值对查询结果进行分组,使用 group 子句。
子句 group r by r.Country into g 根据 Country 属性组合。并定义一个新的标识符g。它以后用于访问分组的结果信息。
var countries = from r in Formula1.GetChampions() group r by r.Country into g orderby g.Count() descending, g.Key where g.Count() >= 2 select new { Country = g.Key, Count = g.Count() }; foreach (var country in countries) { Console.WriteLine(format: "{0,-10} {1}", arg0: country.Country, arg1: country.Count); }
使用扩展方法,子句 group r by r.Country into g 解析为 GroupBy(r => r.Country) 返回分组序列。
var contries2 = Formula1.GetChampions(). GroupBy(r => r.Country). OrderByDescending(g => g.Count()). ThenBy(g => g.Key). Where(g => g.Count() >= 2). Select(g => new { Country = g.Key, Count = g.Count() });
对嵌套的对象分组
分组的对象包含嵌套的序列,可以改变 select 子句创建的匿名类型。
var countries = from r in Formula1.GetChampions() group r by r.Country into g orderby g.Count() descending, g.Key where g.Count() >= 2 select new { Country = g.Key, Count = g.Count(), Racers = from racer in g orderby racer.LastName select racer.FirstName + " " + racer.LastName }; foreach (var country in countries) { Console.WriteLine(format:"{0, -10} {1}", arg0:country.Country, arg1:country.Count); foreach (var racer in country.Racers) { Console.WriteLine(format:"{0}; ", arg0:racer); } Console.WriteLine(); }
内连接
使用 join 子句 根据特定的条件合并两个数据源,但之前要获得两个要连接的列表。
var racers = from r in Formula1.GetChampions() from y in r.Years select new { Year = y, Name = r.FirstName + " " + r.LastName }; var teams = from t in Formula1.GetContructorChampions() from y in t.Years select new { Year = y, Name = t.Name }; //var racersAndTeams = // (from r in racers // join t in teams on r.Year equals t.Year // orderby t.Year // select new // { // Year = r.Year, // Champion = r.Name, // Constructor = t.Name // }).Take(10); var racersAndTeams = (from r in from r1 in Formula1.GetChampions() from yr in r1.Years select new { Year = yr, Name = r1.FirstName + " " + r1.LastName } join t in from t1 in Formula1.GetContructorChampions() from yt in t1.Years select new { Year = yt, Name = t1.Name } on r.Year equals t.Year orderby t.Year select new { Year = r.Year, Champion = r.Name, Constructor = t.Name }).Take(10); Console.WriteLine("Year World Champion Constructor Title"); foreach (var item in racersAndTeams) { Console.WriteLine("{0}: {1,-20} {2}", item.Year, item.Champion, item.Constructor); }
左外连接
左外连接返回左边序列中的全部元素,即使它们在右边的序列中并没有匹配的元素。
左外连接用join子句和 DefaultIfEmpty 方法定义。 使用 DefaultIfEmpty 定义其右侧的默认值。
var racers = from r in Formula1.GetChampions() from y in r.Years select new { Year = y, Name = r.FirstName + " " + r.LastName }; var teams = from t in Formula1.GetContructorChampions() from y in t.Years select new { Year = y, Name = t.Name }; var racersAndTeams = (from r in racers join t in teams on r.Year equals t.Year into rt from t in rt.DefaultIfEmpty() orderby r.Year select new { Year = r.Year, Champion = r.Name, Constructor = t == null ? "no constructor championship" : t.Name }).Take(10); Console.WriteLine("Year Champion Constructor Title"); foreach (var item in racersAndTeams) { Console.WriteLine("{0}: {1,-20} {2}", item.Year, item.Champion, item.Constructor); }
组连接
左外连接使用了组连接和 into 子句。它有一部分与组连接相同,只不过组连接不适用 DefaultIfEmpty 方法。 使用组连接时,可以连接两个独立的序列,对于其中一个序列中的某个元素,另一个序列中存在对应项列表。
var racers = Formula1.GetChampionships() .SelectMany(cs => new List<RacerInfo>() { new RacerInfo { Year = cs.Year, Position = 1, FirstName = cs.First.FirstName(), LastName = cs.First.LastName() }, new RacerInfo { Year = cs.Year, Position = 2, FirstName = cs.Second.FirstName(), LastName = cs.Second.LastName() }, new RacerInfo { Year = cs.Year, Position = 3, FirstName = cs.Third.FirstName(), LastName = cs.Third.LastName() } }); var q = (from r in Formula1.GetChampions() join r2 in racers on new { FirstName = r.FirstName, LastName = r.LastName } equals new { FirstName = r2.FirstName, LastName = r2.LastName } into yearResults select new { FirstName = r.FirstName, LastName = r.LastName, Wins = r.Wins, Starts = r.Starts, Results = yearResults }); foreach (var r in q) { Console.WriteLine("{0} {1}", r.FirstName, r.LastName); foreach (var results in r.Results) { Console.WriteLine("{0} {1}", results.Year, results.Position); } }
集合操作
扩展方法 Distinct()、 Union()、Intersect() 和 Except() 都是集合操作。
Func<string, IEnumerable<Racer>> racersByCar = car => from r in Formula1.GetChampions() from c in r.Cars where c == car orderby r.LastName select r; Console.WriteLine("World champion with Ferrari and McLaren"); foreach (var racer in racersByCar("Ferrari").Intersect(racersByCar("McLaren"))) { Console.WriteLine(racer); }
集合操作通过调用实体类的 GetHashCode() 和 Equals() 方法比较对象。 对于自定义比较,可以传递实现 IEqualityComparer<T>接口的对象。
合并
Zip 方法。 .Net 4.0 新加的,用一个谓词函数把两个相关的序列合并为一个。
var racerNames = from r in Formula1.GetChampions() where r.Country == "Italy" orderby r.Wins descending select new { Name = r.FirstName + " " + r.LastName }; var racerNamesAndStarts = from r in Formula1.GetChampions() where r.Country == "Italy" orderby r.Wins descending select new { LastName = r.LastName, Starts = r.Starts }; var racers = racerNames.Zip(racerNamesAndStarts, (first, second) => first.Name + ", starts: " + second.Starts); foreach (var r in racers) { Console.WriteLine(r); }
分区
扩展方法 Take() 和 Skip() 等的分区操作可以用于分页。
Skip() 忽略根据页面大小和实际页数计算出的项数。
Take() 根据页面大小提取一定数量的项。
int pageSize = 5; int numberPages = (int)Math.Ceiling(Formula1.GetChampions().Count() / (double)pageSize); for (int page = 0; page < numberPages; page++) { Console.WriteLine("Page {0}", page); var racers = (from r in Formula1.GetChampions() orderby r.LastName, r.FirstName select r.FirstName + " " + r.LastName). Skip(page * pageSize).Take(pageSize); foreach (var name in racers) { Console.WriteLine(name); } Console.WriteLine(); }
对应的扩展方法 TakeWhile() 和 SkipWhile() 。
聚合操作符
聚合操作符(如 Count()、Sum()、Min()、Max()、Average() 、Aggregate() )返回一个值。
Count 返回集合项数。
var query = from r in Formula1.GetChampions() let numberYears = r.Years.Count() where numberYears >= 3 orderby numberYears descending, r.LastName select new { Name = r.FirstName + " " + r.LastName, TimesChampion = numberYears }; foreach (var r in query) { Console.WriteLine("{0} {1}", r.Name, r.TimesChampion); }
Sum 序列中的所有数字的和。
var countries = (from c in from r in Formula1.GetChampions() group r by r.Country into c select new { Country = c.Key, Wins = (from r1 in c select r1.Wins).Sum() } orderby c.Wins descending, c.Country select c).Take(5); foreach (var country in countries) { Console.WriteLine("{0} {1}", country.Country, country.Wins); }
Min 返回集合中的最小值。
Max 返回集合中的最大值。
Average 返回集合中的平均值。
Aggregate 传递一个 lambda 表达式,该表达式对所有的值进行聚合。
转换操作符
查询可以推迟到访问数据项时再执行。在迭代中使用查询时,查询会执行。而使用转换操作符会立即执行查询,把查询结果放在数组、列表或字典中。
ToList() 立即执行查询,结果放在 List<T> 类中。
List<Racer> racers = (from r in Formula1.GetChampions() where r.Starts > 150 orderby r.Starts descending select r).ToList(); foreach (var racer in racers) { Console.WriteLine("{0} {0:S}", racer); }
也可以用 ToLookup<TKey, TElement> 类中,键可以对应多个值。
ToDictionary 支持键对应一个值。
var racers = (from r in Formula1.GetChampions() from car in r.Cars select new { Car = car, Racer = r } ).ToLookup(cr => cr.Car, cr => cr.Racer); if (racers.Contains("Williams")) { foreach (var williamsRacer in racers["Williams"]) { Console.WriteLine(williamsRacer); } }
Cast 在非类型化的集合上查询
var list = new System.Collections.ArrayList(Formula1.GetChampions() as System.Collections.ICollection); var query = from r in list.Cast<Racer>() where r.Country == "USA" orderby r.Wins descending select r; foreach (var racer in query) { Console.WriteLine("{0:A}", racer); }
生成操作符
生成操作符 Range()、Empty() 和 Repear() 是返回序列的正常静态方法。
在 Linq to Objects 中,这些方法可用于 Enumerable 类。
如 需要填充一二范围的数字,此时就应使用 Range() 方法。这个方法第一个参数作为起始值,把第二个参数作为要填充的项数。
var values = Enumerable.Range(1, 20); foreach (var value in values) { Console.WriteLine(value); }
Range() 方法不返回填充所定义值的集合,与其他方法一样,推迟查询,返回一个 RangeEnumerator。其中用 yield return 语句,来递增值。
该结果也可以与其他扩展方法一起用。
var values = Enumerable.Range(1, 20).Select(n => n * 3); foreach (var value in values) { Console.WriteLine(value); }
Empty() 方法返回一个不返回值的迭代器,用于需要一个集合的参数,可以给参数传递空集合。
string[] names1 = { "Hartono, Tommy" }; string[] names2 = { "Adams, Terry", "Andersen, Henriette Thaulow", "Hedlund, Magnus", "Ito, Shu" }; string[] names3 = { "Solanki, Ajay", "Hoeing, Helge", "Andersen, Henriette Thaulow", "Potra, Cristina", "Iallo, Lucio" }; List<string[]> namesList = new List<string[]> { names1, names2, names3 }; IEnumerable<string> allNames = namesList.Aggregate(Enumerable.Empty<string>(), (current, next) => next.Length > 3 ? current.Union(next) : current); foreach (string name in allNames) { Console.WriteLine(name); }
Repeat() 方法 返回一个迭代器,把同一个值重复特定的次数。
IEnumerable<string> strings = Enumerable.Repeat("I like programming.", 15); foreach (String str in strings) { Console.WriteLine(str); }
并行Linq
System.Linq 名称空间中的包含的类 ParallelEnumerable 可以分解查询的工作, 使其分布在多个线程上。尽管 Enumerable 类给 IEnumerable<T> 接口定义了扩展方法,但 ParallelEnumerable 类的大多数扩展方法是 ParallelQuery<TSource>类的扩展。一个重要的例外是 AsParallel() 方法,扩展 IEnumerable<TSource> 接口,返回 ParallelQuery<TSource>类,所以正常的集合类可以以平行方式查询。
static IEnumerable<int> SampleData() { const int arraySize = 100000000; var r = new Random(); return Enumerable.Range(0, arraySize).Select(x => r.Next(140)).ToList(); } var data = SampleData();
var res = (from x in data.AsParallel() where Math.Log(x) < 4 select x).Average();
// 修改后的语法 var res2 = data.AsParallel().Where(x => Math.Log(x) < 4).Select(x => x).Average();
与Linq查询一样,编译器会修改语法,调用AsParallel()、Where()、Select()、Average()。 Asparallel() 方法调用用 ParallelEnumerable 类定义,扩展 IEnumberable<T>接口,所以可以对简单的数组调用它。 AsParallel() 方法返回 ParallelQuery<TSource>。因为返回的类型,所以编译器选择的Where()方法是ParallelEnumerable.Where(),而不是 Enumerable.Where()。 其他的 Select 和 Average 也是来自 ParallelEnumerable 类。与 Enumerable 类相反,对于 ParllelEnumerable类,查询是分区的,以便多个线程可以同时处理该查询。集合可以分为多个部分,其中每个部分由不同的线程处理,以筛选其余项。完成分区的工作后,就需要合并,获得所有部分的总和。
运行时,可以打开任务管理器,就可以发现所有的CPU都处于忙碌的状态。
分区器
AsParallel()方法不仅扩展了 IEnumerable<T> 接口,还扩展了 Partitioner 类。通过它,可以影响创建的分区。
手动创建一个分区器
var result = (from x in Partitioner.Create(data).AsParallel() where Math.Log(x) < 4 select x).Average();
也可以调用WithExecutionMode() 和 WithDegreeOfParallelism() 方法,影响并行机制。对于 WithExecutionMode() 方法传递 ParallelExecutionMode的一个Default值或者 ForceParallelism值。默认情况下,并行Linq避免使用系统开销很高的并行机制。对于WithDegreeOfParallelism()方法,可以传递一个整数值,制定并行运行的最大任务数。
取消
.NET 提供一个标准方法,来取消长时间运行的任务,也适用于并行Linq。
要取消长时间运行的查询可以给查询添加WithCancellation() 方法,并传递一个 CancellactionToken令牌作为参数。CancelllationToken令牌从CancellactionTokenSource类中创建。该查询在单独的线程中运行,在该线程中,捕获一个OperationCanceledException类型的异常。如果取消了查询就出发这个异常。在主线程中,调用CancellationTokenSource类的Cancel()方法可以取消任务。
var data = SampleData(); CancellationTokenSource cts = new CancellationTokenSource(); Task.Factory.StartNew(() => { try { var res = (from x in data.AsParallel().WithCancellation(cts.Token) where Math.Log(x) < 4 select x).Average(); Console.WriteLine("query finished, sum:{0}",res); } catch (OperationCanceledException ex) { Console.WriteLine("canceled!"); Console.WriteLine(ex.Message); } }); string input = Console.ReadLine(); if (input.ToLower().Equals("y")) { cts.Cancel(); Console.WriteLine("canceled 2!"); }
表达式树
扩展方法需要将一个委托类型作为参数,这就可以将 lambda 表达式赋予参数。 lambda 表达式赋予 Expression<T>类型的参数。C#编译器根据类型给 lambda表达式定义不同的行为。如果类型是 Express<T>,编译器就从 lambda 表达式中创建一个表达式树,并存储在程序集中。这样,就可以在运行期间分析表达式树,并进行优化,以便于查询数据源。
http://www.cnblogs.com/mcgrady/archive/2014/05/17/3732694.html#_label5
private static void DisplayTree(int indent, string message, Expression expression) { string output = String.Format("{0} {1} ! NodeType: {2}; Expr: {3} ", "".PadLeft(indent, '>'), message, expression.NodeType, expression); indent++; switch (expression.NodeType) { case ExpressionType.Lambda: Console.WriteLine(output); LambdaExpression lambdaExpr = (LambdaExpression)expression; foreach (var parameter in lambdaExpr.Parameters) { DisplayTree(indent, "Parameter", parameter); } DisplayTree(indent, "Body", lambdaExpr.Body); break; case ExpressionType.Constant: ConstantExpression constExpr = (ConstantExpression)expression; Console.WriteLine("{0} Const Value: {1}", output, constExpr.Value); break; case ExpressionType.Parameter: ParameterExpression paramExpr = (ParameterExpression)expression; Console.WriteLine("{0} Param Type: {1}", output, paramExpr.Type.Name); break; case ExpressionType.Equal: case ExpressionType.AndAlso: case ExpressionType.GreaterThan: BinaryExpression binExpr = (BinaryExpression)expression; if (binExpr.Method != null) { Console.WriteLine("{0} Method: {1}", output, binExpr.Method.Name); } else { Console.WriteLine(output); } DisplayTree(indent, "Left", binExpr.Left); DisplayTree(indent, "Right", binExpr.Right); break; case ExpressionType.MemberAccess: MemberExpression memberExpr = (MemberExpression)expression; Console.WriteLine("{0} Member Name: {1}, Type: {2}", output, memberExpr.Member.Name, memberExpr.Type.Name); DisplayTree(indent, "Member Expr", memberExpr.Expression); break; default: Console.WriteLine(); Console.WriteLine("{0} {1}", expression.NodeType, expression.Type.Name); break; } } static void Main() { Expression<Func<Racer, bool>> expression = r => r.Country == "Brazil" && r.Wins > 6; DisplayTree(0, "Lambda", expression); }
LINQ提供程序
LINQ提供程序为特定的数据源实现了标准的查询操作符。LINQ提供程序也许会实现比LINQ定义的更多扩展方法,但至少要实现标准操作符。LINQ to XML 如 Extensions 类定义 Elements()、Descendants() 和 Ancestors() 。
LINQ提供程序的实现方案是根据名称空间和第一个参数的类型来选择的。如 LINQ to Objects 定义 Where() 方法的参数 和 在 LINQ to Entities 中定义的 Where() 的方法参数不同。