原文作者:tim_deschryver
翻譯:沙漠盡頭的狼(谷歌翻譯加持)
Entity Framework 7 包括一些已被要求的流行功能,其中之一是批量操作。Julie Lerman 的一条推文引起了我的注意,我不得不亲自尝试一下。
推文地址:https://twitter.com/julielerman/status/1557743067691569156

為什麼?
那麼,如果我們已經可以更新和刪除實體,為什麼還需要這個功能呢?這裡的關鍵詞是性能。這是一個在 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 计划。