要專業系統地學習EF前往《你必須掌握的Entity Framework 6.x與Core 2.0》這本書的作者(汪鵬,Jeffcky)的博客:https://www.cnblogs.com/CreateMyself/
數據操作CRUD,我們只說Update,因為在EF中Update有點復雜 后面我們說批量數據更新
Update操作
上下文沒有提供Update方法,所以我們要更新操作一般都是將數據查詢出來,修改實體屬性的值,然后SaveChanges()就OK了
眼熟一下平時的Update
// 一般的修改 var pro = ctx.Products.FirstOrDefault(); Console.WriteLine(JsonConvert.SerializeObject(pro)); Console.WriteLine(ctx.Entry(pro).State); // Unchanged //{"Order":null,"Name":"牙刷","Price":14.00,"Unit":"只","FK_Order_Id":"82903023-a7a6-4839-9caa-153ee9d00e65","Id":"1b25351c-3008-4d27-a9de-6749ec1d0845","AddTime":"2019-01-15T10:35:03.947"} pro.Name = "牙刷2"; Console.WriteLine(ctx.Entry(pro).State); // Modified var res = ctx.SaveChanges(); Console.WriteLine(res); // result:1
查詢出來沒做修改的實體,狀態為Unchange,修改了屬性值,狀態變為Modified
現在要是我憑空new一個Product對象,id設置為數據庫中某一個產品的Id,然后讓上下文對這個新對象追蹤,最后再修改實體狀態為Modified,看看能不能修改
// 數據庫中存在的某一條數據的Id string id = "b0465c73-a7ab-4135-9bf8-4ec85ac6b1e2"; Product p = new Product { Id = id, Name = "娃哈哈", AddTime = DateTime.Now, Price = 3, Unit = "瓶" }; Console.WriteLine(ctx.Entry(p).State); // Detached ctx.Products.Attach(p); // 這里報錯
失敗,在我對它進行追蹤時就報錯了。因為不能跟蹤多個相同鍵的實體,就和數據庫中主鍵重復沖突一樣
System.InvalidOperationException: Attaching an entity of type '_2019011402.Entity.Product' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
解決辦法就是,去掉一個追蹤,那么我們把該條數據查詢出來,對它取消追蹤,這個再跟蹤這個新對象就可以了
// 全部屬性更新 var pro = ctx.Products.AsNoTracking().FirstOrDefault(x => x.Id.Contains(id)); Console.WriteLine(ctx.Entry(pro).State); // Detached Product p = new Product { Id = id, Name = "娃哈哈", AddTime = DateTime.Now, Price = 3, Unit = "瓶", FK_Order_Id = "469b82be-8139-4e67-b566-5b2b5f6d838d" }; ctx.Products.Attach(p); ctx.Entry(p).State = System.Data.Entity.EntityState.Modified; var res = ctx.SaveChanges(); Console.WriteLine(res); // result:1 ok
然后我貼一段代碼,和主題沒什么關系
//我來聲明兩個變量查詢數據庫中的同一條記錄,引用相等 var pro2 = ctx.Products.FirstOrDefault(x => x.Id.Contains(id)); var pro3 = ctx.Products.FirstOrDefault(x => x.Id.Contains(id)); Console.WriteLine("pro2_state:{0}", ctx.Entry(pro2).State); // Unchanged Console.WriteLine("pro3_state:{0}", ctx.Entry(pro3).State); // Unchanged Console.WriteLine(ReferenceEquals(pro2, pro3)); // True Test t1 = new Test { Id = "id1", Name = "Test1" }; Test t2 = new Test { Id = "id1", Name = "Test1" }; Console.WriteLine(ReferenceEquals(t1, t2)); // False
接着看Update操作,如果我們只更新一個實體的部分屬性呢?
// 部分屬性更新,少了name,unit var pro = ctx.Products.AsNoTracking().FirstOrDefault(x => x.Id.Contains(id)); Console.WriteLine(ctx.Entry(pro).State); // Detached Product p = new Product { Id = id, AddTime = DateTime.Now, Price = 3, FK_Order_Id = "469b82be-8139-4e67-b566-5b2b5f6d838d" }; ctx.Products.Attach(p); ctx.Entry(p).State = System.Data.Entity.EntityState.Modified; var res = ctx.SaveChanges(); Console.WriteLine(res); // result:1 ok
看看數據庫中的情況

上面的情況說明Modified是全部更新,不能部分更新
部分更新
那么怎么部分更新?書中給了兩種辦法
1.手動指定更新屬性
ctx.Entry(p).Property(x => x.Name).IsModified = true; ctx.Entry(p).Property(x => x.Unit).IsModified = true;
2.用Entry().CurrentValues.SetValues() 方法
// 使用 Entry()..CurrentValues.SetValues() 方式 var pro = ctx.Products.FirstOrDefault(x => x.Id.Contains(id)); Product p = new Product { Id = id, Name = "盆子2", //AddTime = new DateTime(1999, 2, 2), //Price = 3, Unit = "個2", FK_Order_Id = "469b82be-8139-4e67-b566-5b2b5f6d838d" }; ctx.Entry(pro).CurrentValues.SetValues(p); ctx.SaveChanges();
但是我使用了之后,覺得不太好,也不知道是不是用錯了,我遇到的問題是這樣的,無法部分更新
比如數據庫中存在這么一條數據{"id":"123",”"name":"張三","age":25,"FK_AddressId":"234"},那么我現在只想更新Name,我就傳遞這個對象{"id":"123","name":"趙四"},但是它還是全部更新
比如我沒有指定age屬性,那么修改為默認值“0”,外鍵在數據庫中不能為空,報錯
各位可以去弄一下
批量更新操作
平時做批量更新,那就是遍歷修改唄
var products = ctx.Products.ToList(); foreach (var item in products) { item.Name = item.Name + "999"; } ctx.SaveChanges();
看看EF生成並執行的SQL語句

他會先把要更新的數據查詢出來,然后逐條更新,如果你創建了存儲過程,那么他會自動調用存儲過程進行更新,這個性能會好一點
我這里有一個存儲過程

然后真正的SQL執行是這樣的

他首先會將要更新的數據查詢出來,然后調用多次存儲過程
還是覺得不太理想?那么作者告訴了我們一個更好的方案,使用第三方庫:EntityFrameWork.Extended

引入命名空間:using EntityFramework.Extensions; 然后調用該Update方法
var products = ctx.Products.Update(x => new Product { Name = x.Name + "777" }); ctx.SaveChanges();
捕獲到SQL語句是這樣的,很奇怪用ctx.Database.Log = Console.WriteLine;捕獲不到,我用的SQL Profiler,這應該是這個Extended庫是第三方的原因,不是EF團隊弄的
UPDATE [dbo].[tb_Products] SET [Name] = CASE WHEN ([Name] IS NULL) THEN N'' ELSE [Name] END + N'777' FROM [dbo].[tb_Products] AS j0 INNER JOIN ( SELECT 1 AS [C1], [Extent1].[Id] AS [Id] FROM [dbo].[tb_Products] AS [Extent1] ) AS j1 ON (j0.[Id] = j1.[Id])
看看數據庫里面

唉,也難怪別人都說EF性能不好,還有很多東西要學啊。
