[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.csprivate 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();
}
結果:
TSQL
TSQL
明確載入和延遲載入都可以延遲相關物件資料的要求,直到確實需要該資料才會載入相關連物件。
這樣可以產生較不複雜的查詢,而且所傳回的總資料量也較少。缺點就是後續查詢都要連接資料來源,
若使用延遲載入,每當存取導覽屬性且尚未載入相關實體時,就會連接資料來源(相關延遲載入考量可參考MSDN)。
TSQL
可以看到積極式載入所產生的查詢相當複雜,而且會回傳大量資料(所有相關聯實體),雖然只連接資料庫一次。
但如果查詢路徑包含太多相關物件,或是物件包含太多資料列資料,可能會發生查詢逾時情況,
這時可以考慮採取取明確載入相關物件,降低查詢複雜性。
最後,當要查詢關聯資料時,建議針對連接資料來源次數和返回資料量來考慮使用何種載入模式。
參考