原文鏈接:https://timdeschryver.dev/blog/new-in-entity-framework-7-bulk-operations-with-executedelete-and-executeupdate
原文作者:tim_deschryver
翻譯:沙漠盡頭的狼(谷歌翻譯加持)
Entity Framework 7 包括一些已被要求的流行功能,其中之一是批量操作。Julie Lerman 的一條推文[1]引起了我的注意,我不得不親自嘗試一下。
推文地址:https://twitter.com/julielerman/status/1557743067691569156[2]

為什么?
那么,如果我們已經可以更新和刪除實體,為什么還需要這個功能呢?這里的關鍵詞是性能。這是一個在 EF 新版本中一直位居榜首的主題,這次也不例外。
添加的方法以多種方式提高了性能。而不是首先檢索實體并將所有實體存儲在內存中,然后我們才能對它們執行操作,最后將它們提交給 SQL。我們現在只需一個操作就可以做到這一點,這會產生一個 SQL 命令。
讓我們看看它在代碼中的樣子。
設置場景
在我們深入示例之前,讓我們首先配置我們的 SQL 數據庫并填充 3 個表:
Persons: 人
Addresses: 地址( 一個人有一個地址)
Pets: 寵物(一個人可以養很多寵物)
using?Microsoft.EntityFrameworkCore;using?(var?context?=?new?NewInEFContext())
{SetupAndPopulate(context);
}static?void?SetupAndPopulate(NewInEFContext?context)
{context.Database.EnsureDeleted();context.Database.EnsureCreated();context.Persons.AddRange(Enumerable.Range(1,?1_000).Select(i?=>{return?new?Person{FirstName?=?$"{nameof(Person.FirstName)}-{i}",LastName?=?$"{nameof(Person.LastName)}-{i}",Address?=?new?Address{Street?=?$"{nameof(Address.Street)}-{i}",},Pets?=?Enumerable.Range(1,?3).Select(i2?=>{return?new?Pet{Breed?=?$"{nameof(Pet.Breed)}-{i}-{i2}",Name?=?$"{nameof(Pet.Name)}-{i}-{i2}",};}).ToList()};}));context.SaveChanges();
}public?class?NewInEFContext?:?DbContext
{public?DbSet<Person>?Persons?{?get;?set;?}public?DbSet<Pet>?Pets?{?get;?set;?}public?DbSet<Address>?Addresses?{?get;?set;?}protected?override?void?OnConfiguring(DbContextOptionsBuilder?options)=>?options.UseSqlServer("Connectionstring");protected?override?void?OnModelCreating(ModelBuilder?modelBuilder){modelBuilder.Entity<Address>().Property<long>("PersonId");modelBuilder.Entity<Pet>().Property<long>("PersonId");}
}public?class?Person
{public?long?PersonId?{?get;?set;?}public?string?FirstName?{?get;?set;?}?=?"";public?string?LastName?{?get;?set;?}?=?"";public?Address??Address?{?get;?set;?}public?List<Pet>?Pets?{?get;?set;?}?=?new?List<Pet>();
}public?class?Address
{public?long?AddressId?{?get;?set;?}public?string?Street?{?get;?set;?}?=?"";
}public?class?Pet
{public?long?PetId?{?get;?set;?}public?string?Breed?{?get;?set;?}?=?"";public?string?Name?{?get;?set;?}?=?"";
}
ExecuteDelete 和 ExecuteDeleteAsync
既然我們已經解決了這個問題,讓我們深入研究ExecuteDelete
和 ExecuteDeleteAsync
。
要批量刪除一組實體,請使用Where
方法過濾掉要刪除的實體(與之前類似)。然后,調用ExecuteDelete
方法刪除實體集合。
using?(var?context?=?new?NewInEFContext())
{SetupAndPopulate(context);context.Pets.Where(p?=>?p.Name.Contains("1")).ExecuteDelete();
}
讓我們也看看它生成的 SQL 語句:
DELETE?FROM?[p]
FROM?[Pets]?AS?[p]
WHERE?[p].[Name]?LIKE?N'%1%'
如您所見,它只是生成一條 SQL 語句來刪除符合條件的實體。這些實體也不再保存在內存中。不錯,簡單,高效!
級聯刪除
讓我們看另一個例子,讓我們刪除一些持有地址和寵物引用的人。通過刪除人員,我們也刪除了地址和寵物,因為刪除語句級聯到外部表。
using?(var?context?=?new?NewInEFContext())
{SetupAndPopulate(context);context.Persons.Where(p?=>?p.PersonId?<=?500).ExecuteDelete();
}
與之前類似,這會產生以下 SQL 語句:
DELETE?FROM?[p]
FROM?[Persons]?AS?[p]
WHERE?[p].[PersonId]?<=?CAST(500?AS?bigint)
受影響的行數
還可以查看刪除操作影響了多少行,ExecuteDelete
返回受影響的行數。
using?(var?context?=?new?NewInEFContext())
{SetupAndPopulate(context);var?personsDeleted?=context.Persons.Where(p?=>?p.PersonId?<=?100).ExecuteDelete();
}
在上面的表達式中,personsDeleted
變量等于 100。
ExecuteUpdate 和 ExecuteUpdateAsync
現在我們已經了解了如何刪除實體,讓我們探索如何更新它們。就像ExecuteDelete
,我們首先必須過濾我們想要更新的實體,然后調用ExecuteUpdate
.
要更新實體,我們需要使用新SetProperty
方法。SetProperty
的第一個參數是通過 lambda 選擇需要更新的屬性,第二個參數也使用 lambda 選擇該屬性的新值,。
例如,讓我們將人員的姓氏設置為“Updated”。
using?(var?context?=?new?NewInEFContext())
{SetupAndPopulate(context);context.Persons.Where(p?=>?p.PersonId?<=?1_000).ExecuteUpdate(p?=>?p.SetProperty(x?=>?x.LastName,?x?=>?"Updated"));
}
這會生成相應的 SQL 語句:
UPDATE?[p]SET?[p].[LastName]?=?N'Updated'
FROM?[Persons]?AS?[p]
WHERE?[p].[PersonId]?<=?CAST(1000?AS?bigint)
我們還可以訪問實體的值并使用它來創建新值。
using?(var?context?=?new?NewInEFContext())
{SetupAndPopulate(context);context.Persons.Where(p?=>?p.PersonId?<=?1_000).ExecuteUpdate(p?=>?p.SetProperty(x?=>?x.LastName,?x?=>?"Updated"?+?x.LastName));
}
產生以下 SQL 語句:
UPDATE?[p]SET?[p].[LastName]?=?N'Updated'?+?[p].[LastName]
FROM?[Persons]?AS?[p]
WHERE?[p].[PersonId]?<=?CAST(1000?AS?bigint)
一次更新多個值
我們甚至可以通過多次調用SetProperty
來一次更新多個屬性。
using?(var?context?=?new?NewInEFContext())
{SetupAndPopulate(context);context.Persons.Where(p?=>?p.PersonId?<=?1_000).ExecuteUpdate(p?=>p.SetProperty(x?=>?x.LastName,?x?=>?"Updated"?+?x.LastName).SetProperty(x?=>?x.FirstName,?x?=>?"Updated"?+?x.FirstName));
}
再一次,對應的 SQL 語句:
UPDATE?[p]SET?[p].[FirstName]?=?N'Updated'?+?[p].[FirstName],[p].[LastName]?=?N'Updated'?+?[p].[LastName]
FROM?[Persons]?AS?[p]
WHERE?[p].[PersonId]?<=?CAST(1000?AS?bigint)
受影響的行數
就像ExecuteDelete
,ExecuteUpdate
也返回受影響的行數。
using?(var?context?=?new?NewInEFContext())
{SetupAndPopulate(context);var?personsUpdated?=context.Persons.Where(p?=>?p.PersonId?<=?1_000).ExecuteUpdate(p?=>?p.SetProperty(x?=>?x.LastName,?x?=>?"Updated"));
}
請注意,不支持更新嵌套實體。
Entity Framework 7 中的更多更新
有關新功能的完整列表,請參閱EF 7 計劃[3]。
參考資料
[1]
Julie Lerman 的一條推文: https://twitter.com/julielerman
[2]https://twitter.com/julielerman/status/1557743067691569156: https://twitter.com/julielerman/status/1557743067691569156
[3]EF 7 計劃: https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/plan?WT.mc_id=DT-MVP-5004452