程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> Linq to Sql:N層應用中的查詢(上) : 返回自定義實體

Linq to Sql:N層應用中的查詢(上) : 返回自定義實體

編輯:關於.NET

如果允許在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/

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved