本文目錄:html
2、EF中的代理模式探祕設計模式
(1)經過選擇以XML方式打開edmx文件,咱們能夠能夠清楚地看到,edmx模型文件本質就是一個XML文件;框架
(2)能夠清楚地看到,edmx模型文件是一個XML文件,其中定義了三大組成部分,這三大組成部分構成了所謂的ORM(對象關係映射);ide
(3)再經過解決方案管理器分析edmx模型文件,其包含了三個子文件:優化
①第一個是xxx.Context.tt,這個首先是一個T4的模板文件,它生成了咱們這個模型的上下文類;ui
public partial class LearnEntities : DbContext { public LearnEntities() : base("name=LearnEntities") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { throw new UnintentionalCodeFirstException(); } public DbSet<T_Class> T_Class { get; set; } public DbSet<T_Message> T_Message { get; set; } public DbSet<T_Person> T_Person { get; set; } }
②第二個是設計器部分,它定義了模型關係圖的元數據,好比每一個類圖的寬度多少,在圖中的座標(X、Y軸)等;this
<edmx:Diagrams> <Diagram DiagramId="edad56670ede49c5ae2e343c9da730f1" Name="關係圖1"> <EntityTypeShape EntityType="LearnModel.T_Class" Width="1.5" PointX="0.75" PointY="1.75" /> <EntityTypeShape EntityType="LearnModel.T_Message" Width="1.5" PointX="5.25" PointY="1" /> <EntityTypeShape EntityType="LearnModel.T_Person" Width="1.5" PointX="3" PointY="1.25" /> <AssociationConnector Association="LearnModel.FK_T_Person_T_Class" /> <AssociationConnector Association="LearnModel.FK_T_Message_T_Person1" /> <AssociationConnector Association="LearnModel.FK_T_Message_T_Person2" /> </Diagram> </edmx:Diagrams>
③第三個就是數據庫表中所對應的實體類對象,它也是一個T4模板文件,對應了全部選擇的數據庫表:spa
public partial class T_Class { public T_Class() { this.T_Person = new HashSet<T_Person>(); } public int Id { get; set; } public string Name { get; set; } public virtual ICollection<T_Person> T_Person { get; set; } }
(1)SSDL
它定義了數據庫中所對應的表的定義,也能夠稱爲存儲模型:
<edmx:StorageModels> <Schema Namespace="LearnModel.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2008" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2009/02/edm/ssdl"> <EntityContainer Name="LearnModelStoreContainer"> <EntitySet Name="T_Class" EntityType="LearnModel.Store.T_Class" store:Type="Tables" Schema="dbo" /> <EntitySet Name="T_Message" EntityType="LearnModel.Store.T_Message" store:Type="Tables" Schema="dbo" /> <EntitySet Name="T_Person" EntityType="LearnModel.Store.T_Person" store:Type="Tables" Schema="dbo" /> <AssociationSet Name="FK_T_Message_T_Person1" Association="LearnModel.Store.FK_T_Message_T_Person1"> <End Role="T_Person" EntitySet="T_Person" /> <End Role="T_Message" EntitySet="T_Message" /> </AssociationSet> <AssociationSet Name="FK_T_Message_T_Person2" Association="LearnModel.Store.FK_T_Message_T_Person2"> <End Role="T_Person" EntitySet="T_Person" /> <End Role="T_Message" EntitySet="T_Message" /> </AssociationSet> <AssociationSet Name="FK_T_Person_T_Class" Association="LearnModel.Store.FK_T_Person_T_Class"> <End Role="T_Class" EntitySet="T_Class" /> <End Role="T_Person" EntitySet="T_Person" /> </AssociationSet> </EntityContainer> <EntityType Name="T_Class"> <Key> <PropertyRef Name="Id" /> </Key> <Property Name="Id" Type="int" Nullable="false" StoreGeneratedPattern="Identity" /> <Property Name="Name" Type="nvarchar" MaxLength="100" /> </EntityType> <EntityType Name="T_Message"> <Key> <PropertyRef Name="Id" /> </Key> <Property Name="Id" Type="int" Nullable="false" StoreGeneratedPattern="Identity" /> <Property Name="Title" Type="nvarchar" Nullable="false" MaxLength="250" /> <Property Name="Message" Type="nvarchar(max)" Nullable="false" /> <Property Name="NickName" Type="nvarchar" Nullable="false" MaxLength="50" /> <Property Name="IsAnonymous" Type="bit" Nullable="false" /> <Property Name="IPAddress" Type="nvarchar" Nullable="false" MaxLength="50" /> <Property Name="PostDate" Type="datetime" Nullable="false" /> <Property Name="PostManId" Type="int" Nullable="false" /> <Property Name="ReceiveManId" Type="int" Nullable="false" /> </EntityType> <EntityType Name="T_Person"> <Key> <PropertyRef Name="Id" /> </Key> <Property Name="Id" Type="int" Nullable="false" StoreGeneratedPattern="Identity" /> <Property Name="Name" Type="nvarchar" Nullable="false" MaxLength="50" /> <Property Name="Age" Type="int" Nullable="false" /> <Property Name="Email" Type="nvarchar" Nullable="false" MaxLength="100" /> <Property Name="ClassId" Type="int" Nullable="false" /> </EntityType> <Association Name="FK_T_Message_T_Person1"> <End Role="T_Person" Type="LearnModel.Store.T_Person" Multiplicity="1" /> <End Role="T_Message" Type="LearnModel.Store.T_Message" Multiplicity="*" /> <ReferentialConstraint> <Principal Role="T_Person"> <PropertyRef Name="Id" /> </Principal> <Dependent Role="T_Message"> <PropertyRef Name="PostManId" /> </Dependent> </ReferentialConstraint> </Association> <Association Name="FK_T_Message_T_Person2"> <End Role="T_Person" Type="LearnModel.Store.T_Person" Multiplicity="1" /> <End Role="T_Message" Type="LearnModel.Store.T_Message" Multiplicity="*" /> <ReferentialConstraint> <Principal Role="T_Person"> <PropertyRef Name="Id" /> </Principal> <Dependent Role="T_Message"> <PropertyRef Name="ReceiveManId" /> </Dependent> </ReferentialConstraint> </Association> <Association Name="FK_T_Person_T_Class"> <End Role="T_Class" Type="LearnModel.Store.T_Class" Multiplicity="1" /> <End Role="T_Person" Type="LearnModel.Store.T_Person" Multiplicity="*" /> <ReferentialConstraint> <Principal Role="T_Class"> <PropertyRef Name="Id" /> </Principal> <Dependent Role="T_Person"> <PropertyRef Name="ClassId" /> </Dependent> </ReferentialConstraint> </Association> </Schema> </edmx:StorageModels>
例如,咱們能夠經過下面的一個代碼片斷來看看它在說明什麼?
<EntityType Name="T_Person"> <Key> <PropertyRef Name="Id" /> </Key> <Property Name="Id" Type="int" Nullable="false" StoreGeneratedPattern="Identity" /> <Property Name="Name" Type="nvarchar" Nullable="false" MaxLength="50" /> <Property Name="Age" Type="int" Nullable="false" /> <Property Name="Email" Type="nvarchar" Nullable="false" MaxLength="100" /> <Property Name="ClassId" Type="int" Nullable="false" /> </EntityType>
是否是跟咱們在MSSQL中所進行的數據表設計差很少?指定主鍵、指定字段的類型、是否爲NULL,最大長度等等;
(2)CSDL
它定義了EF模型中與SSDL對應的實體類對象的定義,這裏C表明Concept,即概念模型;
<edmx:ConceptualModels> <Schema Namespace="LearnModel" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns="http://schemas.microsoft.com/ado/2008/09/edm"> <EntityContainer Name="LearnEntities" annotation:LazyLoadingEnabled="true"> <EntitySet Name="T_Class" EntityType="LearnModel.T_Class" /> <EntitySet Name="T_Message" EntityType="LearnModel.T_Message" /> <EntitySet Name="T_Person" EntityType="LearnModel.T_Person" /> <AssociationSet Name="FK_T_Person_T_Class" Association="LearnModel.FK_T_Person_T_Class"> <End Role="T_Class" EntitySet="T_Class" /> <End Role="T_Person" EntitySet="T_Person" /> </AssociationSet> <AssociationSet Name="FK_T_Message_T_Person1" Association="LearnModel.FK_T_Message_T_Person1"> <End Role="T_Person" EntitySet="T_Person" /> <End Role="T_Message" EntitySet="T_Message" /> </AssociationSet> <AssociationSet Name="FK_T_Message_T_Person2" Association="LearnModel.FK_T_Message_T_Person2"> <End Role="T_Person" EntitySet="T_Person" /> <End Role="T_Message" EntitySet="T_Message" /> </AssociationSet> </EntityContainer> <EntityType Name="T_Class"> <Key> <PropertyRef Name="Id" /> </Key> <Property Type="Int32" Name="Id" Nullable="false" annotation:StoreGeneratedPattern="Identity" /> <Property Type="String" Name="Name" MaxLength="100" FixedLength="false" Unicode="true" /> <NavigationProperty Name="T_Person" Relationship="LearnModel.FK_T_Person_T_Class" FromRole="T_Class" ToRole="T_Person" /> </EntityType> <EntityType Name="T_Message"> <Key> <PropertyRef Name="Id" /> </Key> <Property Type="Int32" Name="Id" Nullable="false" annotation:StoreGeneratedPattern="Identity" /> <Property Type="String" Name="Title" Nullable="false" MaxLength="250" FixedLength="false" Unicode="true" /> <Property Type="String" Name="Message" Nullable="false" MaxLength="Max" FixedLength="false" Unicode="true" /> <Property Type="String" Name="NickName" Nullable="false" MaxLength="50" FixedLength="false" Unicode="true" /> <Property Type="Boolean" Name="IsAnonymous" Nullable="false" /> <Property Type="String" Name="IPAddress" Nullable="false" MaxLength="50" FixedLength="false" Unicode="true" /> <Property Type="DateTime" Name="PostDate" Nullable="false" Precision="3" /> <Property Type="Int32" Name="PostManId" Nullable="false" /> <Property Type="Int32" Name="ReceiveManId" Nullable="false" /> <NavigationProperty Name="T_Person" Relationship="LearnModel.FK_T_Message_T_Person1" FromRole="T_Message" ToRole="T_Person" /> <NavigationProperty Name="T_Person1" Relationship="LearnModel.FK_T_Message_T_Person2" FromRole="T_Message" ToRole="T_Person" /> </EntityType> <EntityType Name="T_Person"> <Key> <PropertyRef Name="Id" /> </Key> <Property Type="Int32" Name="Id" Nullable="false" annotation:StoreGeneratedPattern="Identity" /> <Property Type="String" Name="Name" Nullable="false" MaxLength="50" FixedLength="false" Unicode="true" /> <Property Type="Int32" Name="Age" Nullable="false" /> <Property Type="String" Name="Email" Nullable="false" MaxLength="100" FixedLength="false" Unicode="true" /> <Property Type="Int32" Name="ClassId" Nullable="false" /> <NavigationProperty Name="T_Class" Relationship="LearnModel.FK_T_Person_T_Class" FromRole="T_Person" ToRole="T_Class" /> <NavigationProperty Name="T_Message" Relationship="LearnModel.FK_T_Message_T_Person1" FromRole="T_Person" ToRole="T_Message" /> <NavigationProperty Name="T_Message1" Relationship="LearnModel.FK_T_Message_T_Person2" FromRole="T_Person" ToRole="T_Message" /> </EntityType> <Association Name="FK_T_Person_T_Class"> <End Type="LearnModel.T_Class" Role="T_Class" Multiplicity="1" /> <End Type="LearnModel.T_Person" Role="T_Person" Multiplicity="*" /> <ReferentialConstraint> <Principal Role="T_Class"> <PropertyRef Name="Id" /> </Principal> <Dependent Role="T_Person"> <PropertyRef Name="ClassId" /> </Dependent> </ReferentialConstraint> </Association> <Association Name="FK_T_Message_T_Person1"> <End Type="LearnModel.T_Person" Role="T_Person" Multiplicity="1" /> <End Type="LearnModel.T_Message" Role="T_Message" Multiplicity="*" /> <ReferentialConstraint> <Principal Role="T_Person"> <PropertyRef Name="Id" /> </Principal> <Dependent Role="T_Message"> <PropertyRef Name="PostManId" /> </Dependent> </ReferentialConstraint> </Association> <Association Name="FK_T_Message_T_Person2"> <End Type="LearnModel.T_Person" Role="T_Person" Multiplicity="1" /> <End Type="LearnModel.T_Message" Role="T_Message" Multiplicity="*" /> <ReferentialConstraint> <Principal Role="T_Person"> <PropertyRef Name="Id" /> </Principal> <Dependent Role="T_Message"> <PropertyRef Name="ReceiveManId" /> </Dependent> </ReferentialConstraint> </Association> </Schema> </edmx:ConceptualModels>
固然,咱們再經過一個代碼片斷來看看在概念模型中是如何定義的?
<EntityType Name="T_Person"> <Key> <PropertyRef Name="Id" /> </Key> <Property Type="Int32" Name="Id" Nullable="false" annotation:StoreGeneratedPattern="Identity" /> <Property Type="String" Name="Name" Nullable="false" MaxLength="50" FixedLength="false" Unicode="true" /> <Property Type="Int32" Name="Age" Nullable="false" /> <Property Type="String" Name="Email" Nullable="false" MaxLength="100" FixedLength="false" Unicode="true" /> <Property Type="Int32" Name="ClassId" Nullable="false" /> <NavigationProperty Name="T_Class" Relationship="LearnModel.FK_T_Person_T_Class" FromRole="T_Person" ToRole="T_Class" /> <NavigationProperty Name="T_Message" Relationship="LearnModel.FK_T_Message_T_Person1" FromRole="T_Person" ToRole="T_Message" /> <NavigationProperty Name="T_Message1" Relationship="LearnModel.FK_T_Message_T_Person2" FromRole="T_Person" ToRole="T_Message" /> </EntityType>
在CSDL中,大部分都與SSDL中所對應的一致,可是咱們發現多了一些屬性。例如:NavigationProperty 導航屬性,由於T_Person表與T_Class、T_Message表都存在一對一或一對多的關係(即存在外鍵),所以在EF模型所生成的對象實體中,加入了外鍵所在實體的導航屬性。
(3)C-S Mapping
它是一個映射關係,它將SSDL與CSDL對應了起來,所以咱們在用EF操做實體類時才能夠正確地生成對相應數據表的SQL語句。
<EntitySetMapping Name="T_Person"> <EntityTypeMapping TypeName="LearnModel.T_Person"> <MappingFragment StoreEntitySet="T_Person"> <ScalarProperty Name="ClassId" ColumnName="ClassId" /> <ScalarProperty Name="Email" ColumnName="Email" /> <ScalarProperty Name="Age" ColumnName="Age" /> <ScalarProperty Name="Name" ColumnName="Name" /> <ScalarProperty Name="Id" ColumnName="Id" /> </MappingFragment> </EntityTypeMapping> </EntitySetMapping> </EntityContainerMapping>
能夠看出,這裏將SSDL中的T_Person與CSDL中的LearnModel.T_Person對應了起來,固然還將他們各自的屬性進行了一一對應。
經過上面的圖片,咱們能夠看到,經過增長代理類B來解耦A與C之間的調用,這樣能夠封裝原來C調用A的一些相關細節,轉換成C直接調用B中封裝後的代理方法,則等同於訪問A。在實際應用中,例如對於WebService的遠程調用時,若是咱們使用添加Web引用的方式,那麼WebService會爲咱們自動生成代理類,咱們全部的交互都只是和代理類進行的,而沒有直接和服務提供者進行。
代理模式:能夠看看園友程興亮的一篇文章《極速理解設計模式之代理模式》
(1)咱們首先有下面這樣一段代碼,它要進行的是一個簡單的修改操做:
static void Edit() { // 下面返回的是一個Person類的代理類對象 T_Person person = db.T_Person.FirstOrDefault(p => p.Id == 11); Console.WriteLine("Before update:{0}", person.ToString()); // 此時操做的也只是Person類的代理類對象,同時標記此屬性爲已修改 person.Name = "周旭龍"; person.Email = "edisonchou7@cuit.edu.cn"; person.Age = 25; // 此時EF上下文會檢查容器內部全部的對象, // 找到標記爲修改的對象屬性生成對應的修改SQL語句 db.SaveChanges(); Console.WriteLine("Update Successfully~~"); }
(2)經過分析這段代碼,咱們知道要進行一個修改操做,須要通過三個步湊:第一是查詢到要修改的那一行,而後進行修改操做賦值,最後提交修改操做。(固然,這是官方推薦的修改操做步湊,你也能夠不通過查詢而直接修改,這須要利用到DbEntityEntry)那麼,爲何要通過這幾個步湊呢?
①咱們首先來看看第一步:查詢
db.T_Person.FirstOrDefault(p => p.Id == 11);
在實際開發中,咱們的應用程序不會直接和數據庫打交道,而是和EF數據上下文中的代理類打交道。首先,經過查詢操做數據庫返回了一行數據,EF上下文將其接收並將其「包裝」起來,因而就有了代理類。在代理類中,真實的實體類對象被封裝了起來,而且在代理類中爲每一個屬性都設置了一個標誌,用來標識其狀態(是否被修改)。而咱們在程序中所得到的數據,都是從代理類中返回的。
②再來看看第二步:修改
person.Name = "周旭龍";
當執行完修改操做以後,代理類中對應字段的標誌會被修改,例如咱們這裏修改了Name屬性,那麼其對應的標誌就會由false變爲true。雖然只是變了一個標誌位,可是卻對EF生成SQL語句產生了重大影響。若是咱們只修改了一個屬性,那麼其生成的SQL語句只會有一個Update ** Set Name='***'。
③最後來看看第三步:提交
db.SaveChanges();
當SaveChanges方法觸發時,EF上下文會遍歷代理類對象中的狀態標誌,若是發現有修改的(即爲True)則將其加入生成的SQL語句中。這裏,由於Name被修改了,因此在生成的SQL語句中會將Name加入,而其餘未修改的則不會加入。
咱們也能夠經過SQLServer Profiler來查看EF所生成的SQL語句:
所謂延遲加載,就是隻有在咱們須要數據的時候纔去數據庫讀取加載它。
在Queryable類中的擴展方法中,Where方法就是一個典型的延遲加載案例。在實際的開發中,咱們每每會使用一些ORM框架例如EF去操做數據庫,Where方法的使用則是每次調用都只是在後續生成SQL語句時增長一個查詢條件,EF沒法肯定本次查詢是否已經添加結束,因此沒有辦法木有辦法在每一個Where方法執行的時候肯定最終的SQL語句,只能返回一個DbQuery對象,當使用到這個DbQuery對象的時候,纔會根據全部條件生成最終的SQL語句去查詢數據庫。
(1)針對條件的延遲加載
DbQuery<T_Person> query = db.T_Person.Where(p => p.Id == 11).OrderBy(p => p.Age) as DbQuery<T_Person>; // 用的時候纔去加載:根據以前的條件生成SQL語句訪問數據庫 T_Person person = query.FirstOrDefault();
經過SQLServer Profiler調試跟蹤,當執行完第一行代碼時,是沒有進行對數據庫的查詢操做的。而當執行到第二行的FirstOrDefault()方法時,EF才根據前面的條件生成了查詢SQL語句去加載數據。
(2)針對外鍵的延遲加載
首先,咱們有這樣兩張表,他們是1:N的關係;其中ClassId是T_Person的外鍵;
其次,在EF所生成的實體對象中,在T_Person的代碼中會有一個T_Class的對象屬性;由於一個T_Person對應一個T_Class;
public partial class T_Person { public T_Person() { this.T_Message = new HashSet<T_Message>(); this.T_Message1 = new HashSet<T_Message>(); } public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public string Email { get; set; } public int ClassId { get; set; } public virtual T_Class T_Class { get; set; } }
最後,在實際開發中,咱們有以下一段代碼:
IQueryable<T_Person> dbQuery = db.T_Person.Where(p => p.ClassId == 1); T_Person person = dbQuery.FirstOrDefault(); Console.WriteLine(person.T_Class.Name);
經過調試咱們能夠發現,在執行最後一段代碼輸出Person對象所屬班級的信息以前,都沒有對Class進行查詢。而在執行到最後一句時,纔去數據庫查詢所對應的Class信息;
所謂即時加載,就是在加載數據時就把該對象相關聯的其它表的數據一塊兒加載到內存對象中去。
在Queryable類中的擴展方法中,ToList()方法就是一個典型的即時加載案例。與延遲加載相對應,在開發中若是使用ToList()方法,EF會根據方法中的條件自動生成SQL語句,而後當即與數據庫進行交互獲取查詢結果,並加載到內存中去。
(1)例如,咱們有如下一段代碼,在執行到第一句的ToList()方法時,EF就當即對數據庫發起訪問,並將結果記載到了內存中,最後將personList指向了這塊記錄在堆中的地址;
List<T_Person> personList = db.T_Person.Where(p => p.ClassId == 1).ToList(); personList.ForEach(p => Console.WriteLine(p.ToString()));
(2)這時候,若是咱們再想對查詢到的結果進行排序,咱們該怎麼寫?是否是寫出瞭如下的代碼:
List<T_Person> personList = db.T_Person.Where(p => p.ClassId == 1).ToList().OrderBy(p => p.Name).ToList(); personList.ForEach(p => Console.WriteLine(p.ToString()));
這時咱們發現,當ToList()以後,OrderBy()方法就沒有對SQL語句進行調整,也沒有再對數據庫發起請求了。由於,這裏的OrderBy()方法是對內存中的數據進行的排序,而不是和前面的Where()方法一塊兒拼接成SQL語句。
前面咱們看到了延遲加載在EF中被普遍應用,可是延遲加載對於外鍵的加載也存在不足:那就是每次調用外鍵實體都會去查數據庫。
(1)咱們來看看下面一段代碼,它的做用是經過每一個班級查詢所對應的全部學生信息:
IQueryable<T_Class> classQuery = db.T_Class; foreach (T_Class c in classQuery) { Console.WriteLine(c.Id + "-" + c.Name + ":"); ICollection<T_Person> pList = c.T_Person; foreach (T_Person p in pList) { Console.WriteLine(p.Id + "-" + p.Name); } }
其顯示結果以下圖所示:
(2)經過SQLServer Profiler跟蹤,能夠發現,每次調用外鍵實體屬性時都會對數據庫發出一塊兒查詢請求,從下圖也能夠看出,總共發出了接近10個請求;
(3)可是,EF也作了一個小優化:對於相同外鍵的加載請求,只會執行一次;例如,這裏存在多個ClassId=1的Person記錄,所以它們都只會執行一次便可;
(4)雖然EF作了一些優化,可是有木有一種方法可以讓咱們只經過一次請求就獲取全部的信息呢?在SQL語句中,咱們能夠經過一個超級簡單的鏈接查詢就能夠實現,那麼在EF中呢如何實現呢?還好,微軟早就想到了這一點,爲咱們提供了一個Include方法。咱們能夠對上面的代碼段進行修改,獲得下面的代碼:
IQueryable<T_Class> classQuery = db.T_Class.Include("T_Person"); foreach (T_Class c in classQuery) { Console.WriteLine("班級:" + c.Id + " " + c.Name + ":"); ICollection<T_Person> pList = c.T_Person; foreach (T_Person p in pList) { Console.WriteLine(p.Id + "-" + p.Name); } }
這下程序在執行到Include方法時,便會與T_Person表進行一個鏈接查詢,將鏈接查詢到的T_Person部分數據存入T_Class的T_Person屬性中,也就是都存入了內存中,後面再次訪問外鍵實體只須要從內存中讀取而不用再發出多個數據庫查詢請求了。
Include方法跟ToList方法同樣,也是即時加載類型的一種具體方法,其本質是生成鏈接查詢的SQL語句。從總體來看,經過Include將以空間換取效率,在某些具體的應用場合能夠適當使用。
(1)陳少鑫,《EF貪婪加載與延遲加載的選擇和使用》:http://www.cnblogs.com/chenshao/p/4169210.html
(2)強子,《解析ASP.NET MVC開發方式之EF延遲加載》:http://www.cnblogs.com/qq731109249/p/3502874.html
(3)Liam Wang,《ASP.NET MVC小牛之路:使用EF》:http://www.cnblogs.com/willick/p/3304534.html