.NET框架4.0的發行推出了許多優秀的增強功能,其中當首推ADO.NET實體框架。該框架已經克服了以前的許多錯誤,並提供了一組增強的API,其中包括許多新的LINQ to SQL框架方面的改善。在本文中,我們將使用這些API的功能來創建一個通用版本的數據倉庫。
一、實體框架概述
實體框架針對數據模型提供了一些更方便的操作方法。默認情況下,設計器可以生成一個描述數據庫的模型。
儘管表格間的映射未必都是1:1的映射。每個表格使用一個ObjectSet加以描述,進而ObjectSet對象又提供了相應的方法來創建、更新或反射實體和實體間的關係。實體框架使用一個實體鍵(這是一個看上去像EntitySet=Customers;CustomerID=4的值)來唯一標識模型內的一個實體及其標識符。使用實體鍵,我們就有了一個方法來更新對象、從數據庫中查詢的對象,等等。
二、創建和更新
讓我們首先來看一個基類示例倉庫的實現。我想分別地討論CRUD操作,首先來學習創建和更新操作。
清單1:創建/更新操作
- public abstract class BaseRepository<T> : IRepository<T>
- where T : EntityObject
- {
- public virtual bool CreateNew(T entity)
- {
- if (entity == null)
- throw new ArgumentNullException("entity");
- var ctx = CreateContext();
- try
- {
- ctx.AddObject(this.GetFullEntitySetName(ctx), entity);
- ctx.SaveChanges();
- return true;
- }
- catch (Exception ex) { .. }
- }
- protected abstract string GetEntitySetName(AdventureWorksObjectContext context);
- public virtual bool Update(T entity)
- {
- if (entity == null)
- throw new ArgumentNullException("entity");
- var ctx = CreateContext();
- entity.EntityKey = ctx.CreateEntityKey(this.GetFullEntitySetName(ctx),
- entity);
- try
- {
- T oldEntity = (T)ctx.GetObjectByKey(entity.EntityKey);
- if (oldEntity == null) return false;
- ctx.ApplyCurrentValues(this.GetFullEntitySetName(ctx), entity);
- ctx.SaveChanges();
- return true;
- }
- catch (Exception ex) { .. }
- }
- }
上述代碼中,我們的BaseRepository類使用ObjectContext類(需要使用CreateContext方法創建每一個請求)和AddObject方法實現添加新的對象,而通過使用ObjectContext類和AttachTo方法實現更新現有的對象。對於創建對象而言,我們需要知道要更新哪種類型的方法。使用我們的助理GetFullEntitySetName方法可以很好地處理這個問題。這個方法能夠返回要添加的標識實體的對象(一個如DotNetSamplesObjectContext.Customers的值)的標識。
對於更新一個對象而言,我們遇到了與上下文有關的問題。每個從數據庫中查詢的對象都使用ObjectStateManager類中的ObjectContext成員進行跟蹤。MVC綁定過程實際上已經構建了它自己的對象副本,並通過反射把這些值注入到此對象中。這意味着我們有一個新的對象,而不是附加到ObjectContext上的對象。
這不是一個大問題,我們首先需要查詢舊記錄。這將爲我們的實體生成一個ObjectStateEntry,並且我們可以成功地執行更新(因爲它需要知道舊記錄是什麼)。該實體還需要使用一個EntityKey實體,提供適當的主鍵信息(記住,EntityKey是確定出已存在的實體的唯一的方式)。
最後,調用ApplyCurrentValues能夠把MVC框架所創建的新的實體值應用到舊實體上。在這裏,我們仍然需要使用實體集的名稱來唯一標識它。
三、元數據
在上面代碼中,我們看到了實體集名稱的使用方法,用來確定ADO.NET實體框架中的實體的類型。例如,它可以用於描述Products表和Product實體之間的一個映射。還例如,對於我們的產品信息庫來說,它可以執行下列操作以獲取實體集。
清單2—返回產品實體集名稱
- protected override Expression<Func<DA.Product, object>> GetDefaultSortingExpression()
- {
- return j => j.ProductID;
- }
- protected override string GetEntitySetName(AdventureWorksObjectContext context)
- {
- return context.Products.EntitySet.Name;
- }
我們很快將會看到GetDefaultSortingExpression的使用。請注意,這裏的GetFullEntitySetName方法把對象的上下文名稱追加到實體集名稱的後面,以取得添加,更新等操作對應對象的正確名稱。
四、數據檢索
一般地,我們還可以執行一些讀取操作,如下所示。
清單3—從數據庫讀取數據
- protected virtual string GetKeyProperty()
- {
- PropertyInfo[] properties = typeof(T).GetProperties();
- foreach (PropertyInfo property in properties)
- {
- EdmScalarPropertyAttribute attrib = property.GetCustomAttributes
- (typeof(EdmScalarPropertyAttribute), false).FirstOrDefault() as EdmScalarPropertyAttribute;
- if (attrib != null && attrib.EntityKeyProperty)
- return property.Name;
- }
- return null;
- }
- public virtual T Get(int key)
- {
- string prop = this.GetKeyProperty();
- if (string.IsNullOrEmpty(prop))
- return null;
- var ctx = CreateContext();
- return (T)ctx.GetObjectByKey(new EntityKey(this.GetFullEntitySetName(ctx),
- prop, key));
- }
- public virtual IQueryable<T> GetAll(int pageIndex, int pageSize)
- {
- var ctx = CreateContext();
- return ctx.CreateObjectSet<T>(this.GetFullEntitySetName(ctx)).OrderBy(this.GetDefaultSortingExpression())
- .Skip(pageIndex * pageSize).Take(pageSize);
- }
默認設計器生成的每個實體類都將把一組屬性添加到它對應的每一個字段屬性上。其中,EdmScalarPropertyAttribute擁有EntityKeyProperty設置,被設置爲true,對應於實體的鍵字段。這就提供了一種靈活的方式來確定主鍵列而不需要使用一個lambda表達式手動指定。
跟蹤分析到ObjectContext方法內部,你會發現通過使用實體集名稱構造一個對象集合可以取得一個數據實體的所有結果。對象集可以使用LINQ擴展方法來按索引頁和大小加以過濾,例如只取得一個包含20個對象的結果集。不幸的是,調用Skip和Take方法需要先對對象進行排序。同樣,你需要使用一個自定義Lambda表達式來執行這個排序操作。
GetObjectByKey方法實際上使用它的鍵從它的數據庫中檢索對象。我們可以利用我們的新的GetKeyProperty反射方法來獲取主鍵屬性的名稱。正如你所看到的,我們不能直接使用這個鍵而需要使用一個EntityKey對象來檢索它。
五、最終實現
我可以利用一個類似下面的信息庫,並且已經在基類中實現了Create、Delete、Update、Get和GetAll方法。我們只需要關心的是,實現其他的查詢操作。
清單4—最終版本的數據倉庫類(其他其他前面已列舉的內容)
- public class ProductsRepository : BaseRepository<DA.Product>
- {
- protected override Expression<Func<DA.Product, object>> GetDefaultSortingExpression()
- {
- return j => j.ProductID;
- }
- protected override string GetEntitySetName(DA.DotNetSamplesObjectContext context)
- {
- return context.Products.EntitySet.Name;
- }
- }
在大多數情況下,代碼生成將是最好的選擇,有助於減少重複代碼,但是,實體框架做了大量的內部基礎工作(實現基礎代碼的自動生成)來實現這些特徵支持而無需我們編寫任何代碼。
六、結論
ADO.NET實體框架提供了大量基礎功能,節省了開發人員大量的代碼編寫時間。在本文中,我們討論了ObjectContext類提供給我們的許多方法,其中包括從後端數據庫獲取和存入數據,等等。
最後,我們有理由相信,ADO.NET實體框架必將在ASP.NET MVC框架應用程序開發的數據管理模型開發中發揮越來越大的作用。