2012年12月23日 星期日

觀察 ADO.NET Entity Framework 5.0 產生的 SQL Command 與取得 Entity 對應的 Table Name

這篇的文章標題蠻長的,其實這與以前的兩篇文章是有關連的,

觀察 Entity Framework 轉換所產出的 SQL Command

動態取得 Entity Framework 中 Entity 對應的 TableName

這兩篇文章都是以使用 ADO.NET Entity Framework 4.0 以及 .NET 4.0 為背景,不過換成 ADO.NET Entity Framework 5.0 與 .NET 4.5 的使用情境下就會有所不同了,那兩篇文章內所說的方法就行不通,所以就必須要換另外的方式來完成需求。


觀察 ADO.NET Entity Framework 5.0 所產生的 SQL Command

在「觀察 Entity Framework 轉換所產出的 SQL Command」這篇文章當中有說到,我們要觀察 EF 所轉換產生的 SQL Command 時,可以對 ObjectQuert<T> 使用 ToTraceString() 來取得,但是當專案使用了 EF 5 以及 .NET 4.5 之後卻無法使用 ToTraceString() 來取得 SQL Command 了……

image

這是因為我們所處理的資料已經不再是 ObjectQuery<T> 而是 DbQuery<T>,所以無法使用 ToTraceString() 方法,就在我遍尋不著可以把 DbQuery<T> 轉換成 ObjectQuery<T> 之際,我所幸就直接把 DbQuery<T> 使用 ToString() 方法,看看會輸出什麼樣的內容時,居然給了我這樣的結果,

image

是的,直接吐出了 SQL Command 給我(操作的資料庫為 Oracle XE,所以顯示的 SQL Command 內容會與 MS SQL Server 的 T-SQL 有所不同),這倒底是怎麼回事呢?

於是我先前往 MSDN,看看有什麼資訊,不過得到的結果並沒有一個明確詳細的資料,

http://msdn.microsoft.com/zh-tw/library/system.data.entity.infrastructure.dbquery.tostring(v=vs.103).aspx

image

最後出動了 Telerik JustDecompile 來查看 EntityFramework 5.0 的原始碼內容,在找到 DbQuery 的原始碼後,看到 ToString() 方法內的程式是這樣寫的,

image

再追到 System.Data.Entity.Internal.Linq 的 InternalQuery,可以看到 ToString() 方法裡還是使用 ObjectQuery 的 ToTraceString() 方法,

image

這也說明了為何 DbQuery<T> 可以直接使用 ToString() 就能取得 SQL Command 的原因了。

 

 

取得 Entity 對應的 Table Name

在「動態取得 Entity Framework 中 Entity 對應的 TableName」這篇文章裡,我實作了一個方法用來取得 Entity 對應到資料庫的 Table Name,

public class ContextExtensions
    {
        #region -- GetTableName --
        /// <summary>
        /// Gets the name of the table.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="context">The context.</param>
        /// <param name="dbType">Type of the db.</param>
        /// <returns></returns>
        public static string GetTableName<T>(ObjectContext context, DataBaseType dbType = DataBaseType.MS_SQL_SERVER) where T : class
        {
            string sql = context.CreateObjectSet<T>().ToTraceString();
            string matchWords = string.Empty;
            switch (dbType)
            {
                case DataBaseType.MS_SQL_SERVER:
                    matchWords = "FROM (?<table>.*) AS";
                    break;
                case DataBaseType.Oracle:
                    matchWords = "FROM \"(?<schema>.*)\".\"(?<table>.*)\"\\s";
                    break;
            }
            Regex regex = new Regex(matchWords);
            Match match = regex.Match(sql);
            string table = match.Groups["table"].Value;
            return table;
        }
        #endregion
        public enum DataBaseType
        {
            MS_SQL_SERVER = 0,
            Oracle = 1
        }

同樣的在 EF 5.0 與 .NET 4.5 的情況下也出現了問題,問題就出現在 DbContext 並沒有 CreateObjectSet<T>() 方法,所以也就沒有 ToTraceString() 方法可以取得 SQL Command,後來在 StackOverflow 裡找到了這篇「How to convert DbSet in Entity framework to ObjectQuery」,問題的內容與我所遇到的情況是一致的,而下方所標示的解答內容為,

image

不過我還是到 MSDN 看看 DbContext 類別的定義,

http://msdn.microsoft.com/en-us/library/system.data.entity.dbcontext(v=vs.103).aspx

image

DbContext 類別是有繼承實作 IObjectContextAdapter 介面,而 IObjectContextAdapter 介面裡只有一個 Property,這個 Property 類別就是 ObjectContext,

http://msdn.microsoft.com/en-us/library/system.data.entity.infrastructure.iobjectcontextadapter(v=vs.103).aspx

image

所以程式就做了以下的修改,如此一來就可以由 DbContext 轉換為 ObjectContext,再進而使用 ObjectContext 執行 CreateObjectSet<T>() 方法來取得 ObjectSet<T> 然後轉出 SQL Command 並取得 Table Name,

image

最後方法就修改為以下的內容:

#region -- GetTableName --
/// <summary>
/// Gets the name of the table.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="context">The context.</param>
/// <param name="dbType">Type of the db.</param>
/// <returns></returns>
public static string GetTableName<T>(DbContext context, DataBaseType dbType = DataBaseType.Oracle) where T : class
{
    ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;
    ObjectSet<T> objectSet = objectContext.CreateObjectSet<T>();
 
    string sql = objectSet.ToTraceString();
 
    string matchWords = string.Empty;
 
    switch (dbType)
    {
        case DataBaseType.MS_SQL_SERVER:
            matchWords = "FROM (?<table>.*) AS";
            break;
 
        case DataBaseType.Oracle:
            matchWords = "FROM \"(?<schema>.*)\".\"(?<table>.*)\"\\s";
            break;
    }
 
    Regex regex = new Regex(matchWords);
    Match match = regex.Match(sql);
 
    string table = match.Groups["table"].Value;
    return table;
}
#endregion

 


話說這篇文章快寫完之前在 Google 搜尋相關資料時,赫然發現到原來中國那邊已經有人介紹過這兩個部分了,

LingzhiSun's Blog

Entity Framework 小技巧一 —— 如何从DbContext得到其内部封装的ObjectContext

Entity Framework 小技巧五 —— 如何得到EF查询生成的SQL?

嗯 …… 不過文章既然都寫了,還是發佈出去吧!就當做是一篇記錄,日後我忘記時還有個地方可以查看。

 

以上

3 則留言:

  1. 請問
    ToString()可以直接產生SQL command
    為什麼不直接用string sql = context.Set.ToString();
    然後直接Regex

    回覆刪除
    回覆
    1. 這篇文章是五年多前所寫的,我巳經不太記得當時的情況

      刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。