接上一篇文章。現在寫程序,做項目不是說功能做完就完事了,在平常的開發過程中對於性能的考慮也是極其重要的。
關於ef的那些事,今天就來說說吧。首先必須得知道.net ef在程序中的五種狀態變化過程與原理。
主要來說說查詢部分的性能優化,在所有查詢中,客戶端查詢出來的數據一般來說是不需要進行跟蹤的。也就是說查詢只是給用戶看,不做其他任何操作。對於基於B/S模式的項目網站開發,應該是無狀態的,也就是ef中的遊離態(Unchanged)(個人理解)。如果是C/S可能還需要進行其他連接的狀態。通俗的說,在開發過程不用去檢測某一個屬性或者類是否被修改或者刪除。
例如:
AsNoTracking:它的作用就是在查詢的過程中不被緩存,也就是不被保留,查出來就完事了,這樣的狀態就變成了Detached遊離態
//AsNoTracking:它的性能比ToList快大約4.8倍,不被緩存的數據。 var item=db.Book.AsNoTracking().First(m=>m.Id==1); Console.WriteLine(db.Entry(item).State); //輸出Detached(遊離態)
去掉AsNoTracking
var item=db.Book.First(m=>m.Id==1); Console.WriteLine(db.Entry(item).State); //輸出UnChanged(持久態)
也就是加上AsNoTracking做出來的查詢操作,就和數據庫斷絕了關係(個人理解)這樣對性能也是一個極好的優化。
然後來說說在項目中對ef整體框架優化的使用。
C#是一門面向對象的語言無外乎就是封裝、繼承、多態。。。類可以繼承類,同樣接口也可以繼承接口。面向對象的思想就是每張表都應該有一個父類,只寫一遍,其他類繼承就好了不用重複去寫。
建立一個空白的解決方案,添加一個名為BaseEntity的類,你可以把它看做是所有類的基礎類(父類)。
重點來說說這個BaseEntity為什麼要作為基礎類,它的用意何在。
先看看裏面的寫了些什麼吧!
以前用int或者long作為主鍵自增長的數據類型,這樣後期數據非常龐大的時候可能會無法預估難免可能會出現重複的數據,這個時候對於整個系統來說就無法保障了。
Guid給我做出了一個計算,它是32位的数字加字母的組合,而且是特別長的不會重複的自增數,可以這樣去理解。
DateTime就是每條數據的創建時間,IsRemove就是作為數據邏輯刪除的標識,也就是說,每個類(表)都會存在這三個字段屬性。你可以通過DateTime對沒張表進行排序的操作。
using System; namespace Book.Models { public class BaseEntity { public Guid Id { get; set; }=Guid.NewGuid(); //計算32位,字母加数字,特別長的,不重複的,自增數 public DateTime DateTime { get; set; }=DateTime.Now; //每條數據的創建時間 public bool IsRemove { get; set; } //偽刪除的標識 } }
創建BookType類
using System.ComponentModel.DataAnnotations; namespace Book.Models { public class BookType:BaseEntity { [StringLength(20)] public string Name { get; set; } } }
創建Book類
using System.ComponentModel.DataAnnotations; namespace Book.Models { public class Book:BaseEntity { [StringLength(50),Required] public string Name { get; set; } public decimal Price { get; set; } } }
這兩個類都會繼承BaseEntity
在App.Config裏面寫上你自己的數據庫連接字符串
這樣的目的就是去生成數據庫,只做生成數據庫的操作。這裏只是在Models層寫了一次,在後期加上表示層還有在寫一遍!
接下來就是去通過指令遷移到數據庫,操作有三步:
- 啟動遷移:enable-migrations
- 添加遷移:add-migration ‘參數’
- 更新數據庫:update-database(如果你需要添加或者修改某個字段屬性,只需要進行第二步和第三步的操作即可!)
記住默認項目要選擇你的Models,如果是多個項目(我這裡是只有一個)就必須要把Models設為啟動項目,不然遷移指令可能不會起到作用
這樣就表示你的數據庫遷移的工作已經完成了,你可以在數據庫進行查看
接下來的工作就是準備分層的工作,什麼是分層?怎麼分?好處是什麼?
我的理解:聽某一個大佬說,代碼是一層一層寫的不是一行一行寫的。個人覺得拿捏了。原諒我沒去百度。。。
一直覺得對於面向對象的思想,一直覺得自己才疏學淺,學的只是皮毛,真是非常的慚愧。
剛好借這個機會來通過分層一起來學習學習。三層,在之前的學習中對於三層也只是找到表示層(UI)、業務邏輯層(BLL)、數據訪問層(DAL)。
個人覺得,這樣確實是解耦了,但是,並沒有將業務邏輯層的“業務邏輯”這一詞的功能發揮到極致,就只是簡單的從UI層get一下BLL層,BLL層簡單的get一下DAL層。。。(個人覺得並沒有解什麼耦。。。來自菜鳥的思考)
那我們可不可以封一個接口層,通過面向對象的繼承來去調整一下呢?
首先得明確接口是可以繼承接口的!
創建一個數據接口層IDAL,結構如下:引用Models層
為什麼寫這個接口呢?原因很簡單,就是將增刪改查等操作進行一個封裝,並且這不是普通的增刪改查,看代碼:
你可以把它看做是增刪改查的基礎接口,這個接口是一個泛型(比如你的商品表,用戶表等等…),它必須得基礎BaseEntity,並且這個T必須繼承與BaseEntity,也就是必須是BaseEntity的派生類。
關於IQueryable的好處註釋以寫!
這四個方法就是增刪改查,就算你有幾百張表,應該也只要寫一次增刪改查的操作就ok(也是來自菜鳥理解)
using System; using System.Linq; using Book.Models; namespace IDAL { //接口封裝增刪改查的方法它,是個泛型,並且要給一個約束,這個T必須是BaseEntity的派生類也就是子類,別的不行 public interface IBaseService<T> where T:BaseEntity { void Add(T t); void Edit(T t); void Remove(Guid id); T GetOne(Guid id); //查詢某一個對象 //IQueryable得到的是一個集合,它不會立刻給你去生成Sql語句,直到你需要拿到指定的某個結果后,再去給你生成Sql語句,比如根據條件,分頁,排序等操作獲得的集合數據 IQueryable<T> GetAll(); } }
既然有了接口,那麼肯定是要實現這個接口才能調用裏面的增刪改查的四個方法吧!既然如此,那麼就再寫一個DAL類庫,去實現這個接口層。
結構如下:引用EF以及Models和IDAL層,並且寫一個BaseService類去實現IBaseService接口裡面的方法
BaseService代碼如下:
using IDAL; using System; using System.Linq; using Book.Models; namespace DAL { public class BaseService<T> : IBaseService<T> where T:BaseEntity //指明這個T是誰,就是繼承BaseEntity的類 { public void Add(T t) { throw new NotImplementedException(); } public void Edit(T t) { throw new NotImplementedException(); } public IQueryable<T> GetAll() { throw new NotImplementedException(); } public T GetOne(Guid id) { throw new NotImplementedException(); } public void Remove(Guid id) { throw new NotImplementedException(); } } }
前面的 IBaseService<T>就是你實現的是哪個接口,後面的Where就是告訴這個接口要實現的T(泛型已經指定了是繼承BaseEntity的派生類)是誰。。。
可能這裏的Where作用還需要去理解一下,個人理解就是約束、條件之類的作用。
怎麼寫方法?代碼如下:
using IDAL; using System; using System.Data.Entity; using System.Linq; using Book.Models; namespace DAL { public class BaseService<T> : IBaseService<T> where T:BaseEntity,new() //指明這個T是誰,就是繼承BaseEntity的類 { private BookContext _db=new BookContext(); public void Add(T t) { //db.Books.Add(t); _db.Set<T>().Add(t); //Set<>就是返回一個DbSet實例,為什麼是T ,作用就是動態的訪問,無論是Book還是BookTypes,你給我什麼我就用T接就好了 _db.SaveChanges(); } public void Edit(T t) { _db.Entry(t).State = EntityState.Modified; //直接通過主題去作修改 _db.SaveChanges(); } public IQueryable<T> GetAll() { return _db.Set<T>().Where(m => !m.IsRemove).AsNoTracking(); //脫離持久態,變為遊離態,並且過濾掉了已被刪除的數據 } public T GetOne(Guid id) { return GetAll().First(m => m.Id == id); //直接通過GetAll在通過id查到你想要的數據 } public void Remove(Guid id) { var t=new T() { Id = id }; //這裡是偽刪除,T不能直接new,也是給個約束條件 new()即可,代表它有構造函數 _db.Entry(t).State = EntityState.Unchanged; //也是根據狀態去刪除 t.IsRemove = true; _db.SaveChanges(); } } }
這些增刪改查方法寫完后,就要去繼承了。原理實際上和BaseEntity差不多
既然增刪改查的方法都寫好了,那麼怎麼去才能調用到它呢?這個時候為什麼還需要去寫IBook和IBookType呢?。首先我們在之前是寫了IBaseService這個接口並且寫了對應的增刪改查方法,但是具體的實現是在DAL層所有,這兩個接口只需要繼承對應的IBaseService就好了,具體實現也是在DAL層,至於DAL層怎麼去寫,請看下面的內容。
這個時候泛型 T就其作用了,我們可以去寫對應的接口繼承IBaseService就好了。。
代碼如下:
IBookService:
namespace IDAL { public interface IBook:IBaseService<Book.Models.Book> { } }
IBookTypeService:
using Book.Models; namespace IDAL { public interface IBookTypeService:IBaseService<BookType> { } }
DAL層結構:
為什麼還要繼承BaseService?首先BaseService具體實現了繼承IBaseService的方法,所以,我們只需要繼承一下就可以調用到BaseService的增刪改查的方法了!
BookService代碼:
using Book.Models; using IDAL; namespace DAL { public class BookService:BaseService<Book.Models.Book>, IBookService { } }
BookTypeService代碼:
using Book.Models; using IDAL; namespace DAL { public class BookTypeService:BaseService<BookType>,IBookTypeService { } }
以上操作完成后,我們只需要實例化DAL層的Service方法就可以調用在UI層增刪改查了。
寫到這裏只是最最基礎的底層,如果還要完善需要進一步的改善改善。。。
如果有錯誤的地方請提出來,本人也是學生,大家都是抱着學習的心態去記錄和鞏固自己的知識點。。。。。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※教你寫出一流的銷售文案?
※超省錢租車方案
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※回頭車貨運收費標準