Tip 技術的選型受技術先進性、技術持續性、技術普及度、推行力度的影響。 我也很無奈,一大把年紀了又要從新學一種ORMapping框架。
說實話,這是我用過的最複雜的ORMapping框架了。 EntityFramework 微軟推出的ORMapping方案,可用代碼來描述(CodeFirst) 實體類和數據庫是對應的,可自動生成數據庫表 實體類和數據操做是分開的。必須另外寫一套代碼操做數據。實體類可歸爲Model層,操做類可歸爲DAL(或BLL)層 EF6.x 目前只能經過 NuGet 進行管理,因此,首先肯定你的 Visual Studio 中的 NuGet 爲最新版本(最低支持 2.8.3,最新版本 3.0)。 本文以最新語法爲準,與老版本區別之處可參考:http://blog.csdn.net/chenyq2008/article/details/41659205 又出來一個 EntityFramwork core(原先叫 EF 7,基於.net core),區別見 http://www.cnblogs.com/n-pei/p/4274907.html http://www.cnblogs.com/xishuai/p/ef7-develop-note.html https://docs.microsoft.com/en-us/ef/efcore-and-ef6/index 架構 EDM (實體數據模型):EDM包括三個模型,概念模型、 映射和存儲模型。 概念模型 ︰ 概念模型包含模型類和它們之間的關係。獨立於數據庫表的設計。 存儲模型 ︰ 存儲模型是數據庫設計模型,包括表、 視圖、 存儲的過程和他們的關係和鍵。 映射 ︰ 映射包含有關如何將概念模型映射到存儲模型的信息。 LINQ to Entities ︰ LINQ to Entities 是一種用於編寫針對對象模型的查詢的查詢語言。它返回在概念模型中定義的實體。 Entity SQL: Entity SQL 是另外一種爐相似於L2E的言語,但相給L2E要複雜的多,因此開發人員不得不單獨學習它。 Object Services(對象服務):是數據庫的訪問入口,負責數據具體化,從客戶端實體數據到數據庫記錄以及從數據庫記錄和實體數據的轉換。 Entity Client Data Provider:主要職責是將L2E或Entity Sql轉換成數據庫能夠識別的Sql查詢語句,它使用Ado.net通訊向數據庫發送數據可獲取數據。 ADO.Net Data Provider:使用標準的Ado.net與數據庫通訊 三種模式 CodeFirst 先定義實體類,用實體類生成數據庫 Database First 從數據庫生成實體類 Model First 使用Visual Studio實體設計器,設計ER,同時生成Entity類和DB 參考 http://www.cnblogs.com/libingql/p/3349866.html https://github.com/aspnet/EntityFramework/wiki https://docs.microsoft.com/en-us/ef/ http://www.cnblogs.com/xuf22/articles/5513283.html http://www.entityframeworktutorial.net/code-first-with-entity-framework.aspx http://www.cnblogs.com/guomingfeng/category/480047.html 安裝 install-package entityframework ---------------------------------------------- 數據庫上下文(DbContext) ---------------------------------------------- DbContext主要負責如下活動: EntitySet: Dbctx包含了全部映射到表的entities Querying:將Linq-To-Entities轉譯爲Sql併發送到數據庫 Change Tracking:從數據庫獲取entities後保留並跟蹤實體數據變化 Persisting Data:根據entity狀態執行Insert、update、delete命令 Caching:Dbctx的默認第一級緩存,在上下文中的生命週期中存儲entity Manage Relationship:Dbctx在DbFirst模式中使用CSDL、MSL、SSDL管理對象關係,Code first中使用fluent api 管理關係 Object Materialization:Dbctx將物理錶轉成entity實例對象 示例 using System.Data.Entity; namespace App { public class AppContext : DbContext { // 構造函數,此處指定了數據庫鏈接字符串 public AppContext() : base("Default") {} // 實體集合 public DbSet<Dept> Depts { get; set; } public DbSet<User> Users { get; set; } public DbSet<Role> Roles { get; set; } // 建立數據庫 protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); Database.SetInitializer(new MigrateDatabaseToLatestVersion<AppContext, AppBoxMigrationConfiguration>()); } } } using (var ctx = newSchoolDBEntities()) { //Can perform CRUD operation using ctx here.. } ---------------------------------------------- 實體類(Entity) ---------------------------------------------- POCO類(不依賴於任何框架的實體類) using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace App { public class User : IKeyID { // 主鍵 [Key] public int ID { get; set; } // 必填;長度約束 [Required, StringLength(50)] public string Name { get; set; } // 可空屬性 public DateTime? Birthday { get; set; } // 關聯對象 public virtual ICollection<Role> Roles { get; set; } public virtual ICollection<Title> Titles { get; set; } public virtual Dept Dept { get; set; } } } 外鍵約束(父子表關係)RelationShip public virtual ICollection<Role> Roles { get; set; } ---------------------------------------------- DbSet ---------------------------------------------- usage public virtual DbSet<User> Users{get; set;} method Add(entity) : 添加 AsNoTracking<entity> : 不緩存,可增長性能 Attach(entity) : 附加實體 Find(int) : 查找(根據主鍵) Create : 建立(注意建立後還需Add操做) Include : 加載附加子對象 Remove : 刪除實體 SqlQuery : 執行Sql語句 ---------------------------------------------- 標註 http://www.cnblogs.com/libingql/p/3352058.html ---------------------------------------------- Entity Framework Code First 約定 表名爲:類名的英語複數形式,建立的表全部者爲dbo 主鍵爲:ID 或 類名 + ID 對於int類型的主鍵,會自動的設置其爲自動增加列 用Annotation標註 可以使用System.ComponentModel.DataAnnotations來指定表名、字段名、字段約束等信息 [Table("Product", Schema = "dbo")] // 表名、全部者 [Key] // 主鍵字段 [Column("MenuID")] // 字段名 [Required] // 字段必填 [TypeName = "MONEY")] // 字段類型 [StringLength(50)] // 字段長度 [DatabaseGenerated(DatabaseGeneratedOption.None)] // 主鍵不自動增加 [DatabaseGenerated(DatabaseGeneratedOption.Identity)] // 主鍵自增加 [DatabaseGenerated(DatabaseGeneratedOption.Computed)] // 該列是經過其它列計算得出的,不會將其持久化到數據庫中。只讀?相似視圖。 [NotMapped] // 屬性不匹配數據庫字段 [TimeStamp] // 時間戳。詳見《併發處理》章節 [ConcurrencyCheck ] // 併發檢測。詳見《併發處理》章節 [ForeignKey("CatID")] // 外鍵名稱。 [Index("TitleIndex", IsUnique = true)] // 惟一鍵 eg [Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)] public Guid SocialSecurityNumber { get; set; } 用代碼方式指定(FluentAPI) public class XXXContext : DbContext { ... protected override void OnModelCreating(DbModelBuilder modelBuilder) { // 設置 Blog 表 var entity = modelBuilder.Entity<Blog>(); entity .ToTable("MyBlog", "dbo") // 表名(EF6) .HasOptional(x => x.ParentMessage) // 表名(EF7) .HasKey(x => x.BlogId); // 主鍵 .HasKey(t => new { t.KeyID, t.CandidateID }) // 複合主鍵 .HasMany(r => r.Users) // 1對多關係 .WithMany(u => u.Roles) // .Map(x => x.ToTable("RoleUsers") // 關聯表 .MapLeftKey("RoleID") // 左鍵關聯 .MapRightKey("UserID")) // 右鍵關聯 .Ignore(t => t.Remark) // 忽略屬性 .HasRequired(t => t.Category) // Category的值來自外鍵 .WithMany(t => t.Products) // .HasForeignKey(d => d.CatID) // } ; // 設置 Blog.Title 字段 entity .Property(x => x.Title) // 字段 .IsRequired() // 必填 .HasMaxLength(50) // 最大長度 .HasColumnName("TitleColumn"); // 字段名 .HasColumnType("text") // 字段類型 .HasPrecision(18, 2) // 數字類型的精度設置 .GenerateValuesOnAdd(false) // 不自增 .ManyToOne(n => n.Entity, t => t.ChildEntities)// 多對1 .ForeignKey(t => t.EntityId) // 外鍵 ; // 可統一設置全部庫表前綴 modelBuilder.Types().Configure(f => f.ToTable("TablePrefix_" + f.ClrType.Name)); } } 或更復雜一點,將配置獨立建一個類 http://download.csdn.net/detail/zhanxueguang/8950589 新建表配置類 using System.Data.Entity.ModelConfiguration; public partial class BlogConfig : EntityTypeConfiguration<Blog> { public BlogConfig() { this.ToTable("Blog"); //表名 this.HasKey(x => x.BlogId); //設置主鍵 this.Property(x => x.Title).IsRequired().HasMaxLength(50); //設置字段屬性,約束 not null, 最大長度50 this.Property(x => x.Content).HasColumnType("text"); //設置字段類型爲 text this.Property(x => x.CreateTime).IsRequired(); // 設置字段約束 not null this.HasMany(x => x.Comments); // 設置導航字段, Blog: Comment --> one: many } } 註冊配置類 using System; using System.Linq; using System.Data.Entity; using System.Reflection; using System.Data.Entity.ModelConfiguration; namespace CodeFirstSample { public partial class CodeFirstContext : DbContext { public CodeFirstContext() : base("CodeFirstContext"){} protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new BlogConfig()); base.OnModelCreating(modelBuilder); } // 獲取相關模型的數據集,使用 new 關鍵字來隱藏父類的 Set<> 方法 public new IDbSet<TEntity> Set<TEntity>() where TEntity : class { return base.Set<TEntity>(); } } } ---------------------------------------------- 操做數據(CRUD) 三種查詢方式 1) LINQ to Entities 2) Entity SQL 3) Native SQL ---------------------------------------------- Query LINQ to Entities: using (var ctx = newSchoolDBEntities()) { var query = ctx.Students.where(s => s.StudentName == "Bill"); var student = query.FirstOrDefault<Student>(); } var query = ctx.Customers.Where(r => r.Address.City == city); var query = ctx.Customers.Where("r.Address.City = '"+city+"'"); // 如今不能用了 var query = ctx.Customer.Where(r => r.CustomerID.Contains("V")); var query = ctx.Customer.Where("it.CustomerID LIKE @CustomerID", new ObjectParameter("CustomerID","%V%")); LINQ Query syntax: using (var ctx = new SchoolDBEntities()) { var query = from st in ctx.Students where st.StudentName == "Bill"select st; var student = query.FirstOrDefault<Student>(); } var query = from c in ctx.Customers where c.Address.City == city select c; Entity SQL: using (var ctx = new SchoolDBEntities()) { string sql = "SELECT VALUE st FROM SchoolDBEntities.Students AS st WHERE st.StudentName == 'Bill'"; var query = db.CreateQuery<Orders>("SELECT VALUE Orders FROM Orders WHERE Orders.OrderDate < @dt;", ps); } var query = ctx.CreateQuery<Customers>("SELECT VALUE c FROM Customers AS c WHERE c.Address.City = @city", new ObjectParameter("city", city)); Entity SQL & EntityClient var objctx = (ctx as IObjectctxAdapter).Objectctx; var student = objctx.CreateQuery<Student>(sqlString); var newStudent = student.First<Student>(); using (var conn = new EntityConnection("name=SchoolDBEntities")) { conn.Open(); EntityCommand cmd = con.CreateCommand(); cmd.CommandText = "SELECT VALUE st FROM SchoolDBEntities.Students as st where st.StudentName='Bill'"; var dict = new Dictionary<int, string>(); using (EntityDataReader rdr = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.CloseConnection)) { while (rdr.Read()) { int a = rdr.GetInt32(0); var b = rdr.GetString(1); dict.Add(a, b); } } } Native SQL(數據庫平臺相關,不推薦使用) using (var ctx = new SchoolDBEntities()) { var studentName = ctx.Students.SqlQuery("Select studentid, studentname, standardId from Student where studentname='Bill'").FirstOrDefault<Student>(); } 評價 解析步驟(都是解析爲CommandTree,再解析爲數據庫依賴的sql語句,本質上並沒有區別) Linq to Entity ---> Command Tree --> SQL --> ADO.net DataProvider --> DB Entity SQL 都是數據庫平臺無關的 Entity SQL 轉換的SQL性能會好一點 Linq to Entity 強類型特性,編譯時檢查 建議臨時查詢選擇EntityCLient,若是是操做數據那隻能採用ObjectService CRUD CREATE ctx.Users.Add(new User() { UserName = "New Student" }); ctx.SaveChanges(); READ var users = ctx.Users.ToList<User>(); var user = ctx.Person.Single(a = >a.Id == 1); ctx.Users.Where(...).ToList(); ctx.Users.Find(...).FirstOrDefault() 字符串比較 // where year >= yearFrom and year <= yearTo string yearFrom = this.txtYearFrom.Text; string yearTo = this.txtYearTo.Text; if (!string.IsNullOrEmpty(yearFrom) && !string.IsNullOrEmpty(yearTo)) q = q.Where(t => t.Year.CompareTo(yearFrom)>=0 && t.Year.CompareTo(yearTo)<=0); if (!string.IsNullOrEmpty(yearFrom) && string.IsNullOrEmpty(yearTo)) q = q.Where(t => t.Year.CompareTo(yearFrom) >= 0); if (string.IsNullOrEmpty(yearFrom) && !string.IsNullOrEmpty(yearTo)) q = q.Where(t => t.Year.CompareTo(yearTo) <= 0); UPDATE 方法一:先找到》更改屬性》SaveChanges public UpdateUserName(int id, string userName) { var user = ctx.Users.Where(s => s.ID == id).FirstOrDefault<Student>(); user.UserName = userName; ctx.SaveChanges(); } 方法二:先設置鍵值》Attach》更改屬性》SaveChanges(至關於直接寫update語句) public UpdateUserName(int id, string userName) { User user = new User{ID = id}; var entity = ctx.Users.Attach(user); entity.UserName = userName; ctx.SaveChanges(); } 方法三:手動修改EntityState public UpdateUserName(int id, string userName) { User user = new User{ID = id, UserName=userName}; var entity = ctx.Entity(user); entity.State = EntityState.Modified; entity.Property("UserName").IsModified = true; ctx.SaveChanges(); } DELETE 方法1:先找到》再刪除》SaveChanges var user = ctx.Users.FirstOrDefault(m => m.ID = id); ctx.Users.Remove(user); ctx.SaveChanges(); 方法2:本身建立對象》附加》刪除》SaveChanges var user = new User{ID = id}; ctx.User.Attach(user); ctx.Remove(user); ctx.SaveChanges(); 方法3:本身建立對象》放入EF容器》修改狀態》SaveChanges var user = new User{ID = id}; var entry = ctx.Entry(user); entry.State = EntityState.Deleted; ctx.SaveChanges(); 注:在建立對象的時候,須要指定主鍵列才行 方法4: 使用擴展 https://github.com/loresoft/EntityFramework.Extended ctx.Users.Delete(u => u.Id = id); 關聯表 新增 int[] roleIDs = StringUtil.GetIntArrayFromString(hfSelectedRole.Text); item.Roles = new List<Role>(); foreach (int roleID in roleIDs) { Role role = new Role { ID = roleID }; DB.Roles.Attach(role); item.Roles.Add(role); } 刪除 清除用戶表中的部門字段數據 DB.Users.Include(u => u.Dept) .Where(u => userIDs.Contains(u.ID)) .ToList() .ForEach(u => u.Dept = null) ; 或者反過來(刪除部門表的User列表信息) Dept dept = DB.Depts.Include(d => d.Users) .Where(d => d.ID == deptID) .FirstOrDefault(); foreach (int userID in userIDs) { User user = dept.Users.Where(u => u.ID == userID).FirstOrDefault(); if (user != null) dept.Users.Remove(user); } 更新 先刪光,再添加 int[] roleIDs = StringUtil.GetIntArrayFromString(hfSelectedRole.Text); int[] addRoleIDs = roleIDs.Except(item.Roles.Select(r => r.RoleID)).ToArray(); int[] removeRoleIDs = item.Roles.Select(r => r.RoleID).Except(roleIDs).ToArray(); foreach (int roleID in addRoleIDs) { var role = new Role { RoleID = roleID }; DB.Roles.Attach(role); item.Roles.Add(role); } foreach (int roleID in removeRoleIDs) { item.Roles.Remove(item.Roles.Single(r => r.RoleID == roleID)); } 脫機實體以及狀態 Attach Attach用來將某個已知存在於數據庫中的實體從新加載到上下文中。SaveChanges不會嘗試將Attached的實體插入到數據庫中,由於這個實體假設已經存在於數據庫中。 User admin = DB.Users.Where(u => u.Name == "admin").FirstOrDefault(); Dept dept = new Dept { ID = 1 }; DB.Depts.Attach(dept); admin.Dept = dept; DB.SaveChanges(); 若是實體已經被加載到EF上下文中,此時再次Attach同一個對象,就會出錯: DB.Depts.Find(1); User admin = DB.Users.Where(u => u.Name == "admin").FirstOrDefault(); Dept dept = new Dept { ID = 1 }; DB.Depts.Attach(dept); admin.Dept = dept; DB.SaveChanges(); 解決辦法: 先在EF的Local緩存中查找對象,若是找不到再Attach新對象 EntityState 有如下狀態 Added Deleted Modified Unchanged Detached eg var student = new Student() { StudentName = "New Student" }; student.StudentAddress = new StudentAddress() { Address1 = "Address", City = "City1" }; using (var ctx = newSchoolDBEntities()) { ctx.Students.Attach(student); // 獲取實體狀態 EntityState var studentEntry = ctx.Entry(disconnectedStudent); var addressEntry = ctx.Entry(disconnectedStudent.StudentAddress); Console.WriteLine("Student EntityState: {0}",studentEntry.State); Console.WriteLine("StudentAddress EntityState: {0}",addressEntry.State); } local https://msdn.microsoft.com/en-us/data/jj592872 using (var context = new BloggingContext()) { // Load all blogs from the database into the context context.Blogs.Load(); // Add a new blog to the context context.Blogs.Add(new Blog { Name = "My New Blog" }); // Mark one of the existing blogs as Deleted context.Blogs.Remove(context.Blogs.Find(1)); // Loop over the blogs in the context. Console.WriteLine("In Local: "); foreach (var blog in context.Blogs.Local) { Console.WriteLine( "Found {0}: {1} with state {2}", blog.BlogId, blog.Name, context.Entry(blog).State); } // Perform a query against the database. Console.WriteLine("\nIn DbSet query: "); foreach (var blog in context.Blogs) { Console.WriteLine( "Found {0}: {1} with state {2}", blog.BlogId, blog.Name, context.Entry(blog).State); } } ---------------------------------------------- 多表查詢 ---------------------------------------------- 多表關聯查詢 var q = (from exp in Common.Db.RptImportExports join invest in Common.Db.RptInvests on exp.Month equals invest.Month join consume in Common.Db.RptConsumes on exp.Month equals consume.Month select new { exp.Month, exp.ExpInc2, invest.LimitInvestInc, consume.LimitRetailInc2 }); if (!this.txtFrom.Text.IsNullOrEmpty()) q = q.Where(t => t.Month.CompareTo(this.txtFrom.Text) >= 0); if (!this.txtTo.Text.IsNullOrEmpty()) q = q.Where(t => t.Month.CompareTo(this.txtTo.Text) <= 0); 注意: join on 語句不能左右顛倒,會報錯 q 是一個IQueryable<~a>匿名類,後面的Where語句會自動檢測到Month屬性 對應的Lambda表達式該怎麼寫? ---------------------------------------------- 執行SQL ---------------------------------------------- 返回實體 using (var ctx = newSchoolDBEntities()) { //列名必須要Entity屬性匹配 var studentList = ctx.Students.SqlQuery("Select * from Student").ToList<Student>(); } 返回非實體類型 using (var ctx = newSchoolDBEntities()) { //Get student name of string type string studentName = ctx.Database.SqlQuery<string>("Select studentname from Student where studentid=1").FirstOrDefault<string>(); } 執行SQL命令 using (var ctx = new SchoolDBEntities()) { int noOfRowUpdated = ctx.Database.ExecuteSqlCommand("Update student set studentname ='changed student by command' where studentid=1"); int noOfRowInserted = ctx.Database.ExecuteSqlCommand("insert into student(studentname) values('New Student')"); int noOfRowDeleted = ctx.Database.ExecuteSqlCommand("delete from student where studentid=1"); } ---------------------------------------------- 事務(Transaction) ---------------------------------------------- 隱式事務 try { Product prod1 = db.Products.First(p => p.ProductID == 4); Product prod2 = db.Products.First(p => p.ProductID == 5); prod1.UnitsInStock -= 3; prod2.UnitsInStock -= 5;// 錯誤:庫存 數量的單位不能是負數 db.SubmitChanges(); // 要麼所有成功要麼所有失敗 } catch (System.Data.SqlClient.SqlExceptione) { //執行異常處理 } 明顯式事務(TransactionScope) using (Transcope ts = new TransactionScope()) { try { Product prod1 = db.Products.First(p => p.ProductID == 4); Product prod2 = db.Products.First(p => p.ProductID == 5); prod1.UnitsInStock -= 3; prod2.UnitsInStock -= 5;// 錯誤:庫存 數量的單位不能是負數 db.SubmitChanges(); // 要麼所有成功要麼所有失敗 } catch (System.Data.SqlClient.SqlExceptione) { //執行異常處理 } } ---------------------------- using (var context = new BlogDbContext()) { using (TransactionScope transaction = new TransactionScope()) { ...... context.SaveChanges(); ...... context.SaveChanges(); // 提交事務。若是本語句前出現任何錯誤,全部操做都會回滾。 transaction.Complete(); } } ---------------------------------------------- 緩存 ---------------------------------------------- EF 只是對查詢條件作了緩存,而不是緩存查詢的結果集 在DbContext生命週期內,實體類是能夠訪問的。 無比懷念 Gentle.net 框架,它是在Application級別進行緩存的。 DbContext未關閉前 EF維護了若干實體的狀態(Entry.State) 能夠根據狀態來判斷實體是否有更新,若無更新便可直接調出來使用 強制不緩存(System.Data.Entity.DbExtensions.AsNoTracking) var people = context.People.Where(p => p.PersonID > 100).AsNoTracking().ToList(); var monkey = from m in _dataContext.Monkeys.AsNoTracking() where m.MonkeyId == monkeyId select m) .FirstOrDefault(); 強制從數據庫中更新緩存 var service = (serviceAgent as PersonAccountServiceAgent); var context = ((System.Data.Entity.Infrastructure.IObjectContextAdapter)service.DBConn.DataBase).ObjectContext; context.Refresh(System.Data.Objects.RefreshMode.StoreWins, CurrentAccount); EF老版本沒有緩存的,不知道EF的緩存可不可靠,不放心的話用HttpContext的緩存,如 if( HttpContext.Cache["temp"]==null) HttpContext.Cache["temp"] = DataContext.info.where(p => p.id == id).FirstOrDefault(); return HttpContext.Cache["temp"] as List<Object>(); 使用 EntityFrame.Extended 有緩存處理 詳見 EntityFrame.Extened 章節 ---------------------------------------------- 併發處理 http://www.aichengxu.com/other/6164839.htm ---------------------------------------------- 併發處理 方案 悲觀併發:A在讀取某條記錄時,就單獨霸佔了這個記錄,B沒法獲? 1鬯屎鮮菥赫ち業某『希換崬此浪侍狻? 樂觀併發:A/B均可以讀取某條記錄時,若A修改了,B提交修改時會提示(可選擇覆蓋或取消) 說明 樂觀鎖並不適合處理高併發的場景,少許的數據衝突纔是樂觀併發的初衷。 悲觀鎖一樣也不適合處理高併發,特別在加鎖成本比較大的時候。 若是項目併發量確實大, 那就能夠考慮採用其餘技術實現,好比 消息隊列等 EF CodeFirst 只支持樂觀併發 步驟 (1)添加RowVersion字段,類型爲TimeStamp,且一個類只能有一個此標示 [Timestamp] public byte [] RowVersion { get; set; } (2)EF更新實體時會先檢查RowVersion,若是發現RowVersion不一致,則拋出DbUpdateConcurrencyException異常 (3)檢測到異常後可考慮重加載數據 // 建立一個ctx取的第一條數據,修改 可是不提交 BlogContext fircontext = new BlogContext(); Blog firstblog = fircontext.Blogs.FirstOrDefault(); firstblog.BlogName = "哈哈,被改掉了" ; // 建立另外一個ctx仍是取第一條數據,修改並提交 BlogContext secContext = new BlogContext(); Blog secondBlog = secContext.Blogs.FirstOrDefault(); secondBlog.BlogAuthor = "JasonShen"; secContext.SaveChanges(); // 這個時候再提交第一個ctx所作的修改 try { // 這是後會發現如今的數據,已經和剛進入時發生了變化,故報錯 fircontext.SaveChanges(); Console.WriteLine("保存成功" ); } catch(Exception e) { Console.WriteLine("保存失敗" ); // 從新從數據庫加載數據 e.Entries.Single().Reload(); Console.WriteLine(firstblog.BlogName); } 針對單個字段的併發 有些時候並不須要控制針對整條記錄的併發,只須要控制某個列的數據 不會出現髒操做就ok 這個時候可以使用ConcurrencyCheck 標示 public class User { ... [ConcurrencyCheck ] public string CertID { get; set; } } 如下代碼中會捕捉到併發異常 BlogContext firContext = new BlogContext (); User u1 = firContext.Users.FirstOrDefault(); BlogContext secContext = new BlogContext (); User u2 = secContext.Users.FirstOrDefault(); u2.certID= "22222"; secContext.SaveChanges(); try { u1.certID = "33333" ; firContext.SaveChanges(); } catch (Exception e) { Console .WriteLine("併發報錯" ); } ---------------------------------------------- 貪婪加載、懶惰加載、定向加載 ---------------------------------------------- 惰性加載:延遲加載對象關聯的實體,用到時再加載,EF默認爲LazyLoading var students = ctx.Students.ToList<Student>(); var student = students[0]; var address = std.StudentAddress; // 地址是動態加載的,此時纔去查詢 貪婪加載:使用Include(),自動加載關聯實體 using (var ctx = new SchoolDBEntities()) { var res = (from s in ctx.Students.Include("Standard") where s.StudentName == "Student1" select s).FirstOrDefault<Student>(); } 定向加載:Reference()和Collection() 方法 using (var ctx = new SchoolDBEntities()) { ctx.Configuration.LazyLoadingEnabled = false; // 加載學生 IList<Student> studList = ctx.Students.ToList<Student>(); Student std = studList.Where(s => s.StudentID == 1).FirstOrDefault<Student>(); // 加載指定學生的Standard信息 ctx.Entry(std).Reference(s => s.Standard).Load(); // 加載指定學生的課程信息 ctx.Entry(std).Collection(s => s.Courses).Load(); } 禁用延遲加載 public PortalContext() : base("name=PortalContext") { this.Configuration.LazyLoadingEnabled = false; } ---------------------------------------------- 修改部分字段不重建表 ---------------------------------------------- 說明 http://www.tuicool.com/articles/EfIvey 用了Codefirst後最大的困擾就是數據變化引發數據庫的刪除再新建,這樣會形成數據丟失 在EntityFrameWork5.0中引入了數據遷移功能能很好的解決這個問題。 步驟 PM> enable-migrations 自動生成 migration配置文件類 namespace NoteSystem.Migrations { using System; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Linq; internal sealed class Configuration<TContext> : DbMigrationsConfiguration<TContext> where TContext:DbContext { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } } } 修改數據庫構造函數 public class XXXContext : DbContext { protected override void OnModelCreating(DbModelBuilder modelBuilder) { Database.SetInitializer(new MigrateDatabaseToLatestVersion<NoteDb, Configuration<NoteDb>>()); //Database.SetInitializer(new DropCreateDatabaseIfModelChanges<NoteDb>()); } } 對應的數據庫中會多一個表 _MigrationHistory MigrationId ContextKey Model ProductVersion 每次修改Model代碼後,會新增一條記錄,並修改對應的表字段 注意,採用該方案後,不要手動去修改數據庫字段。 ---------------------------------------------- 查看 EF 生成的 SQL ---------------------------------------------- (1) query.ToTraceString() (2) 調試時鼠標移到query上面,便可查看sql (3) 記錄日誌 this.Database.Log = (t) => Trace.WriteLine(t); http://www.genshuixue.com/i-cxy/p/7689447 記錄到log: <interceptors> <interceptor type="System.Data.Entity.Infrastructure.Interception.DatabaseLogger, EntityFramework"/> </interceptors> 記錄到文件 <interceptors> <interceptor type="System.Data.Entity.Infrastructure.Interception.DatabaseLogger, EntityFramework"> <parameters> <parameter value="C:\Temp\LogOutput.txt"/> </parameters> </interceptor> </interceptors> (4) 經過 sqlserver 的 SQL Profile來查看EF生成和執行的SQL語句 ---------------------------------------------- 外鍵關聯 http://www.cnblogs.com/libingql/p/3353112.html ---------------------------------------------- 產品目錄和產品 public class Category { public int CategoryID { get; set; } public string CategoryName { get; set; } public virtual ICollection<Product> Products { get; set; } } public class Product { public int ProductID { get; set; } public string ProductName { get; set; } public decimal UnitPrice { get; set; } public int CategoryID { get; set; } public virtual Category Category { get; set; } // virtual是爲了啓用延遲加載 } 會自動生成 Product.CategoryID ---(N:1)--- Category.CategoryID 刪除Category某條記錄會自動級聯刪除Product中的相關記錄 手工指定外鍵 [ForeignKey("UserProfile")] public int UserID { get; set; } // EF6 modelBuilder.Entity<Product>() .HasForeignKey(d => d.CatID) // 外鍵 .HasRequired(t => t.Category) // 外鍵值來自Category表 .WithMany(t => t.Products) // Category對Product是1對多關係 ; // EF7 modelBuilder.Entity<ChildEntity>() .ManyToOne(n => n.Entity, t => t.ChildEntities) .ForeignKey(t => t.EntityId) ; modelBuilder.Entity<Entity>() .WithMany() .Map(x => x.MapKey("ParentID")) 禁止級聯刪除 modelBuilder.Entity<Product>() .HasForeignKey(d => d.CatID) // 外鍵 .HasRequired(t => t.Category) // 外鍵值來自Category表 .WithMany(t => t.Products) // Category對Product是1對多關係 .WillCascadeOnDelete(false) // 可禁止級聯刪除 ; 或統一禁止 protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); // 禁用默認表名複數形式 modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); // 禁用一對多級聯刪除 modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>(); // 禁用多對多級聯刪除 } 更詳細(或複雜的)請查看http://www.cnblogs.com/libingql/p/3353112.html 建立刪除數據庫 string userMDF = System.IO.Path.Combine(userTempFolder, @"NewCreateDB.mdf"); string connStr = String.Format (@"DataSource=.SQLEXPRESS; AttachDbFilename={0};IntegratedSecurity=True; Connect Timeout=30;User Instance=True; Integrated Security = SSPI;",userMDF); NewCreateDB newDB = new NewCreateDB(connStr); newDB.CreateDatabase(); ... if (db.DatabaseExists()) Console.WriteLine("Northwind數據庫存在"); else Console.WriteLine("Northwind數據庫不存在"); ..... newDB.DeleteDatabase(); 繼承 [Table(Name = "dbo.Contacts")] [InheritanceMapping(Code = "Unknown", Type = typeof(Contact), IsDefault = true)] [InheritanceMapping(Code = "Employee", Type = typeof(EmployeeContact))] [InheritanceMapping(Code = "Supplier", Type = typeof(SupplierContact))] [InheritanceMapping(Code = "Customer", Type = typeof(CustomerContact))] [InheritanceMapping(Code = "Shipper", Type = typeof(ShipperContact))] public partial class Contact : INotifyPropertyChanging, INotifyPropertyChanged { [Column(Storage = "_ContactID",IsPrimaryKey = true, IsDbGenerated = true)] public int ContactID{ } [Column(Storage = "_ContactType",IsDiscriminator = true)] public string ContactType{ } } public abstract partial class FullContact : Contact{ } public partial class EmployeeContact : FullContact{ } public partial class SupplierContact : FullContact{ } public partial class CustomerContact : FullContact{ } public partial class ShipperContact : Contact{ }
-------------------------------------------------- 性能優化 http://www.cnblogs.com/nele/p/6118100.html -------------------------------------------------- 使用最新版EF nuget install-package EntityFramework 查看生成的 Sql 語句 VS 診斷工具 用SQL Server Profiler 工具監控運行的sql語句 數據庫分頁 var items = query.Skip((PageIndex - 1) * PageSize).Take(PageSize).ToList(); 對只讀數據不跟蹤實體 var items = db.Person.Where(a => a.IsDeleted == false).AsNoTracking().ToList(); 對於只讀操做,建議採用AsNoTracking,數據是Detached狀態,不跟蹤狀態。 設置 Configuration.AutoDetectChangesEnabled = false; // 自動檢測實體變動 Configuration.ValidateOnSaveEnabled = false; // 保存時不驗證數據有效性 Configuration.LazyLoadingEnabled = false; // 禁止懶加載 Configuration.ProxyCreationEnabled = false; // ? 使用合適的加載方式 懶惰加載: 附加表字段不加載 context.Configuration.ProxyCreationEnabled = true; context.Configuration.LazyLoadingEnabled = true; 導航屬性被標記爲virtual 按需加載: 加載附加表字段,儘量減小數據庫訪問次數 var user = db.Person.Include(a => a.Roles); ToList()的位置 IQueryable返回的是查詢表達式,也就是說生成了SQL查詢語句可是卻尚未與數據庫進行交互。 ToList()方法是將生成的 IQueryable 接口生成的 sql 語句提交給數據庫進行查詢,再轉化爲對象列表 db.Person.Where(a => a.IsDeleted == false).ToList(); 使用SqlQuery時不跟蹤實體 此方法得到的實體查詢是在數據庫(Database)上,實體不會被上下文跟蹤。 var user = db.Database.SqlQuery<User>("select * from user", parameter).ToList(); 此方法得到的實體查詢是被上下文跟蹤,因此能直接賦值後SaveChanges()。 var user = db.Set<User>().SqlQuery("select * from user").ToList(); user.Last().Name = "makmong"; db.SaveChanges(); 儘可能用ViewModel代替實體Model,減小獲取字段 var query = db.User.Select(a => new { Id = a.Id, Name = a.Name }).ToList(); -------------------------------------------------- Entity Framework Extendeds https://github.com/loresoft/EntityFramework.Extended nuget install-package EntityFramework.Extended using EntityFramework.Extended; -------------------------------------------------- 直接刪除 db.Users.Where(u => u.FirstName == "firstname").Delete(); db.Users.Delete(u => u.FirstName == "firstName"); // 已廢棄 直接寫更新語句 update user set name="newName" where firstName="kevin"; db.Users.Where(t => t.FirstName=="Kevin").Update(t => new User() {Name = "ceshi"}); db.Users.Update(t1 => t1.FirstName=="Kevin", t2 => new User() {Name = "ceshi"}); // 已廢棄 查詢一批數據並統計個數 nuget install-package EntityFramework.Extended 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; 注:FutureXXX()方法都暫停執行語句,等到第一個ToList()語句出現,批量執行? 全部Future函數中的查詢包裝到一個鏈接中執行 緩存 從緩存中取數據 var tasks = db.Tasks.Where(t => t.CompleteDate == null).FromCache(); // 使用默認緩存 var users = db.User.Where(u => u.Id > 5).FromCache(CachePolicy.WithDurationExpiration(TimeSpan.FromSeconds(30))); // 300秒緩存 可指定緩存標誌 // cache assigned tasks var tasks = db.Tasks .Where(t => t.AssignedId == myUserId && t.CompleteDate == null) .FromCache(tags: new[] { "Task", "Assigned-Task-" + myUserId }); // some update happened to Task, expire Task tag CacheManager.Current.Expire("Task"); 自定義緩存方案 Locator.Current.Register<ICacheProvider>(() => new MemcachedProvider()); 移除緩存 RemoveCache() Audit 捕捉數據庫實體變動。詳見github var db = new TrackerContext(); var audit = db.BeginAudit(); ... db.SaveChanges(); var log = audit.LastLog; -------------------------------------------------- EFUtilities https://github.com/MikaelEliasson/EntityFramework.Utilities nuget install-package EFUtilities using EntityFramework.Utilities; -------------------------------------------------- 修改 using (var db = new YourDbContext()) { db.AttachAndModify(new BlogPost { ID = postId }).Set(x => x.Reads, 10); db.SaveChanges(); } IncludeEFU var result = db.Contacts .IncludeEFU(db, c => c.PhoneNumbers) .ToList(); var result = db.Contacts .IncludeEFU(db, x => x.PhoneNumbers .Where(n => n.Number == "10134") .OrderBy(p => p.ContactId) .ThenByDescending(p => p.Number)) .ToList() ; ForceDelete db.Database.ForceDelete() Bulk delete var count = EFBatchOperation .For(db, db.BlogPosts) .Where(b => b.Created < upper && b.Created > lower && b.Title == "T2.0") .Delete() ; Bulk insert EFBatchOperation.For(db, db.BlogPosts).InsertAll(list); Batch update EFBatchOperation.For(db, db.Comments).UpdateAll(commentsFromDb, x => x.ColumnsToUpdate(c => c.Reads)); var lines = csv.ReadAllLines().Select(l => l.Split(";")); var comments = lines.Select(line => new Comment{ Id = int.Parse(line[0]), Reads = int.Parse(line[1]) }); EFBatchOperation.For(db, db.Comments).UpdateAll(comments, x => x.ColumnsToUpdate(c => c.Reads)); EFBatchOperation.For(db, db.Comments).Where(x => x.Text == "a").Update(x => x.Reads, x => x.Reads + 1); -------------------------------------------------- LinqKit https://github.com/loresoft/EntityFramework.Extended -------------------------------------------------- -------------------------------------------------- LinqToExcel https://github.com/paulyoder/LinqToExcel nuget install-package LinqToExcel -------------------------------------------------- demo var excel = new ExcelQueryFactory("excelFileCsvFile"); var indianaCompanies = from c in excel.Worksheet<Company>() where c.State == "IN" select c; worksheet var oldCompanies = from c in repo.Worksheet<Company>("US Companies") //worksheet name = 'US Companies' where c.LaunchDate < new DateTime(1900, 1, 1) select c; 字段映射 excel.AddMapping<Company>(x => x.State, "Providence"); //maps the "State" property to the "Providence" column excel.AddMapping("Employees", "Employee Count"); //maps the "Employees" property to the "Employee Count" column var indianaCompanies = from c in excel.Worksheet<Company>() where c.State == "IN" && c.Employees > 500 select c; 或者 public class Company { [ExcelColumn("Company Title")] //maps the "Name" property to the "Company Title" column public string Name { get; set; } [ExcelColumn("Providence")] //maps the "State" property to the "Providence" column public string State { get; set; } [ExcelColumn("Employee Count")] //maps the "Employees" property to the "Employee Count" column public string Employees { get; set; } } where var indianaCompanies = from c in excel.Worksheet() where c["State"] == "IN" || c["Zip"] == "46550" select c; var largeCompanies = from c in excel.Worksheet() where c["EmployeeCount"].Cast<int>() > 500 select c; 無標題行 var indianaCompanies = from c in excel.WorksheetNoHeader() where c[2] == "IN" //value in 3rd column select c; 在Excel命名區域(NamedRange)中查找 var indianaCompanies = from c in excel.NamedRange<Company>("NamedRange") //Selects data within the range named 'NamedRange' where c.State == "IN" select c; 在Excel指定區域中查找 var indianaCompanies = from c in excel.WorksheetRange<Company>("B3", "G10") //Selects data within the B3 to G10 cell range where c.State == "IN" select c; 根據索引找工做表 var oldCompanies = from c in repo.Worksheet<Company>(1) //Queries the second worksheet in alphabetical order where c.LaunchDate < new DateTime(1900, 1, 1) select c; 字段處理 excel.AddTransformation<Company>(x => x.IsBankrupt, cellValue => cellValue == "Y"); var bankruptCompanies = from c in excel.Worksheet<Company>() where c.IsBankrupt == true select c; 獲取工做表名/字段名 var worksheetNames = excel.GetWorksheetNames(); var columnNames = excel.GetColumnNames("worksheetName"); 一些全局設置 excel.TrimSpaces = TrimSpacesType.Both; excel.ReadOnly = true; excel.DatabaseEngine == DatabaseEngine.Ace; excel.StrictMapping = StrictMappingType.Both; excel.UsePersistentConnection = true;
------------------------------------------------ Expression 表達式(二叉樹) 參考 http://kb.cnblogs.com/page/42489/ http://kb.cnblogs.com/page/42509/5/ http://kb.cnblogs.com/page/42509/ ------------------------------------------------ eg Expression<Func<int, int, int>> expression = (a, b) => a * b + 2; var lambda = expression.Compile(); var result = lambda(1, 2); // 4 eg: 2+3 BinaryExpression body = Expression.Add(Expression.Constant(2), Expression.Constant(3)); // 2+3 Expression<Func<int>> expression = Expression.Lambda<Func<int>>(body, null); Expression -> Lambda Func<int> lambda = expression.Compile(); Console.WriteLine(lambda()); 概念 表達式樹主要由下面四部分組成: 1、Body 主體部分 2、Parameters 參數部分 3、NodeType 節點類型 4、Lambda表達式類型 eg Expression<Func<int, int, int>> expr = (x, y) => { return x+y; }; 等價於 ParameterExpression p1 = Expression.Parameter(typeof(int), "a"); ParameterExpression p2 = Expression.Parameter(typeof(int), "b"); BinaryExpression exp = Expression.Multiply(p1, p2); var lamExp = Expression.Lambda<Func<int, int, int>>(exp, new ParameterExpression[] { p1, p2 }); 常見的表達式(詳見 System.Linq.Expression類) 注:表達式常有Assign、Checked附加劇載,表示是否檢測功能,詳見示例 表達式都有如下類別(覆蓋了全部的C#表達式) BinaryExpression(二元表達式) Add/Substract/Multiply/Devide/Power : +-*/^ Equal/NotEqual/ReferenceEqual/ReferenceNotEqual : == != GreaterThan/GreaterThanOrEqual/LessThan/LessThanOrEqual : > >= < <= And/Or/OrElse/ExclusiveOr : && || Assign : Coalesce : LeftShift/RightShift : << >> MakeBinary : Modulo/ModuloAssign : MethodCallExpression IndexExpression UnaryExpression(單元表達式) Increment/PreIncrement/PostIncrement : ++ Decrement/PreDecrement/PostDecrement : -- IsFalse/IsTrue : ?? Not : ! Negate : - Quote : " Throw/Rethrow : TypeAs : as UnaryPlus : Unbox : ! OnesComplement : MakeUnary : Convert : BlockExpression GotoExpression DebugInfoExpression ConditionalExpression ConstantExpression DefaultExpression DynamicExpression InvocationExpression LabelExpression LambdaExpression ListInitExpression LoopExpression TryExpression MemberInitExpression NewExpression NewArrayExpression ParameterExpression RuntimeVariablesExpression SwitchExpression TypeBinaryExpression 常見的表達式 數學相關 Add/Substract/Multiply/Devide/Power Equal/NotEqual/ReferenceEqual/ReferenceNotEqual GreaterThan/GreaterThanOrEqual/LessThan/LessThanOrEqual 布爾相關 Equal/NotEqual/ReferenceEqual/ReferenceNotEqual GreaterThan/GreaterThanOrEqual/LessThan/LessThanOrEqual And/Or/OrElse/ExclusiveOr 反射相關 Constant Property Field Call 數組相關 ArrayAccess/ArrayIndex/ArrayLength/ ExpressionVisitor 若表達式爲加法,改成減法 public class OperationsVisitor : ExpressionVisitor { public Expression Modify(Expression expression) { return Visit(expression); } protected override Expression VisitBinary(BinaryExpression b) { if (b.NodeType == ExpressionType.Add) { Expression left = this.Visit(b.Left); Expression right = this.Visit(b.Right); return Expression.Subtract(left,right); } return base.VisitBinary(b); } } a+b*2 變爲 a-b*x Expression<Func<int, int, int>> lambda = (a, b) => a + b * 2; var operationsVisitor = new OperationsVisitor(); Expression modifyExpression = operationsVisitor.Modify(lambda); Console.WriteLine(modifyExpression.ToString()); IQueryable http://kb.cnblogs.com/page/42510/ EF中查詢表達式(linq for sql 或 lamdba)返回的結果是IQueryable public interface IQueryable : IEnumerable { Type ElementType {get;} Expression Expression {get;} IQueryProvider Provider {get;} } public interface IQueryable<T> : IQueryable, IEnumerable<T>, IEnumerable { } public interface IQueryProvider { IQueryable CreateQuery(Expressiong expression); object Execute(Expression expression); IQueryable<T> CreateQuery<T>(Expression expression)' T Execute<T>(Expression expression); } 查詢數組 List<String> myList = new List<String>() { "a", "ab", "cd", "bd" }; IEnumerable<String> query = from s in myList where s.StartsWith("a") select s; foreach (string s in query) ....; Expression in linq to sql select contactName IQueryable<Customer> custs =db.Customers; ParameterExpression param = Expression.Parameter(typeof (Customer), "c"); Expression selector =Expression.Property(param, typeof (Customer).GetProperty("ContactName")); Expression pred =Expression.Lambda(selector, param); Expression expr = Expression.Call(typeof(Queryable), "Select", new Type[] { typeof (Customer), typeof(string) }, Expression.Constant(custs), pred); IQueryable<string> query = db.Customers.AsQueryable().Provider.CreateQuery<string>(expr); 生成的 SQL語句爲: SELECT [t0].[ContactName] FROM [dbo]. [Customers]AS [t0] ---------------------------------------------------------------- Where IQueryable<Customer> custs = db.Customers; ParameterExpression param = Expression.Parameter(typeof(Customer), "c"); Expression left = Expression.Property(param, typeof(Customer).GetProperty("City")); //c.City=="London" Expression right = Expression.Constant("London"); Expression filter = Expression.Equal(left, right); Expression pred = Expression.Lambda(filter, param); Expression expr = Expression.Call(typeof(Queryable), "Where", new Type[] { typeof(Customer) }, Expression.Constant(custs), pred); //Where(c=>c.City=="London") IQueryable<Customer> query = db.Customers.AsQueryable().Provider.CreateQuery<Customer>(expr); 生成的SQL語句爲: SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] FROM [dbo].[Customers] AS [t0] WHERE [t0].[City] = @p0