如果允許在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.CustomerID6: join OD in context.Order_Details
7: on O.OrderID equals OD.OrderID8: join P in context.Products
9: on OD.ProductID equals P.ProductID10: 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.CustomerID8: join OD in context.Order_Details
9: on O.OrderID equals OD.OrderID10: join P in context.Products
11: on OD.ProductID equals P.ProductID12: 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.CustomerID19: join OD in context.Order_Details
20: on O.OrderID equals OD.OrderID21: join P in context.Products
22: on OD.ProductID equals P.ProductID23: 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對查詢結果進行轉換的描述如下:
否則會引發異常。 |
我愣是看了好多遍,還是沒有搞明白,為啥將結果集轉換到對象集合時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 = query06: 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過程中走過的一些彎路,列出來供大家參考,避免重蹈我的覆轍。
---------------------------------------------------------------------------------------------------------------
--------------------------我是華麗的分割線(happyhippy.cnblogs.com)-------------------------------
---------------------------------------------------------------------------------------------------------------
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 = query016: 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 | 貌似可行 | -- |
浙公網安備 33010602011771號