EF CORE 7 中的新功能-使用 ExecuteDelete 和 ExecuteUpdate 進行批次操作

EF CORE 7 中的新功能-使用 ExecuteDelete 和 ExecuteUpdate 進行批次操作

Entity Framework 7 包含一些已被要求的熱門功能,其中之一是批次操作。

最後更新 2022/9/10 下午10:55
tim_deschryver
預計閱讀 6 分鐘
分類
EF Core
標籤
.NET C# EF Core ORM

原文链接:https://timdeschryver.dev/blog/new-in-entity-framework-7-bulk-operations-with-executedelete-and-executeupdate

原文作者:tim_deschryver

翻譯:沙漠盡頭的狼(Google 翻譯加持)

Entity Framework 7 包含了一些備受期待的熱門功能,其中之一是批次操作。Julie Lerman 的一則推文引起了我的注意,我不得不親自試試看。

推文地址:https://twitter.com/julielerman/status/1557743067691569156

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

既然我們已經解決了這個問題,讓我們深入探討 ExecuteDeleteExecuteDeleteAsync

要批次刪除一組實體,請使用 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 計劃

繼續探索

延伸閱讀

更多文章