本文目錄:
一、EF中的edmx文件
1.1 emdx文件本質:一個XML文件
(1)通過選擇以XML方式打開edmx文件,我們可以可以清楚地看到,edmx模型文件本質就是一個XML文件;
(2)可以清楚地看到,edmx模型文件是一個XML文件,其中定義了三大組成部分,這三大組成部分構成了所謂的ORM(對象關系映射);
(3)再通過解決方案管理器分析edmx模型文件,其包含了三個子文件:
①第一個是xxx.Context.tt,這個首先是一個T4的模板文件,它生成了我們這個模型的上下文類;
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軸)等;
<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模板文件,對應了所有選擇的數據庫表:
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.2 emdx組成部分:SSDL、CSDL、C-S Mapping
(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對應了起來,當然還將他們各自的屬性進行了一一對應。
二、EF中的代理類對象
2.1 代理模式初探
通過上面的圖片,我們可以看到,通過增加代理類B來解耦A與C之間的調用,這樣可以封裝原來C調用A的一些相關細節,轉換成C直接調用B中封裝后的代理方法,則等同於訪問A。在實際應用中,例如對於WebService的遠程調用時,如果我們使用添加Web引用的方式,那么WebService會為我們自動生成代理類,我們所有的交互都只是和代理類進行的,而沒有直接和服務提供者進行。
代理模式:可以看看園友程興亮的一篇文章《極速理解設計模式之代理模式》
2.2 EF中的代理應用
(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語句:
三、EF中的延遲加載與即時加載
3.1 淺談延遲加載
所謂延遲加載,就是只有在我們需要數據的時候才去數據庫讀取加載它。
在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信息;
3.2 淺談即時加載
所謂即時加載,就是在加載數據時就把該對象相關聯的其它表的數據一起加載到內存對象中去。
在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語句。
3.3 使用Include提高查詢效率
前面我們看到了延遲加載在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