一個簡單的EF查詢框架除了運行項目外,大概需要5個類庫項目,當然這個不是一定要這樣做,這可以根據自己的需要設置有多少個項目。這里介紹的方法步驟只適合EF零基礎的人看看就是了。
在開始之前,先建立一個運行項目,不管是MVC還是winfrom項目,只要能運行就OK,這是為了在后面能測試EF框架的地方。
一、模型項目。這個項目主要負責與數據庫映射的,里面的類都是數據庫表的實體。
首先在解決方案中建一個文件夾EFModel,這里將會收納所有的與EF查詢有關的項目,這樣做只是為了將他們與運行項目分開而已,沒有什么特別的意思。
在EFModel中建立一個類庫,命名為DBModel,然后在這個類庫中創建一個EF
添加之后,選擇從數據庫生成
在這里,服務器名可以輸入數據庫所在的服務器的ip,然后測試一下鏈接,看是否成功。在下面的鏈接到數據庫中選擇你要鏈接的數據庫
下一步后,如果沒有什么特別設置,就直接完成就可以了。然后看看生成的App.config文件,在connectionStrings節點下就是數據庫的鏈接字符串了
由於沒有選擇任何的表,因此在數據庫模型中就是空的,這時需要添加新的模型來映射數據庫中的表,打開model.edmx,然后右鍵:
下面這一步最好選擇是,否則在后面運行程序的時候可能會出現:基礎提供程序在 Open 上失敗。的錯誤
找到要添加的表,然后點完成
最后保存model,就能看到在model.tt下添加了一個實體類。至此,模型項目就完成了,如果以后需要添加新的實體類,也從數據庫更新模型就是了。
這種模式下,必須是先在數據庫中把表建好,然后才能添加實體類,如果需要對已經添加了實體類的表做結構上的修改,那么在數據庫中完成修改后,需要在model.edmx中先刪除原來的實體,然后重新添加一次才行。
二、增刪改查公用方法的接口項目IDAO
建立一個類庫項目EF.IDAO,添加兩個引用
然后添加接口IBaseDao,
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace EF.IDAO { public interface IBaseDao<T> where T:class, new ()//約束T類型必須可以實例化 { //根據條件獲取實體對象集合 IQueryable<T> LoadEntites();
//千萬別用下面兩種方式去查詢,因為這兩種方式是吧整個表所有的數據查出來后再按條件篩選的
//IQueryable<T> LoadEntites(Func<T,bool> whereLambda );
//IQueryable<T> LoadEntites(Func<T,bool> whereLambda, int pageIndex, int pageSize,out int totalCount);
//增加
T AddEntity(T entity);
//更新
T UpdateEntity(T entity);
//刪除
bool DelEntity(T entity);
//根據條件刪除
bool DelEntityByWhere(Func<T, bool> whereLambda);
}
}
此時基接口中的CRUD方法就定義完成。接下來我們需要使用T4模版生成所有的實體類接口並實現IBaseDao接口。
添加名為IDaoExt的T4模板
加入如下代碼:
<#@ template debug="false" hostspecific="true" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ include file="EF.Utility.CS.ttinclude"#> <#@ output extension=".cs" #> <# CodeGenerationTools code = new CodeGenerationTools(this); MetadataLoader loader = new MetadataLoader(this); CodeRegion region = new CodeRegion(this, 1); MetadataTools ef = new MetadataTools(this); string inputFile = @"..\\DBModel\\Model.edmx";//指定edmx實體模型所在的路徑 EdmItemCollection ItemCollection = loader.CreateEdmItemCollection(inputFile); string namespaceName = code.VsNamespaceSuggestion(); EntityFrameworkTemplateFileManager fileManager = EntityFrameworkTemplateFileManager.Create(this); #> using System; using System.Collections.Generic; using System.Linq; using System.Text; using DBModel;//引用Domain的命名空間 namespace EF.IDAO //實體類接口所在的命名空間 { <# foreach (EntityType entity in ItemCollection.GetItems<EntityType>().OrderBy(e => e.Name)) //遍歷edmx模型中映射的實體對象 {#> public interface I<#=entity.Name#>Dao:IBaseDao<<#=entity.Name#>> //生成實體對象接口,這里生成的接口的名稱是根據模型中的實體類名來設置的,而實體的類名是在建數據庫表名的時候設置的名稱 { } <#};#> }
其中,有注釋的地方是需要根據自己的情況修改的地方(后面項目中的文本模板文件也是如此)。保存文件,然后就可以在IDaoExt.tt下生成了一個文件IDaoExt.cs,這個文件中有一個接口,這個接口就是上面添加的實體所對應的接口。它是根據模板IDaoExt.tt來生成的,因此每當添加了新的實體后,都需要重新保存一次這些tt文件。當然,也可以不用這個模板文件來生成對應的接口,那樣的話就需要手動的添加了,這根據自己的愛好決定用哪種方式。
在項目中建一個局部接口IDBSession,注意,是局部接口,要用到關鍵字partial。
namespace EF.IDAO { public partial interface IDBSession { int SaveChange();//用於在業務邏輯層對提交進行管理 } }
這個接口主要是提供實體類接口的訪問屬性,以實現對數據訪問層的封裝。建一個文本模板IDBSession.tt
<#@ template debug="false" hostspecific="true" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ include file="EF.Utility.CS.ttinclude"#> <#@ output extension=".cs" #> <# CodeGenerationTools code = new CodeGenerationTools(this); MetadataLoader loader = new MetadataLoader(this); CodeRegion region = new CodeRegion(this, 1); MetadataTools ef = new MetadataTools(this); string inputFile = @"..\\DBModel\\Model.edmx"; EdmItemCollection ItemCollection = loader.CreateEdmItemCollection(inputFile); string namespaceName = code.VsNamespaceSuggestion(); EntityFrameworkTemplateFileManager fileManager = EntityFrameworkTemplateFileManager.Create(this); #> using System; using System.Collections.Generic; using System.Linq; using System.Text; using DBModel; using EF.IDAO; namespace EF.IDAO { public partial interface IDBSession { <#foreach (EntityType entity in ItemCollection.GetItems<EntityType>().OrderBy(e => e.Name)) {#> I<#=entity.Name#>Dao <#=entity.Name#>Dao { get; set; } <#};#> } }
保存之后,就自動生成了IDBSession的局部接口了
再添加一個接口IDBSessionFactory,為業務邏輯層提供統一訪問入口:
namespace EF.IDAO { public interface IDBSessionFactory { IDBSession GetCurrentDBSession(); } }
該接口中定義GetCurrentDBSession()方法,其作用是通過該接口方法獲取需要的實體對象。至此,這個項目基本上完成了,在這里添加了如下文件:
三、增刪改查等方法的實現項目DAO
建好項目后添加如下引用:
創建ObjectContextFactory類,用來獲取EF上下文。當網站訪問量增大時,為避免EF產生的臟數據問題,我們使用System.Runtime.Remoting.Messaging 命名空間下的CallContext來解決線程內上下文唯一。
namespace EF.DAO { public class ObjectContextFactory { public static DbContext GetCurrentObjectContext() { //從CallContext數據槽中獲取EF上下文 DbContext objectContext = CallContext.GetData(typeof(ObjectContextFactory).FullName) as DbContext; if (objectContext == null) { //如果CallContext數據槽中沒有EF上下文,則創建EF上下文,並保存到CallContext數據槽中 objectContext = new DBContent();//當數據庫替換為MySql等,只要在此處EF更換上下文即可。這里的DBContent是model.context.cs中的局部類 CallContext.SetData(typeof(ObjectContextFactory).FullName, objectContext); } return objectContext; } } }
創建BaseDao類,實現IBaseDao中定義方法,用於所有實體類繼承此基類。但是,這個類和接口IBaseDao沒有任何的關系,他們之間的聯系體現在這個類的派生類實現了IBaseDao這個接口
namespace EF.DAO { public class BaseDao<T> where T : class, new() { DbContext objectContext = ObjectContextFactory.GetCurrentObjectContext() ;//獲取EF上下文 /// <summary> /// 加載實體集合 /// </summary> /// <param name="whereLambda"></param> /// <returns></returns> public virtual IQueryable<T> LoadEntites() { return objectContext.Set<T>().AsQueryable<T>(); }/// <summary> /// 添加實體 /// </summary> /// <param name="entity"></param> /// <returns>返回更新后的實體</returns> public virtual T AddEntity(T entity) { objectContext.Set<T>().Add(entity); objectContext.SaveChanges(); return entity; } /// <summary> /// 更新實體 /// </summary> /// <param name="entity"></param> /// <returns>返回更新后的實體</returns> public virtual T UpdateEntity(T entity) { objectContext.Set<T>().Attach(entity); objectContext.Entry<T>(entity).State = EntityState.Modified;//將附加的對象狀態更改為修改 objectContext.SaveChanges(); return entity; } /// <summary> /// 刪除實體 /// </summary> /// <param name="entity"></param> /// <returns></returns> public virtual bool DelEntity(T entity) { objectContext.Set<T>().Attach(entity); objectContext.Entry<T>(entity).State = EntityState.Deleted;//將附加的實體狀態更改為刪除 if (objectContext.SaveChanges() > 0) { return true;//刪除成功 } else { return false;//刪除失敗 } } /// <summary> /// 根據條件刪除對象 /// </summary> /// <param name="whereLambda">條件</param> /// <returns></returns> public virtual bool DelEntityByWhere(Func<T, bool> whereLambda) { var tmp = objectContext.Set<T>().Where<T>(whereLambda);//根據條件從數據庫中獲取對象集合 foreach (var entity in tmp) { objectContext.Entry<T>(entity).State = EntityState.Deleted;//標記對象為刪除狀態刪除 } if (objectContext.SaveChanges() > 0) { return true; } else { return false; } } } }
使用T4模板生成所有實體對象的實現,生成所有的實體類繼承自BaseDao並實現各自的接口。創建一個模板文件DaoExt
<#@ template debug="false" hostspecific="true" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ include file="EF.Utility.CS.ttinclude"#> <#@ output extension=".cs" #> <# CodeGenerationTools code = new CodeGenerationTools(this); MetadataLoader loader = new MetadataLoader(this); CodeRegion region = new CodeRegion(this, 1); MetadataTools ef = new MetadataTools(this); string inputFile = @"..\\DBModel\\Model.edmx"; EdmItemCollection ItemCollection = loader.CreateEdmItemCollection(inputFile); string namespaceName = code.VsNamespaceSuggestion(); EntityFrameworkTemplateFileManager fileManager = EntityFrameworkTemplateFileManager.Create(this); #> using System; using System.Collections.Generic; using System.Linq; using System.Text; using DBModel; using EF.IDAO; namespace EF.DAO { <# foreach (EntityType entity in ItemCollection.GetItems<EntityType>().OrderBy(e => e.Name)) {#> public partial class <#=entity.Name#>Dao:BaseDao<<#=entity.Name#>>,I<#=entity.Name#>Dao { } <#};#> }
創建文本模板DBSessionExt,來生成DBSession實現接口IDBSession
<#@ template debug="false" hostspecific="true" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".cs" #> <#@ include file="EF.Utility.CS.ttinclude"#> <# CodeGenerationTools code = new CodeGenerationTools(this); MetadataLoader loader = new MetadataLoader(this); CodeRegion region = new CodeRegion(this, 1); MetadataTools ef = new MetadataTools(this); string inputFile = @"..\\DBModel\\Model.edmx"; EdmItemCollection ItemCollection = loader.CreateEdmItemCollection(inputFile); string namespaceName = code.VsNamespaceSuggestion(); EntityFrameworkTemplateFileManager fileManager = EntityFrameworkTemplateFileManager.Create(this); #> using System; using System.Collections.Generic; using System.Linq; using System.Text; using EF.IDAO; using System.Data.Entity; namespace EF.DAO { public partial class DBSession : IDBSession { private DbContext _efContext; //EF上下文 這段代碼是因為要實現IDBSession接口中的SaveChange方法而寫的,因為每次保存這個文本模板時,如果沒有這段代碼那么在生成的類中就會缺少SaveChange方法的實現,從而導致編譯不過 public DbContext EfContext { get { if (_efContext == null) { _efContext = ObjectContextFactory.GetCurrentObjectContext(); } return _efContext; } set { _efContext = value; } } public int SaveChange() { return EfContext.SaveChanges();//調用SaveChanges()方法提交操作 } <#foreach (EntityType entity in ItemCollection.GetItems<EntityType>().OrderBy(e => e.Name)) {#> private I<#=entity.Name#>Dao _<#=entity.Name#>Dao; public I<#=entity.Name#>Dao <#=entity.Name#>Dao { get { if (_<#=entity.Name#>Dao == null) { _<#=entity.Name#>Dao = new <#=entity.Name#>Dao(); } return _<#=entity.Name#>Dao; } set { _<#=entity.Name#>Dao = value; } } <#}#> } }
添加一個類DBSessionFactory,實現接口IDBSessionFactory
namespace EF.DAO { public class DBSessionFactory : IDBSessionFactory { public IDBSession GetCurrentDBSession() { IDBSession dbSession = CallContext.GetData(typeof(DBSessionFactory).FullName) as DBSession; if (dbSession == null) { dbSession = new DBSession(); CallContext.SetData(typeof(DBSessionFactory).FullName, dbSession); } return dbSession; } } }
至此,我們就已經完成了對數據訪問層的封裝,當數據庫中的表或字段有更新時,我們只需要重新運行一下相應T4模版,就可以實現與數據庫保存一致。一共添加了如下文件
四、業務邏輯層的封裝
創建名為EF.IBLL的程序集,主要用於業務邏輯層接口定義
創建接口IBaseService,這個接口和IBaseDao一模一樣的,但是這個接口是業務邏輯層的,而IBaseDao是數據訪問層的。所以可以將IBaseDao直接拷過來換個名字就行了
創建名為IServiceExt的T4模版,用於自動生成所有實體對象的接口,並繼承自IBaseService接口
<#@ template debug="false" hostspecific="true" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".cs" #> <#@ include file="EF.Utility.CS.ttinclude"#> <# CodeGenerationTools code = new CodeGenerationTools(this); MetadataLoader loader = new MetadataLoader(this); CodeRegion region = new CodeRegion(this, 1); MetadataTools ef = new MetadataTools(this); string inputFile = @"..\\DBModel\\Model.edmx"; EdmItemCollection ItemCollection = loader.CreateEdmItemCollection(inputFile); string namespaceName = code.VsNamespaceSuggestion(); EntityFrameworkTemplateFileManager fileManager = EntityFrameworkTemplateFileManager.Create(this); #> using System; using System.Collections.Generic; using System.Linq; using System.Text; using DBModel; namespace EF.IBLL { <# foreach (EntityType entity in ItemCollection.GetItems<EntityType>().OrderBy(e => e.Name)) {#> public interface I<#=entity.Name#>Service : IBaseService<<#=entity.Name#>> { } <#};#> }
至此,業務邏輯層定義完成
五、實現業務邏輯層,對數據訪問層的調用
創建項目EF.BLL,創建名為BaseService基類。該基類中實現了對數據訪問層的調用,也實現了增刪改查
步驟: 1、先將IDBSessionFactory封裝為屬性,用於獲取IDBSession
2、再將IDBSession封裝為屬性,用於獲取EF上下文對象
3、定義IBaseDao類型的CurrentDao屬性,用於屬性獲取具體的實體對象
4、定義抽象方法 SetCurrentDao(),用於子類設置實現,為CurrentDao屬性賦具體的實體對象
namespace EF.BLL { public abstract class BaseService<T> where T : class, new() { //構造函數 public BaseService() { //調用SetCurrentDao()方法,要求子類必須實現 SetCurrentDao(); } //獲取EF實體工廠 IDBSessionFactory _dbSessionFactory; IDBSession _dbSession; public IDBSessionFactory DbSessionFactory { get { if (_dbSessionFactory == null) { _dbSessionFactory = new DBSessionFactory(); } return _dbSessionFactory; } set { _dbSessionFactory = value; } } public IDBSession DbSession { get { if (_dbSession == null) { _dbSession = DbSessionFactory.GetCurrentDBSession();//通過數據訪問層提供的工廠獲取EF實體對象 } return _dbSession; } set { _dbSession = value; } } //數據訪問層基接口類型可以接收數據訪問層的所有實體Dao public IBaseDao<T> CurrentDao { get; set; } //該方法用於子類實現,其作用是設置相應的實體Dao public abstract void SetCurrentDao(); } }
在實現增加和更新方法時,我們這時調用DBSessin中封裝的SaveChanges()方法進行提交,主要目的是實現業務層控制提交。由於EF具有延遲加載特性,因此我們利用此特性,實現批量操作時,一次提交數據庫,以提高程序性能,因此我們這時需要將BaseDao中的增加和更新方法中的SaveChange()方法注視掉。
基類BaseService剩下的代碼:
//以下是CRUD實現 public virtual IQueryable<T> LoadEntites() { return this.CurrentDao.LoadEntites(); }
public virtual T AddEntity(T entity) { var tmp = this.CurrentDao.AddEntity(entity); this.DbSession.SaveChange(); return tmp; } public virtual T UpdateEntity(T entity) { var tmp = this.CurrentDao.UpdateEntity(entity); this.DbSession.SaveChange(); return tmp; } public virtual bool DelEntity(T entity) { return this.CurrentDao.DelEntity(entity); } public virtual bool DelEntityByWhere(Func<T, bool> whereLambda) { return this.DelEntityByWhere(whereLambda); }
至此,BaseService業務邏輯層基類就封裝完成,接下來使用T4模版自動生成所有實體。
創建名為ServiceExt的T4模版
<#@ template debug="false" hostspecific="true" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".cs" #> <#@ include file="EF.Utility.CS.ttinclude"#> <# CodeGenerationTools code = new CodeGenerationTools(this); MetadataLoader loader = new MetadataLoader(this); CodeRegion region = new CodeRegion(this, 1); MetadataTools ef = new MetadataTools(this); string inputFile = @"..\\DBModel\\Model.edmx"; EdmItemCollection ItemCollection = loader.CreateEdmItemCollection(inputFile); string namespaceName = code.VsNamespaceSuggestion(); EntityFrameworkTemplateFileManager fileManager = EntityFrameworkTemplateFileManager.Create(this); #> using System; using System.Collections.Generic; using System.Linq; using System.Text; using EF.IBLL; using DBModel; namespace EF.BLL { <# foreach (EntityType entity in ItemCollection.GetItems<EntityType>().OrderBy(e => e.Name)) {#> public partial class <#=entity.Name#>Service : BaseService<<#=entity.Name#>>, I<#=entity.Name#>Service { public override void SetCurrentDao() { this.CurrentDao = this.DbSession.<#=entity.Name#>Dao; } } <#};#> }
這時我們就對業務邏輯層封裝完成。
現在需要建一個運行項目,來測試是否真的可以訪問數據庫了。項目建好后,添加DBModel,IBLL,BLL的引用。將DBModel中app.config里面的字符串鏈接復制到運行項目中的web.config中
然后在要訪問數據庫的方法中通過下面的代碼來實現:
EF.IBLL.IN_CommodityService ics = new EF.BLL.N_CommodityService(); IList<DBModel.N_Commodity> lis = ics.LoadEntites().where(t => t.Id_bigint < 200).ToList(); int s = lis.Count;
//如果是分頁查詢,如下:
IList<DBModel.N_Commodity> les = ics.LoadEntites().where(t => t.Id_bigint < 200).Take(30).Skip(20).ToList();
//還可以通過OrderBy,ThenByDescending,ThenBy等方法來實現排序,這些都要在tolist方法之前調用
也就是說,所有對數據庫的訪問都是通過EF.IBLL中的實體接口來實現的。
總結:整個數據訪問的框架就分為業務邏輯層(EF.IBLL,EF.BLL),數據訪問層(EF.IDAO,EF.DAO)和實體(DBModel)三個部分。在創建實體的時候,要注意EF版本的選擇,目前來說最好是選擇5.0版本的,因為EntityFramework的版本為5.0,否則可能會出現意想不到的錯誤。還有就是在選擇數據連接的時候,對於是否在字符串中包括敏感數據,選擇是就行了,這也是為了避免出現不可預知的錯誤。每次更新了實體的時候,都要重新保存一次所有的文本模板文件,以便能自動生成實體類和接口。
以上介紹的一些對數據庫的訪問的方法都是linq方式的,有時候我們需要通過sql語句來實現對數據庫的訪問,這種方式在另一篇隨筆再詳細的介紹。
ThenBy