文章目錄
- 1. IEnumerable
- 2. IQueryable
- 3. LINQ to SQL
- 4. IEnumerable & IQueryable
- 4.1 Expression
- 4.2 Provider

1. IEnumerable
namespace System.Collections:
public interface IEnumerable
{public IEnumerator GetEnumerator ();
}public interface IEnumerator
{pubilc object Current { get; }public bool MoveNext ();public void Reset ();
}
IEnumerable 只有一個方法 GetEnumerator(), 既實現IEnumerable的所有泛型集合,都具備可枚舉(IEnumerator)的能力。
IEnumerator 只有一個屬性 Current,和兩個方法 MoveNext() \ Reset()。
通過 MoveNext() 和 Current 可以不停地移動 enumerator 的位置并返回當前的元素。
Reset() 會將 enumerator 設置到初始位置,既第一個元素之前。
2. IQueryable
namespace System.Linq:public interface IQueryable<out T> : System.Collections.Generic.IEnumerable<out T>, System.Linq.IQueryable {}public interface IQueryable : System.Collections.IEnumerable
{// 表達式樹返回結果的元素類型public Type ElementType { get; }// 獲取IQueryable實例的表達式樹public System.Linq.Expressions.Expression Expression { get; }public System.Linq.IQueryProvider Provider { get; }
}// 定義用于創建和執行IQueryable對象所描述的查詢的方法
public interface IQueryProvider
{public System.Linq.IQueryable CreateQuery(System.Linq.Expressions.Expression expression);public System.Linq.IQueryable<TElement> CreateQuery<TElement> (System.Linq.Expressions.Expression expression);public object? Execute(System.Linq.Expressions.Expression expression);public TResult Execute<TResult> (System.Linq.Expressions.Expression expression);
}
IQueryable 繼承 IEnumerable,所以 IQueryable 具備可枚舉的能力。
IQueryable 中的 Expession / Provider 則用來實現LINQ to SQL,具體可以看接下來的2節詳細解釋。
3. LINQ to SQL
LINQ to SQL是.Net Framework v3.5的組件,是能夠提供將關系數據作為對象管理的運行時基礎結構。
以往,編程語言通過 API 訪問數據庫數據的時候,需要將查詢語句轉為文本字符串。LINQ to SQL則會將對象模型中的語言集成查詢轉換為SQL,并發給數據庫執行。當返回結果時,LINQ to SQL會再轉換回可以用編程語言處理額對象。
所以,當擁有一個查詢的時候,并不意味著查詢已經執行:
var q = from c in dbContext.Customers Where c.City == ":London" select c;
命令對象會保留描述查詢的字符串,IQueryable 對象的 Expression。命令對象的 ExecuteReader() 方法執行后,以 DataReader 形式返回結果。IQueryable 對象通過 GetEnumerator() 方法返回 IEnumerator 結果。
如下 foreach 會執行兩次 query,這種行為成為延遲執行:
var q =from c in db.Customerswhere c.City == "London"select c;
// Execute first time
foreach (Customer c in q)Console.WriteLine(c.CompanyName);
// Execute second time
foreach (Customer c in q)Console.WriteLine(c.CompanyName);
如果提前將結果轉為任意標準的集合類,可以避免重復執行。
var q =from c in db.Customerswhere c.City == "London"select c;
// Execute once using ToList() or ToArray()
var list = q.ToList();
foreach (Customer c in list)Console.WriteLine(c.CompanyName);
foreach (Customer c in list)Console.WriteLine(c.CompanyName);
更多資料:LINQ:.NET Language-Integrated查詢
4. IEnumerable & IQueryable
4.1 Expression
針對 IEnumerable所設計的擴展方法都將表達式視為委托,而針對 IQueryable 的那些擴展方法用的則是表達式樹(expression tree)。
IQueryable 會解析表達式樹,并把這棵樹表示的邏輯轉為 provider 能夠操作的格式,將其放在離數據最近的地方去執行。即傳輸數據往往會少于 IEnumerable,總體性能更好。
借鑒 《Effective C#》中的例子,如下兩種寫法,返回的結果相同,但是工作方式卻不同:
// 1. use IQueryable
var q = from c in dbContext.Customers Where c.City == ":London" select c;
var finalAnswer = from c in q ordery c.Name select c;// 2. use Enumerable
var q = (from c in dbContext.Customers where c.City == "London" select c).AsEnumerable();
var finaAnswer = from c in q orderby c.Name select c;
方法1,采用的是 IQueryable內置的 LINQ to SQL,q的查詢語句,會和 第二行的組合起來,即只需要向數據庫發送一次調用,where和orderby會在同一次sql查詢操作里完成。
方法2,則是把數據庫對象從 IQueryable 強制轉為了 IEnumerable形式的標準可枚舉對象,即先向數據庫發送查詢請求,獲得所有的數據后,放在本地進行排序操作。
假如每種方法第二行還有一次 where 篩選一部分數據,那么方法1會組合2次 where,數據庫只會返回最終目標數據集。而方法2會先從數據把所有第一次 where 得到的數據傳到本地,之后在本地再次篩選,無疑增加了網絡數據傳輸量。
4.2 Provider
IQueryable 的 Provider 未必能支持每一種查詢方式,只能支持某些固定的運算符、方法,所以一旦查詢操作里面調用除此之外的方法,那么就有可能把序列當成 IEnumerable 來查詢,而非 IQueryable,否則會拋出異常。
再上 《Effective c#》例子:
private bool isValidProduct(Product p) => p.ProductName.LastIndexOf('C') == 0;// 1. 轉為 Enumerable 執行
var q1 = from p in dbContext.Products.AsEnumerable() where isValidProduct(p) select p;// 2. 直接執行 IsValidProduct
var q2 = from p in dbContext.Products where isValidProduct(p) select p;
方法1可以正常運行,只是在 AsEnumerable() 之后,查詢必須在本地執行,where 字句內的邏輯由LINQ to Objects處理。
方法2則會拋出異常,因為IQueryProvider會把查詢操作轉為T-SQL,交由遠端執行。