要專業系統地學習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
View Code
查詢出來沒做修改的實體,狀态為Unchange,修改了屬性值,狀态變為Modified
現在要是我憑空new一個Product對象,id設定為資料庫中某一個産品的Id,然後讓上下文對這個新對象追蹤,最後再修改實體狀态為Modified,看看能不能修改
//資料庫中存在的某一條資料的Id
string id = "b0465c73-a7ab-4135-9bf8-4ec85ac6b1e2";
Product p= newProduct
{
Id=id,
Name= "娃哈哈",
AddTime=DateTime.Now,
Price= 3,
Unit= "瓶"};
Console.WriteLine(ctx.Entry(p).State);//Detached
ctx.Products.Attach(p); //這裡報錯
View Code
失敗,在我對它進行追蹤時就報錯了。因為不能跟蹤多個相同鍵的實體,就和資料庫中主鍵重複沖突一樣
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 = newProduct
{
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
View Code
然後我貼一段代碼,和主題沒什麼關系
//我來聲明兩個變量查詢資料庫中的同一條記錄,引用相等
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
View Code
接着看Update操作,如果我們隻更新一個實體的部分屬性呢?
//部分屬性更新,少了name,unit
var pro = ctx.Products.AsNoTracking().FirstOrDefault(x =>x.Id.Contains(id));
Console.WriteLine(ctx.Entry(pro).State);//Detached
Product p = newProduct
{
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
View Code
看看資料庫中的情況
上面的情況說明Modified是全部更新,不能部分更新
部分更新
那麼怎麼部分更新?書中給了兩種辦法
1.手動指定更新屬性
ctx.Entry(p).Property(x => x.Name).IsModified = true;
ctx.Entry(p).Property(x=> x.Unit).IsModified = true;
View Code
2.用Entry().CurrentValues.SetValues() 方法
//使用 Entry()..CurrentValues.SetValues() 方式
var pro = ctx.Products.FirstOrDefault(x =>x.Id.Contains(id));
Product p= newProduct
{
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();
View Code
但是我使用了之後,覺得不太好,也不知道是不是用錯了,我遇到的問題是這樣的,無法部分更新
比如資料庫中存在這麼一條資料{"id":"123",”"name":"張三","age":25,"FK_AddressId":"234"},那麼我現在隻想更新Name,我就傳遞這個對象{"id":"123","name":"趙四"},但是它還是全部更新
比如我沒有指定age屬性,那麼修改為預設值“0”,外鍵在資料庫中不能為空,報錯
各位可以去弄一下
批量更新操作
平時做批量更新,那就是周遊修改呗
var products =ctx.Products.ToList();foreach (var item inproducts)
{
item.Name= item.Name + "999";
}
ctx.SaveChanges();
View Code
看看EF生成并執行的SQL語句
他會先把要更新的資料查詢出來,然後逐條更新,如果你建立了存儲過程,那麼他會自動調用存儲過程進行更新,這個性能會好一點
我這裡有一個存儲過程
然後真正的SQL執行是這樣的
他首先會将要更新的資料查詢出來,然後調用多次存儲過程
還是覺得不太理想?那麼作者告訴了我們一個更好的方案,使用第三方庫:EntityFrameWork.Extended
引入命名空間:using EntityFramework.Extensions; 然後調用該Update方法
var products = ctx.Products.Update(x => new Product { Name = x.Name + "777"});
ctx.SaveChanges();
View Code
捕獲到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])
View Code
看看資料庫裡面
唉,也難怪别人都說EF性能不好,還有很多東西要學啊。