如果允許在UI層直接訪問Linq to Sql的DataContext,可以省去很多問題,譬如在處理多表join的時 候,我們使用var來定義L2S查詢,讓IDE自動推斷變量的具體類型 (IQueryable<匿名類型>),並 提供友好的智能提示;而且可以充分應用L2S的延遲加載特性,來進行動態查詢。但如果我們希望將業務 邏輯放在一個獨立的層中(譬如封裝在遠程的WCF應用中),又希望在邏輯層應用Linq to sql,則情況就 比較復雜了;由於我們只能使用var( IQueryable<匿名類型>),而var只能定義方法(Method)范圍 中聲明的變量,出了方法(Method)之後IDE就不認得它了;在這種對IQueryable<匿名類型>一無所 知的情況下,又希望能在開發時也能應用上IDE的智能感應,我們該怎麼定義層之間交互的數據傳輸載體 呢?又如何對它進行動態查詢呢?
內容比較多,分上下兩篇,上篇介紹查詢返回自定義實體,下篇介紹動態查詢。
下面來看一個示例(以NorthWind數據庫為示例),現在我們要在界面上展示某個用戶什麼時間訂購 了哪些產品。
如果允許在UI層直接訪問DataContext,我們可以這樣來寫:
1: using (NorthWindDataContext context = new NorthWindDataContext())
2: {
3: var query0 = from C in context.Customers
4: join O in context.Orders
5: on C.CustomerID equals O.CustomerID
6: join OD in context.Order_Details
7: on O.OrderID equals OD.OrderID
8: join P in context.Products
9: on OD.ProductID equals P.ProductID
10: select new
11: {
12: C.CustomerID,
13: C.CompanyName,
14: C.ContactName,
15: C.Address,
16: O.OrderDate,
17: P.ProductName
18: };
19: gridView.DataSource = query0.ToList();
20: gridView.DataBind();
21: }
這裡只查詢需要顯示的列,避免返回不必要的列。查詢返回的是一個泛型匿名對象集合,由於綁定操 作與查詢操作在同一個方法內,所以IDE會自動幫忙推斷var的對象類型。但如果要將查詢邏輯封裝在遠 程的WCF中,我們該用啥作為層之間交互的數據傳輸載體呢?List<???>,裡面的“???”該是啥呢 ?
以下是我嘗試過的幾種方案和走過的彎路。
1. 擴展默認實體定義
從上面的代碼中可以看到,我們需要返回的屬性信息主要來源於Customers實體,下面來嘗試下能否 在該實體的定義中直接附加字段
OrderDate和ProductName:
1: partial class Customers
2: {
3: public DateTime OrderDate {get;set;}
4: public string ProductName { get; set; }
5: }
然後這樣來寫查詢,看看能不能欺騙L2S來自動匹配這新增的兩個屬性:
1: public List<Customers> GetOrderInfo(string customerID)
2: {
3: using (NorthWindDataContext context = new NorthWindDataContext())
4: {
5: var query1 = from C in context.Customers
6: join O in context.Orders
7: on C.CustomerID equals O.CustomerID
8: join OD in context.Order_Details
9: on O.OrderID equals OD.OrderID
10: join P in context.Products
11: on OD.ProductID equals P.ProductID
12: where C.CustomerID == customerID
13: select
C
; //直接返回實體
14:
15: //或者這樣
16: var query2 = from C in context.Customers
17: join O in context.Orders
18: on C.CustomerID equals O.CustomerID
19: join OD in context.Order_Details
20: on O.OrderID equals OD.OrderID
21: join P in context.Products
22: on OD.ProductID equals P.ProductID
23: where C.CustomerID == customerID
24: select
new Customers
//顯示構造實體
構造實體
25: {
26: CustomerID = C.CustomerID,
27: CompanyName = C.CompanyName,
28: ContactName = C.ContactName,
29: Address = C.Address,
30: OrderDate = O.OrderDate,
31: ProductName = P.ProductName
32: };
33: return query1.ToList(); //query2.ToList()
34: }
35: }
很遺憾的是,query1查詢執行的結果,沒有取得我們需要的數據:
而query2也拋出了
NotSupportedException:不允許在查詢中顯式構造實體類型“TestLINQ.Customers”。
看來,這種方法行不通。
2. 使用Translate來返回自定義實體
在老趙的這篇文章中:《在LINQ to SQL中使用Translate方法以及修改查詢用SQL》,裡面提出了一 種方法來來砍掉那些不需要加載的信息,且可以繼續使用LINQ to SQL進行查詢。
這裡借鑒下裡面的思路,看看在增加屬性的情況下,結果會怎樣:
1: public List<Customers> GetOrderInfo(string customerID)
2: {
3: using (NorthWindDataContext context = new NorthWindDataContext())
4: {
5: var query3 = query0;
6: return context.ExecuteQuery<Customers>(query);
7: }
8: }
說明:
(1) 這裡的Customers類型定義,繼續用上一節中的對實體類的擴展;
(2) DataContext.ExcuteQuery<T>(IQuery query)方法,使用的老趙的DataContext擴展;
(3) 為 避免L2S查詢占用太多的版面,前面對每個查詢都進行了編號,query0, query1, query2….,下面如果 需要用到同樣的查詢時,直接引用前面的查詢,以節省版面和突出重點。
很遺憾的是,這次希望又落空了。
返回的結果中,OrderDate和ProductName依然為空。
老趙只提供了砍掉不需要的字段的方法,把增加字段的方法自己留著了/:)
另外補充一點,這裡對老趙提供的方法做了一點兒改進:如果調用OpenConnection時打開了新的連接 ,則需要在用完後關閉該連接,下面是代碼:
1: public static List<T> ExecuteQuery<T>(this DataContext dataContext, IQueryable query)
2: {
3: using (DbCommand command = dataContext.GetCommand(query))
4: {
5: bool openNewConnecion = false;
6: try
7: {
8: openNewConnecion = dataContext.OpenConnection();
9: using (DbDataReader reader = command.ExecuteReader())
10: {
11: return dataContext.Translate<T>(reader).ToList();
12: }
13: }
14: finally
15: {
16: if (openNewConnecion) //如 果打開了新的連接,則需要手動Close
17: dataContext.Connection.Close ();
18: }
19: }
20: }
21:
22: /// <summary>
23: /// 打開連接
24: /// </summary>
25: /// <param name="dataContext"></param>
26: /// <returns>是否打開了新的連接(這個返 回值可能容易讓人誤解,汗...)</returns>
27: private static bool OpenConnection (this DataContext dataContext)
28: {
29: if (dataContext.Connection.State == ConnectionState.Closed)
30: {
31: dataContext.Connection.Open ();
32: return true;
33: }
34: return false;
35: }
3. 執行TSQL
使用DataContext自帶的ExcuteQuery<T>方法:
1: public List<Customers> GetOrderInfo(string customerID)
2: {
3: using (NorthWindDataContext context = new NorthWindDataContext())
4: {
5: string sql = @"SELECT C.CustomerID, C.CompanyName, C.ContactName, C.[Address], O.OrderDate, P.ProductName
6: dbo.Customers AS C
7: dbo.Orders AS O
8: ON O.CustomerID = C.CustomerID
9: dbo.[Order Details] AS OD
10: ON OD.OrderID = O.OrderID
11: dbo.Products AS P
12: ON P.ProductID = OD.ProductID
13: E C.CustomerID={0}";
14: return context.ExecuteQuery<Customers>(sql, customerID).ToList();
15: }
16: }
結果跟第二節中的結果相同,又失敗了……
補充,MSDN上關於Translate和ExcuteQuery對查詢結果進行轉換的描述如下:
1. 使查詢結果中的列與對象中的字段和屬性相匹配的算法如下所示:
1.1 如果字段或屬性映射到特定列名稱,則結果集中應包含該列名稱。
1.2 如果未映射字段或屬性,則結果集中應包含其名稱與該字段或屬性相同的列。
1.3 通過先查找區分大小寫的匹配來執行比較。如果未找到匹配項,則會繼續搜索不區分大小寫的匹 配項。
2. 如果同時滿足下列所有條件,則該查詢應當返回(除延遲加載的對象外的)對象的所有跟蹤的字 段和屬性:
2.1 T 是由 DataContext 顯式跟蹤的實體。
2.2 ObjectTrackingEnabled 為 true。
2.3 實體具有主鍵。
否則會引發異常。
我愣是看了好多遍,還是沒有搞明白,為啥將結果集轉換到對象集合時L2S把我增加的字段給拋棄了 ……
4. 繼承默認實體定義
既然不讓我在L2S生成的默認實體上直接進行擴展,那我可以派生一個實體並添加我們需要的字段嗎 ?
1: public class
CustomerExt : Customers
2: {
3: public DateTime? OrderDate {get;set;}
4: public string ProductName { get; set; }
5: }
然後在業務邏輯層裡面這樣寫:
1: public List<
CustomerExt
> GetOrderInfo(string customerID)
2: {
3: using (NorthWindDataContext context = new NorthWindDataContext ())
4: {
5: var query4 = query0
6: return context.ExecuteQuery<
CustomerExt
>(query).ToList();
7: }
8: }
遺憾的是,程序執行到dataContext.Translate<T>(reader).ToList()時,又出錯了,拋出了 InvalidOperationException異常:
未處理 System.InvalidOperationException Message="類型為“TestLINQ.Customers”的數據成員“System.String CustomerID”不是類型 “CustomerExt”的映射的一部分。該成員是否位於繼承層次結構根節點的上方?" Source="System.Data.Linq" StackTrace: 在 System.Data.Linq.SqlClient.SqlBinder.Visitor.GetRequiredInheritanceDataMember (MetaType type, MemberInfo mi) 在 System.Data.Linq.SqlClient.SqlBinder.Visitor.AccessMember(SqlMember m, SqlExpression expo) 在 System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitMember(SqlMember m) 在 System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node) 在 System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitExpression(SqlExpression expr) 在 System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitNew(SqlNew sox) 在 System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node) 在 System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitExpression(SqlExpression expr) 在 System.Data.Linq.SqlClient.SqlVisitor.VisitUserQuery(SqlUserQuery suq) 在 System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitUserQuery(SqlUserQuery suq) 在 System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node) 在 System.Data.Linq.SqlClient.SqlBinder.Bind(SqlNode node) 在 System.Data.Linq.SqlClient.SqlProvider.BuildQuery(ResultShape resultShape, Type resultType, SqlNode node, ReadOnlyCollection`1 parentParameters, SqlNodeAnnotations annotations) 在 System.Data.Linq.SqlClient.SqlProvider.GetDefaultFactory(MetaType rowType) 在 System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.Translate(Type elementType, DbDataReader reader) 在 System.Data.Linq.DataContext.Translate(Type elementType, DbDataReader reader) 在 System.Data.Linq.DataContext.Translate[TResult](DbDataReader reader) 在 TestLINQ.DataContextExtensions.ExecuteQuery[T](DataContext dataContext, DbCommand command) 位置 D:\04.Other\WinForm\TestLINQ\DataContextExtensions.cs:行號 74 在 TestLINQ.DataContextExtensions.ExecuteQuery[T](DataContext dataContext, IQueryable query, Boolean withNoLock) 位置 D:\04.Other\WinForm\TestLINQ\DataContextExtensions.cs:行號 53 在 TestLINQ.DataContextExtensions.ExecuteQuery[T](DataContext dataContext, IQueryable query) 位置 D:\04.Other\WinForm\TestLINQ\DataContextExtensions.cs:行號 28 在 TestLINQ.Program.GetOrderInfo(String customerID) 位置 D:\04.Other\WinForm\TestLINQ\Class1.cs:行號 49 在 TestLINQ.Program.Main(String[] args) 位置 D:\04.Other\WinForm\TestLINQ\Class1.cs: 行號 21 在 System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args) 在 System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) 在 Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() 在 System.Threading.ThreadHelper.ThreadStart_Context(Object state) 在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) 在 System.Threading.ThreadHelper.ThreadStart() InnerException:
回過頭來看看L2S中的繼承,MSDN說法如下:
若要在 LINQ 中執行繼承映射,您必須在繼承層次結構的根類中指定屬性 (Attribute) 和屬性 (Attribute) 的屬性 (Property)。(FROM MSDN: 映射繼承層次結構 (LINQ to SQL))
看得我有點兒暈暈的....如果我不想修改L2S幫我生成的類型定義文件,則需要通過partial類對默認 生成的Customers進行擴展:擴展一個屬性作為鑒別器值?
好像挺繞的,我最終還是沒有嘗試成功… …
上面啰嗦了這麼多廢話,是我使用L2S過程中走過的一些彎路,列出來供大家參考,避免重蹈我的覆 轍。
5. 顯式自定義實體
在上面一節嘗試使用繼承時,查看錯誤堆棧信息,最後定位到GetRequiredInheritanceDataMember這 裡,這是在訪問基類成員時出錯了。於是我起了個邪惡的念頭,把基類拋棄掉,顯式再定義一個實體看 看:
1: public class
CustomerOrderDetial
2: {
3: public string CustomerID { get; set; }
4: public string CompanyName { get; set; }
5: public string ContactName { get; set; }
6: public string Address { get; set; }
7: public DateTime? OrderDate { get; set; }
8: public string ProductName { get; set; }
9: }
10:
11: public List<
CustomerOrderDetial
> GetOrderInfo(string customerID)
12: {
13: using (NorthWindDataContext context = new NorthWindDataContext())
14: {
15: var query5 = query0
16: return context.ExecuteQuery<
CustomerOrderDetial
>(query5).ToList();
17: }
18: }
這次運行通過了,而且得到了我們想要的結果,Congratulations!
但是,這樣操作的話,每次我們都要去手工編寫代碼,將我們需要的字段封裝 成一個實體類型。
結合上面第3節中的結論,我推測Translate和ExcuteQuery是按照下列邏輯來將結果集轉換成對象集 合的:
1: if(實體是由Table影射的實體)
2: {
3: 轉換時,只匹配標記為[Column]的 屬性
4: }
5: else //顯式自定義實體(參考下面第4節)
6: {
7: 轉換時,根據屬 性名與結果集中的列名進行匹配
8: }
6. 使用視圖/存儲過程/自定義函數
另一種方法是使用視圖、或存儲過程、或自定義函數,讓L2S設計器或者SqlMeta工具將視圖映射成實 體,或生成調用存儲過程和自定義函數的代碼。
可以參考MSDN:存儲過程 (LINQ to SQL)。使用自 定義函數過程與存儲過程差不錯,使用視圖的過程與表差不多,具體可以看MSDN中介紹,及L2S生成的源 代碼,這裡就不啰嗦了。
然而,視圖、存儲過程、自定義函數也不是萬金油。就拿本文的例子來說,我們的應用場景是“查詢 客戶什麼時間訂了哪些產品”,於是我們定義了一個視圖來關聯相關的四張表;但一個應用系統中,往 往會有很多場景;各種場景相互之間很相似,但又有不同,譬如“查詢客戶什麼時間訂了哪些公司生產 的哪些產品”、“查詢客戶什麼時間訂了 哪些雇員銷售的哪些產品”,我們又該怎麼處理呢?為每個場 景定制一個視圖?還是做一個“聰明 ”的大視圖,把所有關聯的表都join起來?
使用前者的結果可 能會是,試圖的數量呈爆炸式增長;
使用後者的結果可能會是:聰明反被聰明誤,性能不是一般地 差。
7. 自定義對象轉換器
前面的兩種方法雖然都可行,但用起來還是有點兒麻煩,能不能簡單一點兒呢?
在使用LINQ之前,我們經常使用Ado.Net從數據庫中取得一個數據集(DataSet或者DataTable),然後 再根據列名稱與對象的屬性名進行匹配,將數據集轉換成對象集合List<T>。在本節中,我將參考 這個思路,自定義一個對象轉換器。
LINQ中,有一個擴展方法IEnumerable.Cast<TResult>,實現了從IEnumerable到 IEnumerable<TResult>的轉換,裡面實現的是遍歷源集合,然後將裡面的元素進強制類型轉換 TResult類型,最後返回 IEnumerable<TResult>。但這裡,我們要實現的是,將IEnumerable< 匿名類型>轉換成 IEnumerable<命名類型>,使用該轉換器的代碼示例如下圖所示:
下面是執行結果(其中CustomerExt使用第4節中的實體定義,繼承自Customers):
使用起來還算比較清爽;當然,也有不足之處,性能怎樣是一個考慮點,還有就是如上面的運行結果 截圖,一些被我們坎掉的字段也會顯示出來;雖然這些額外字段的值都為空,但考慮下列情況:UI層取 得的結果是List<CustomerExt>,但他怎麼知道CustomerExt中哪些字段可以用,哪些字段被閹割 了呢?答案是:源代碼前面沒有秘密,只有看底層的源代碼了-.-
下面來看下這個對象轉換器的源代碼:
1: public static class ObjectConverter
2: {
3: private class CommonProperty
4: {
5: public PropertyInfo SourceProperty { get; set; }
6: public PropertyInfo TargetProperty { get; set; }
7: }
8:
9: public static
List<TResult>
ConvertTo<TResult>(this IEnumerable source)
10: where TResult : new()
11: {
12: if (source == null) //啥都不用干
13: return null;
14:
15: if (source is IEnumerable<TResult>)
16: return source.Cast<TResult>().ToList();//源類型於目標類型一致,可以直接轉換
17:
18: List<TResult> result = new List<TResult>();
19: bool hasGetElementType = false;
20: IEnumerable<CommonProperty> commonProperties = null; //公共屬性(按屬性名稱進行匹配)
21:
22: foreach (var s in source)
23: {
24: if (
!hasGetElementType
) //訪問第一個元素時,取得屬性對應關系;後續的元素就不用再重新計算了
25: {
26: if (s is TResult) //如果源類型是目標類型的子類,可以直接 Cast<T>擴展方法
27: {
28:
return source.Cast<TResult>().ToList();
29: }
30: commonProperties = GetCommonProperties(s.GetType(), typeof(TResult));
31: hasGetElementType = true;
32: }
33:
34: TResult t = new TResult();
35: foreach (CommonProperty commonProperty in commonProperties) //逐個屬性拷貝
36: {
37: object value = commonProperty.SourceProperty.GetValue(s, null);
38: commonProperty.TargetProperty.SetValue(t, value, null);
39: }
40: result.Add(t);
41: }
42:
43:
return result;
44: }
45:
46: private static IEnumerable<CommonProperty> GetCommonProperties(Type sourceType, Type targetType)
47: {
48: PropertyInfo[] sourceTypeProperties = sourceType.GetProperties();//獲取源對象所有屬性
49: PropertyInfo[] targetTypeProperties = targetType.GetProperties(); //獲 取目標對象所有屬性
50: return from SP in sourceTypeProperties
51: join TP in targetTypeProperties
52: on SP.Name.ToLower () equals TP.Name.ToLower() //根據屬性名進行對應(不區分大小寫)
53: select new CommonProperty
54: {
55: SourceProperty = SP,
56: TargetProperty = TP
57: };
58: }
59: }
源代碼前沒有秘密,裡面就是實現了最簡單的轉換:將源對象集合中的元素逐個轉換成目標對象。
關於這段代碼的一點補充說明(下面的源類型和目標類型,是指泛型中的T,而不是 IEnumerable<T>):
(1).如果源類型於目標類型一致,或者源類型是目標類型的子類,則可以不用逐個元素遍歷了,直接 調用IEnumerable的擴展方法Cast<T>()即可;
用Reflector看了下其源代碼實現,裡面比較繞,不知道性能咋樣,暫時不管了,用著先,而且這樣 很省事兒。
另外List<T>也提供了一個ConvertAll<TOutput>(Converter<T, TOutput> converter)方法,可以自己定義一個對象轉換器方法,然後傳給Converter<T, TOutput>委托;但這裡用不上該方法,原因如下:
a. 看其源代碼實現,可以發現其就是遍歷集 合循環執行Converter委托,這樣不便於進行優化(參考下面的第(2)點);
b. 雖然我可以實現一個 Converter<T, TOutput>,但在外面該怎樣調用呢?因為query的類型是IQueryable<匿名類型 >,所以在調用時,我們根本不知道該傳啥進去。
(2). 如果不滿足(1),則需要逐個元素進行轉換。由於在進入foreach(上面代碼的第22行)之前,還 不知道源類型是什麼類型,因此將GetCommonProperties方法放到循環中;但如果源集合中有100個元素 ,而循環中每次都來執行這個方法,合計執行100次,這樣會顯得很傻X,因此裡面加了點控制,只在處 理第一個元素時調用該方法,然後將屬性匹配結果緩存下來(使用局部變量commonProperties進行緩存) ,從而避免每次都做無用功。
(3). 執行返回的結果時List<TResult>,也即是執行此方法時,如果傳進來的是 IQueryable<T>,則會立即進行計算。
(4). 這裡面還有繼續優化的余地:如果有100個用戶同時在執行這個查詢請求,則每個請求裡面都在 進行執行 GetCommonProperties函數,然後各自進行著反射(取得“特定匿名類型”和CustomerExt類型 的屬性集合)和屬性匹配(取得“特定匿名類型”和CustomerExt類型的公共屬性)運算,這樣又會顯得傻 X了。對於一個普通的已經部署完畢的應用系統,其中的實體類型定義是恆定的(不考慮動態編譯的情況 ;對於匿名類型,在編譯時,編譯器會為其創建類型定義),而且類型之間的轉換關系也是恆定的,因此 我們可以這些信息緩存下來,避免每次請求都執行重復計算。下面是一個最簡單的屬性緩存器,采用靜 態變量來保存計算過的信息,直接替換上面的GetCommonProperties方法即可:
1: private static class PropertyCache
2: {
3: private static object syncProperty = new object();
4: private static object syncCommon = new object();
5:
6: private static Dictionary<Type, PropertyInfo[]> PropertyDictionary =
7: new Dictionary<Type, PropertyInfo[]>(); //緩 存類型的PropertyInfo數組
8: private static Dictionary<string, IEnumerable<CommonProperty>> CommonPropertyDictionary =
9: new Dictionary<string, IEnumerable<CommonProperty>>(); //緩存兩種類型的公共屬性對 應關系
10:
11: private static PropertyInfo[] GetPropertyInfoArray(Type type)
12: {
13: if (!PropertyCache.PropertyDictionary.ContainsKey (type))
14: {
15: lock (syncProperty)
16: {
17: if (!PropertyCache.PropertyDictionary.ContainsKey(type)) //雙重 檢查
18: {
19: PropertyInfo[] properties = type.GetProperties();
20: PropertyCache.PropertyDictionary.Add (type, properties); //Type是單例的(Singleton),可以直接作為Key
21: }
22: }
23: }
24: return PropertyCache.PropertyDictionary[type];
25: }
26:
27: public static IEnumerable<CommonProperty> GetCommonProperties(Type sourceType, Type targetType)
28: {
29: string key = sourceType.ToString() + targetType.ToString();
30: if (! PropertyCache.CommonPropertyDictionary.ContainsKey(key))
31: {
32: lock (syncCommon)
33: {
34: if (! PropertyCache.CommonPropertyDictionary.ContainsKey(key)) //雙重檢查
35: {
36: PropertyInfo[] sourceTypeProperties = GetPropertyInfoArray(sourceType);//獲取源對象所有屬性
37: PropertyInfo[] targetTypeProperties = GetPropertyInfoArray(targetType);//獲取目標對象所 有屬性
38: IEnumerable<CommonProperty> commonProperties = from SP in sourceTypeProperties
39: join TP in targetTypeProperties
40: on SP.Name.ToLower() equals TP.Name.ToLower()
41: select new CommonProperty
42: {
43: SourceProperty = SP,
44: TargetProperty = TP
45: };
46: PropertyCache.CommonPropertyDictionary.Add(key, commonProperties);
47: }
48: }
49: }
50: return PropertyCache.CommonPropertyDictionary[key];
51: }
52: }
8. Something Others
上面第7節中,看起來好像解決了文章標題所提出的問題,但這種方式也可能是個陷阱。
其中使用了CustomerExt,其繼承自L2S生成的默認實體Customers,這樣帶來的一個好處就是可以復 用Customers中的屬性定義,而不必像第5節中一樣,重新定義一套。但是從繼承的語義上來講,繼承體 現的是一種IS-A的關系,因此套用過來的話就是這樣:“客戶什麼時間訂購哪些商品”是一個“客戶” !???這是啥?幼兒園沒畢業吧?打回去重讀……
在某些場景下,我們可以應用繼承,譬如NorthWind數據庫中有張表dbo.Contacts記錄用戶的聯系信 息,則我們可以對Customer或者 Employee進行擴展,添加聯系信息;而對於本文所舉的這個例子,繼承 是被濫用了。當然,本文的重點是Linq to Sql,而不是OO,因此,這裡就請各位看官不要追究我的錯誤 了………我先原諒我自己,願主也原諒我吧,阿彌陀佛。
為了將功補過,這裡引入一點Entity Framework的東西,下面這個截圖來自《Linq in Action》:
在Linq to Sql中,我們只能將表或者視圖影射成實體定義,且這種影射是1對1影射。從上圖可以看 到,在EF中,可以建立一個概念模型,將多個表影射到一個實體定義;於是,整個世界清靜了……
我也只是撇了一眼,還沒有用過EF,不知道自己理解的對不對;這裡只是做個引子,有興趣的話,各 位可以自己研究研究,記得把研究結果分享給我/:)
最有來個總結(由於個人認知的局限性,這些結論可能不一定正確):
可行性 缺點 擴展默認實體定義 否 -- 使用Translate來返回自定義實體 否 -- 執行TSQL返回自定義實體 否 -- 繼承默認實體定義 否 -- 顯式自定義實體 是 麻煩,要自己Code,定義新的實體類型 使用視圖/存儲過程/自定義函數 是 不夠靈活,無法為每個應用場景都去訂制視圖 自定義對象轉換器 是 繼承關系可能會被濫用;返回的實體集合是個黑盒子,上層可能不知道實體的哪些屬性可用 ,哪些不可用 Entity Framework 貌似可行 --
出處:http://happyhippy.cnblogs.com/