一、對象管理機制-複雜
爲更好的管理模型對象,EF提供了一套內部管理機制和跟蹤對象的狀態,保存對象一致性,使用方便,可是性能有所下降。html
二、執行機制-高度封裝
在EF中,全部的查詢表達式都會通過語法分析、解析SQL語句、而後調用底層的ADO.NET對象去執行,中間的這些環節致使性能有所下降。git
三、SQL語句-低效
EF採用映射機制將對象操做轉換成SQL語句,SQL語句通常的基於標準模塊生成的,不會進行特殊優化,和直接編寫SQL語句操做數據庫相比,效率會打折扣,複雜操做更爲明顯。github
狀態管理機制優化sql
AsNoTracing()方法
一、添加AsNoTracing()方法後,對象將不被狀態管理,查詢性能提升
二、返回的實體將再也不DbContext中緩存
三、適合場景:純粹查詢,不進行數據增刪改操做docker
禁用自動跟蹤變化數據庫
分析
一、關閉前,當執行Add()操做時將會耗費大量的性能,會致使DbContext遍歷全部緩存的Entry,比較原始值和當前值,而這個操做會很是耗費性能
二、關閉後,使用Add()方法告知DbContext中對象的變化便可,若是是刪除使用Remove()方法,若是是修改則還須要經過State屬性顯示告知變化
三、適合場景:大批量操做數據(添加、刪除、修改)c#
優化措施列表緩存
下面開始一一介紹安全
使用最新版的EF正式版本代替老的版本(除舊迎新哈哈),畢竟EF是微軟所重視的主流數據操做庫,每次升級版本優化效果都挺明顯的。ide
若使用延遲加載遍歷單個Model下的某一集合屬性,以下面的例子:
var user = db.Person.Single(a => a.Id == 1); foreach (var role in user.Roles) { Console.WriteLine(role.Name); }
每次咱們須要訪問屬性Role.Name的時候都會訪問數據,這樣累加起來的開銷是很大的。
EF默認使用延遲加載獲取導航屬性關聯的數據。
做爲默認配置的延遲加載,須要知足如下幾個條件:
context.Configuration.ProxyCreationEnabled = true;
context.Configuration.LazyLoadingEnabled = true;
導航屬性被標記爲virtual
這三個條見缺一不可。所以能夠選擇性禁用全局延遲加載或者是某一屬性的延遲加載.
這點其實也跟上面的同樣響應了一個原則:儘可能的減小數據庫的訪問次數,
var user = db.Person.Include(a=>a.Roles);
一次查詢將UserProfile與其Role表數據查詢出來
IQueryable返回的是查詢表達式,也就是說生成了SQL查詢語句可是卻尚未與數據庫進行交互。
IEnumerable則是已經執行查詢數據庫的操做且數據保存在了內存中
因此在進行條件拼接的時候必定要在IQueryable類型後面追加Where條件語句,而不是等到ToList以後再開始寫條件
錯誤的寫法:
db.Person.ToList().Where(a => a.IsDeleted == false);
正確的寫法:
db.Person.Where(a => a.IsDeleted == false).ToList();
這些寫法的意思就是把數據條件拼湊好,再訪問數據庫。不然從數據庫獲取所有數據後再過濾,假如數據很龐大幾十萬,那後果可想而知!
對於只讀操做,強烈建議使用AsNoTracking進行數據獲取,這樣省去了訪問EF Context的時間,會大大下降數據獲取所需的時間。
同時因爲沒有受到上下文的跟蹤緩存,所以取得的數據也是及時最新的,更利於某些對數據及時性要求高的數據查詢。
db.Person.Where(a => a.IsDeleted == false).AsNoTracking().ToList();
下面是本人編寫關於更改AsNoTracking數據Update的兩種方式測試與總結:
EntityDB db = new EntityDB(); var users = db.User.AsNoTracking().ToList(); foreach (var user in users) { db.Set<User>().Attach(user); } foreach (var user in users) { user.IsDeleted = true; //db.Entry(user).State=EntityState.Modified; } db.SaveChanges();
以上代碼我將未跟蹤的數據作Attach後賦值SaveChanges生成的SQL語句以下:
而採用直接賦值後Entry修改State狀態爲Modified
EntityDB db = new EntityDB(); var users = db.User.AsNoTracking().ToList(); /* foreach (var user in users) { db.Set<User>().Attach(user); }*/ foreach (var user in users) { user.IsDeleted = false; db.Entry(user).State=EntityState.Modified; } db.SaveChanges();
生成的SQL語句以下:
對比咱們得出結論第一種採用Attach後賦值的方法是執行的按需更新,也就是說更新哪一個字段就update它,而第二種則是無論更新了哪一個字段,生成的SQL語句都是更新所有。
爲何第一種方法中我Attach後僅僅只是給對象賦值且沒有修改State爲Modified,但EF卻能幫我修改數據值,那是由於
當SaveChanges時,將會自動調用DetectChanges方法,此方法將掃描上下文中全部實體,
並比較當前屬性值和存儲在快照中的原始屬性值。若是被找到的屬性值發生了改變,
此時EF將會與數據庫進行交互,進行數據更新,因此不用設置State爲Modified。
對於刪除操做則須要在Attach後設置 db.Entry(user).State = EntityState.Deleted;
借鑑於此,我又封裝了一個獨立的AttachList方法,此方法僅僅只是將由AsNoTracking 取得的數據附加到上下文中,由於不用關注以後的操做是Update或者Delete因此只用了Attach。
如下截圖代碼是直接從個人項目中摘取出來展現:
其中最關鍵的是性能上的提升(就是上述文字標記的地方),當查詢大量數據時,使用此方法比不使用而將其附加到上下文容器中,性能提高不是一點點。
對於某些特殊業務,咱們也可使用sql語句查詢實體,如下只是一個簡單的事例操做
SqlParameter[] parameter = { };
var user = db.Database.SqlQuery<User>("select * from user", parameter).ToList();
此方法得到的實體查詢是在數據庫(Database)上,實體不會被上下文跟蹤。
SqlParameter[] parameter = { }; var user = db.Set<User>().SqlQuery("select * from user", parameter).ToList();
此方法得到的實體查詢是被上下文跟蹤,因此能直接賦值後SaveChanges()。
var user = db.Set<User>().SqlQuery("select * from user").ToList(); user.Last().Name = "makmong"; db.SaveChanges();
固然一樣支持帶參數的查詢與存儲過程操做,我就不一一列出了此處只作點出便可。
咱們執行以下語句
var query = db.User.Where(a=>a.Name=="makmong").ToList();
生成的SQL語句
再試一個語句
var query = db.User.Where(a=>a.Name== DbFunctions.AsNonUnicode("makmong")).ToList();
生成的SQL語句
其中生成的SQL語句區別了,一個加了N,一個未加N,N是將字符串做爲Unicode格式進行存儲。
由於.Net字符串是Unicode格式,在上述SQL的Where子句中當一側有N型而另外一側沒有N型時,此時會進行數據轉換,也就是說若是你在表中創建了索引此時會失效代替的是形成全表掃描。
用 DbFunctions.AsNonUnicode 方法來告訴.Net將其做爲一個非Unicode來對待,此時生成的SQL語句兩側都沒有N型,就不會進行更多的數據轉換,也就是說不會形成更多的全表掃描。
因此當有大量數據時若是不進行轉換會形成意想不到的結果。
所以在進行字符串查找或者比較時建議用AsNonUnicode()方法來提升查詢性能。
你們可能都會碰到這種狀況就是Model實體擁有多個字段,可是查詢數據到頁面展現的時候可能只須要顯示那麼幾個字段,這個時候建議使用ViewModel查詢,
也就是說須要哪些字段就查詢哪些,而不是 「select *」將所有字段加載出來。此操做即出於安全考慮 (不該該將實體Model直接傳遞到View上面),同時查詢的字段減小 (可能就幾個) 對查詢性能也有所提高。
例:
var query = db.User.ToList();
對應的查詢語句爲:
接着新建ViewModel
public class UserViewModel { public int Id { get; set; } public string Name { get; set; } }
開始查詢:
var query = db.User.Select(a=>new UserViewModel() { Id = a.Id, Name = a.Name }).ToList();
對應的查詢語句爲:
咱們先來了解下Sqlserver中tinyint, smallint, int, bigint的區別
bigint:從-263(-9223372036854775808)到263-1(9223372036854775807)的整型數據,存儲大小爲 8 個字節。一個字節就是8位,那麼bigint就有64位
int:從-231(-2,147,483,648)到231-1(2,147,483,647)的整型數據,存儲大小爲 4 個字節。int類型,最大能夠存儲32位的數據
smallint:從-215(-32,768)到215-1(32,767)的整數數據,存儲大小爲 2 個字節。smallint就是有16位
tinyint:從0到255的整數數據,存儲大小爲 1 字節。tinyint就有8位。
因此對於有些範圍比較短的數值長度,例如枚舉類型值,徹底可使用byte類型替換int類型,對應生成數據庫tinyint類型以節省數據存儲。
如:
public CouponType CouponType { get; set; }
public enum CouponType : byte { RedBag = 0, Experience = 1, Cash = 2, JiaXiQuan = 3 }
對應的數據庫類型:
此時的CouponType字段對應數據庫就是一個tinyint類型
咱們先看下 SQL Server中DateTime與DateTime2的區別
DateTime字段類型對應的時間格式是 yyyy-MM-dd HH:mm:ss.fff ,3個f,精確到1毫秒(ms),示例 2014-12-03 17:06:15.433 。
DateTime2字段類型對應的時間格式是 yyyy-MM-dd HH:mm:ss.fffffff ,7個f,精確到0.1微秒(μs),示例 2014-12-03 17:23:19.2880929 。
咱們知道EF Model的DateTime對應的SQL類型是DateTime
例:
public DateTime CreateDateTime { get; set; }
對應的數據庫實體類型:
可是在業務操做中不少時間值咱們僅僅只須要精確到秒就夠了(特殊業務除外),
那多餘的毫秒數既無用又佔數據庫存儲(逼死處女座),既然是優化操做那麼咱們是否能夠去除毫秒數而只存儲到秒呢?例:2014-12-03 17:06:15
So咱們可使用特性Attribute及抽象類PrimitivePropertyAttributeConfigurationConvention來達到這一目的。
很少說直接上代碼:
[AttributeUsage(AttributeTargets.Property)]
public sealed class DateTime2PrecisionAttribute : Attribute { public DateTime2PrecisionAttribute(byte precision = 0) { Precision = precision; } public byte Precision { get; set; } }
public class DateTime2PrecisionAttributeConvention: PrimitivePropertyAttributeConfigurationConvention<DateTime2PrecisionAttribute> { public override void Apply(ConventionPrimitivePropertyConfiguration configuration, DateTime2PrecisionAttribute attribute) { if (attribute.Precision > 7) { throw new InvalidOperationException("Precision must be between 0 and 7."); } configuration.HasPrecision(attribute.Precision); configuration.HasColumnType("datetime2"); } }
理解一下代碼,第一句中的AttributeTargets.Property表示能夠對屬性(Property)應用特性(Attribute)
而構造函數DateTime2PrecisionAttribute則指定了要應用的datetime的精度值。
而最後兩句
configuration.HasPrecision(attribute.Precision); configuration.HasColumnType("datetime2");
則是將咱們所定義的類型精度與對應聲明數據類型附加給要標記的實體類型。
最後還須要將DateTime2PrecisionAttributeConvention方法註冊到咱們的DbContext中
public virtual DbSet<User> User { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Add(new DateTime2PrecisionAttributeConvention()); }
如今咱們再使用此特性在上面的屬性CreateDateTime中看下效果吧
結果圖:
是否是感受不錯。固然基於此拓展,咱們也能夠擴展咱們想要的Model數據類型,如:控制decimal的精度(2位或4位小數),改邊nvarchar(max)爲咱們想要的長度類型(具體狀況看業務再優化吧)。
1.EF實現指定字段的更新
在以往的數據更新操做中咱們使用EF的修改都是先查詢一次數據附加到上下文中,而後給須要修改的屬性賦值,雖然說EF可以自動跟蹤實體作到按需更新,但更新前查詢不只沒有必要,並且增長了額外的開銷。EF刪除和修改數據只能先從數據庫取出,而後再進行刪除.
當進行以下操做時:
delete from user where Id>5; update user set Name=」10」;
咱們須要這樣操做
var t1 = db.User.Where(t => t.Id > 5).ToList(); foreach (var t in t1) { db.User.Remove(t); } db.SaveChanges(); var t2 = db.User.ToList(); foreach (var t in t1) { t.Name = "ceshi"; } db.SaveChanges();
有沒辦法作到一條語句操做的更改呢?如「update user set name=’張三’where id=1」。
此時就須要使用EF的擴展庫EntityFramework.Extended了。
在github中提供了一個EF擴展庫https://github.com/loresoft/EntityFramework.Extended
在VS能夠直接經過NuGet安裝
安裝完成後試驗下:
固然須要先引用:
using EntityFramework.Extensions;
編寫代碼測試及查看結果:
EntityDB db = new EntityDB(); db.User.Where(a => true).Update(a => new User() {Name = "ceshi"});
EntityDB db = new EntityDB(); db.User.Where(a => true).Delete();
嗯,至於具體選擇怎麼用,看業務分析哈。
2.批量查詢功能
例如:在分頁查詢的時候,須要查詢結果數,和結果集
EF作法:查詢兩次
var q = db.User.Where(u => u.Name.StartsWith("a")); var count = q.Count(); var data = q.Skip(10).Take(10).ToList();
EF擴展庫的作法:一次查詢
var q = db.User.Where(t => t.Name.StartsWith("a")); var q1 = q.FutureCount(); var q2 = q.Skip(10).Take(10).Future(); var data = q2.ToList(); var count = q1.Value;
3.查詢緩存功能
咱們如今的後臺項目權限管理模塊,全部的菜單項都是寫進數據庫裏,不一樣的角色用戶所獲取展現的菜單項各不相同。
項目導航菜單就是頻繁的訪問數據庫致使性能低下(一開始獲得1級菜單,而後經過1級獲取2級菜單,2級獲取3級)
解決方法就是第一次查詢後把數據給緩存起來設定緩存時間,而後一段時間繼續查詢此數據(譬如整個頁面刷新)則直接在緩存中獲取,從而減小與數據庫的交互。
代碼以下:
var users = db.User.Where(u => u.Id > 5).FromCache(CachePolicy.WithDurationExpiration(TimeSpan.FromSeconds(30)));
若是在30秒內重複查詢,則會從緩存中讀取,不會查詢數據庫
咱們再提出二個問題那就是,
1:第一次查詢緩存數據修改後(如:保存到數據庫)緊接着繼續查詢一次,因爲緩存時間沒有失效,此時在緩存中查詢的數據是剛剛修改的最新的嗎?
2:在不一樣的上下文中緩存獲取結果是同樣的嗎?
寫代碼測試看下:
上圖中我在第一個上下文中得到數據緩存,而後給Name賦值」sss」,固然此處爲了測試緩存是否更新因此我沒有作SaveChanges()的操做,而後接着從緩存中獲取數據,由結果可知此緩存值也相應的更改了。
所以在一段時間內即便操做修改了數據值也只須要在更改的時候操做一次數據庫,減小了與數據庫的交互。
另外須要注意的是更改的時候能夠根據操做結果選擇是否繼續緩存,例如數據更改失敗可是緩存卻改動了,下次取值數據就會不一致,因此當咱們在更新數據庫失敗時就能夠選擇移除緩存調用RemoveCache()方法。
當數據庫的表及數據達到必定規模後咱們想到的優化就有分庫,分表之類的優化操做。
對於以前的ADO.NET來講分庫是一件很普通的操做。
好比下面的非跨數據庫查詢語句:
SELECT Name FROM dbo.User WHERE ID=1
跨數據庫查詢語句:
SELECT Name FROM MaiMangAdb.dbo.blog_PostBody WHERE ID=1
咱們知道EF的DbContext中已經指定了鏈接字符串
public EntityDB() : base("DefaultConnection")
<connectionStrings> <add name="DefaultConnection" connectionString="Data Source=.;Initial Catalog=EFStudy;Integrated Security=True;" providerName="System.Data.SqlClient" /> </connectionStrings>
也就是說全部的上下文操做都是基於這個數據庫來操做的,那咱們就不能用ADO.NET那套,多個查詢配多個連接去操做數據庫。
固然大神們也給出了一套方法,並且也是簡單明瞭。那我也就直接將其移植過來記錄一下吧。
方法就是給數據庫添加SYNONYM 同義詞,我在此演示下
建立2張Model表User和Role
public class User { [Key] public int Id { get; set; } public string Name { get; set; } public bool IsDeleted { get; set; } [DateTime2Precision] public DateTime CreateDateTime { get; set; } } public class Role { [Key] public int Id { get; set; } public string Name { get; set; } }
並添加一條語句:
EntityDB db = new EntityDB(); db.User.Add(new User { Id = 1, Name = "ddd" ,CreateDateTime = DateTime.Now}); db.Role.Add(new Role() {Id = 1, Name = "admin"}); db.SaveChanges();
運行查看數據庫:
如今數據庫表及內容都有了。而後我要把User表及內容移植到另外一個數據庫中,且不影響當前的EF操做。
建立新的數據庫EFSYNONYM並添加User表,表結構和EFStudy中的User一致。
而後在EFStudy中刪除表User且建立同義詞
CREATE SYNONYM [dbo].[Users] FOR [EFSYNONYM].[dbo].[Users]
效果如圖
此時的User和Role已經分別存在於不一樣的數據庫裏面,咱們來插入查詢數據操做下
至此分庫成功。固然此方法也有個缺點就是分庫表和主表間由同義詞關聯而沒法創建主外鍵關係(其實當數據量達到必定級別後聯合join查詢反而不如分開屢次查詢來得快,
且因爲在同一個上下文中,不用太過於關心由數據屢次鏈接開關而產生影響,凡事有利弊總得有個最優是吧),所以咱們能夠把一些獨立的容易過時的數據表給移植到單獨的數據庫,利於管理同時也利於優化查詢。
參考資料
https://www.cnblogs.com/ahao214/p/9431910.html
http://blog.jd-in.com/947.html