[C#][EF]淺談資料載入模式

[C#][EF]淺談資料載入模式

之前我們大概聊了EDM是什麼東東,而這篇再來談談Entity Framework資料載入模式,

了解Entity Framework載入模式個人認為是相當重要的一個環節,

因為在查詢關聯資料時,這關係你的應用程式存取資料庫的次數和返回的資料量,

當然也影響者查詢效能。

 

資料載入模式有三種

明確式載入(Explicit loading):

該模式可以確保程式未明確要求相關聯實體的情況下,

絕對不會執行關聯實體查詢作業(等到確實需要該關聯實體才會載入),

對大多情況來說也許是比較好的方法,同時這也是Entity Framework 的預設行為模式,

但如果您使用 Load 方法,在集合的反覆運算期間明確載入相關聯實體,

這將會導致有多個查詢進行資料庫的存取,因為每一個 Load 呼叫各會有一個查詢(連接資料來源)。

 

延遲載入(Lazy loading):

當查詢傳回物件時,相關聯物件不會同時載入,

但存取導覽屬性時會從資料來源自動載入相關實體物件(會多次存取store queries),

而且只會載入必要資料。

在 Entity Framework 執行階段中,ObjectContext 執行個體中之 LazyLoadingEnabled 屬性的預設值是 false。

不過,如果您使用 Entity Framework 工具建立新的模型和對應的產生類別,

產生的程式碼會在產生之物件內容的建構函式中,將 LazyLoadingEnabled 設定為 true。

 

積極式載入或使用 Include 定義查詢路徑(Eager loading or Defining Query Paths with Include):

和延遲載入剛好相反,在載入特定的相關物件組之程式,會連同查詢中所明確要求的實體物件一起載入,

並且會在初始查詢中返回大量資料,也會產生比較複雜的查詢,雖然只連接資料來源一次。 

 

 

接下來我們來看看這三種模式所產生的查詢陳述式(Product和WorkOrder)

 

TableProduct.cs
 private static readonly Func<AdventureWorksEntities, int, IQueryable<Product>> _CompiledQuery =
    CompiledQuery.Compile<AdventureWorksEntities, int, IQueryable<Product>>(
   ( db, productid ) => from c in db.Product
                      where c.ProductID == productid
                      select c);
 
        private static readonly Func<AdventureWorksEntities, int, IQueryable<Product>> _CompiledQuery2 =
  CompiledQuery.Compile<AdventureWorksEntities, int, IQueryable<Product>>(
 ( db, productid ) => from c in db.Product.Include( "WorkOrder" )
                      where c.ProductID == productid
                      select c );    
       
 
        public string GetWorkOrderIDbyExplicit( int productid )
        {
            using( AdventureWorksEntities db = new AdventureWorksEntities() )
            {
                try
                {
                    //Set LazyLoadingEnabled to false. 
                    db.ContextOptions.LazyLoadingEnabled = false;
                    db.Product.MergeOption = MergeOption.NoTracking;
                    StringBuilder sb = new StringBuilder();          
                    var Product = _CompiledQuery( db, productid ).FirstOrDefault();
                    // If lazy loading is not enabled no workorder will be loaded for the Myproduct.
                    //使用 Load 方法載入關聯實體 明確式載入  
                    if( Product != null )
                        Product.WorkOrder.Load();
                    foreach( var order in Product.WorkOrder )
                    {
                        sb.Append( order.WorkOrderID.ToString() );
                        sb.Append( "," );
                    }     
 
                    return string.Format( "ProductId: {0} ,Name: {1} ,WorkOrderID: {2} ",
                     Product.ProductID.ToString(), Product.Name.ToString(), sb.ToString() );
                }
                catch( Exception ex )
                {
                    return ex.Message;
                }
            } 
        }
 
        public string GetWorkOrderIDbyLazy( int productid )
        {
            using( AdventureWorksEntities db = new AdventureWorksEntities() )
            {
                try
                {
                    // Set LazyLoadingEnabled to true. (default  延遲載入)
                    db.ContextOptions.LazyLoadingEnabled = true;
                    db.Product.MergeOption = MergeOption.NoTracking;
                    StringBuilder sb = new StringBuilder();
                    var Product = _CompiledQuery( db, productid ).FirstOrDefault();
 
                    foreach( var order in Product.WorkOrder )
                    {
                        sb.Append( order.WorkOrderID.ToString() );
                        sb.Append( "," );
                    }
                   
                    return string.Format( "ProductId: {0} ,Name: {1} ,WorkOrderID: {2} ",
                         Product.ProductID.ToString(), Product.Name.ToString(), sb.ToString() );
                }
                catch( Exception ex )
                {
                    return ex.Message;
                }
            }
        }
 
        public string GetWorkOrderIDbyEager( int productid )
        {
            using( AdventureWorksEntities db = new AdventureWorksEntities() )
            {
                try
                {
                    // Set LazyLoadingEnabled to false. 
                    db.ContextOptions.LazyLoadingEnabled =false;
                    db.Product.MergeOption = MergeOption.NoTracking;
                    StringBuilder sb = new StringBuilder();
                    var Product = _CompiledQuery2( db, productid ).FirstOrDefault();//使用 Include 積極式載入
 
                    foreach( var order in Product.WorkOrder )
                    {
                        sb.Append( order.WorkOrderID.ToString() );
                        sb.Append( "," );
                    }
 
                    return string.Format( "ProductId: {0} ,Name: {1} ,WorkOrderID: {2} ",
                    Product.ProductID.ToString(), Product.Name.ToString(), sb.ToString() );
                }
                catch( Exception ex )
                {
                    return ex.Message;
                }
            }
        }

 

TestControl.cs

  #region Test DataLoad
 
        public string ExecLazy( int productid )
        {
            StringBuilder sb = new StringBuilder();
            Stopwatch sw = new Stopwatch();
            string Result=string.Empty;
            try
            {
                sw.Reset();
                sw.Start();
                TableProduct tabproduct = new TableProduct();
                Result=tabproduct.GetWorkOrderIDbyLazy( productid);
                sw.Stop();
                sb.AppendLine( string.Format( "花費時間(ms): {0} , 結果: {1}",
                    sw.ElapsedMilliseconds.ToString(), Result ) );
                return sb.ToString();
            }
            catch( Exception ex )
            {
                return ex.Message;
            }             
        }
 
        public string ExecExplicit( int productid )
        {
            StringBuilder sb = new StringBuilder();
            Stopwatch sw = new Stopwatch();
            string Result = string.Empty;
            try
            {
                sw.Reset();
                sw.Start();
                TableProduct tabproduct = new TableProduct();
                Result = tabproduct.GetWorkOrderIDbyExplicit( productid);
                sw.Stop();
                sb.AppendLine( string.Format( "花費時間(ms): {0} , 結果: {1}",
                    sw.ElapsedMilliseconds.ToString(), Result ) );
                return sb.ToString();
            }
            catch( Exception ex )
            {
                return ex.Message;
            }   
        }
 
        public string ExecEager( int productid )
        {
 
            StringBuilder sb = new StringBuilder();
            Stopwatch sw = new Stopwatch();
            string Result = string.Empty;
            try
            {
                sw.Reset();
                sw.Start();
                TableProduct tabproduct = new TableProduct();
                Result = tabproduct.GetWorkOrderIDbyEager( productid);
                sw.Stop();
                sb.AppendLine( string.Format( "花費時間(ms): {0} , 結果: {1}",
                    sw.ElapsedMilliseconds.ToString(), Result ) );
                return sb.ToString();
            }
            catch( Exception ex )
            {
                return ex.Message;
            }   
        }
 
        #endregion

 

DataLoad.aspx.cs

 protected void Button1_Click( object sender, EventArgs e )
        {
            int[] products = new int[ 5 ] {722,316,733,951,3 };
            TestControl testcontrol = new TestControl();
            StringBuilder sb = new StringBuilder();
            sb.AppendLine( string.Format( "方法: {0} ", DropDownList1.SelectedItem.Text ) );   
            switch( DropDownList1.SelectedIndex )
            {                  
                case 0:  
                    sb.AppendLine( testcontrol.ExecExplicit(722) );                  
                    break;
                case 1:
                    sb.AppendLine( testcontrol.ExecLazy( 722 ) ); 
                    break;
                case 2:
                    sb.AppendLine( testcontrol.ExecEager( 722 ) );  
                    break;
                default:
                    sb.AppendLine( "沒有執行!" );
                    break;
            }     
 
            TextBox1.Text = sb.ToString();
        }

 

  

 

 

 

結果:  

 

image

 

TSQL

image 

 

 

 

 

TSQL

image 

明確載入和延遲載入都可以延遲相關物件資料的要求,直到確實需要該資料才會載入相關連物件。

這樣可以產生較不複雜的查詢,而且所傳回的總資料量也較少。缺點就是後續查詢都要連接資料來源,

若使用延遲載入,每當存取導覽屬性且尚未載入相關實體時,就會連接資料來源(相關延遲載入考量可參考MSDN)。

 

 

image

 

TSQL

image

image

可以看到積極式載入所產生的查詢相當複雜,而且會回傳大量資料(所有相關聯實體),雖然只連接資料庫一次。

但如果查詢路徑包含太多相關物件,或是物件包含太多資料列資料,可能會發生查詢逾時情況,

這時可以考慮採取取明確載入相關物件,降低查詢複雜性。

 

最後,當要查詢關聯資料時,建議針對連接資料來源次數和返回資料量來考慮使用何種載入模式。

  

參考

載入相關的物件 (Entity Framework)  

查詢執行