New features in EF CORE 7-Batch operations using ExecuteDelete and ExecuteUpdate

New features in EF CORE 7-Batch operations using ExecuteDelete and ExecuteUpdate

Entity Framework 7 includes some popular features that have been required, one of which is batch operations.

最后更新 9/10/2022 10:55 PM
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

Written by Tim_Deschryver

The Wolf at the End of the Desert

Entity Framework 7 包括一些已被要求的流行功能,其中之一是批量操作。Julie Lerman 的一条推文引起了我的注意,我不得不亲自尝试一下。

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

https://twitter.com/julielerman/status/1557743067691569156

Why?

So, if we can already update and delete entities, why do we need this functionality? The key word here is performance. This is a theme that has always topped the list in new EF releases, and this time is no exception.

The added methods improve performance in a number of ways. Instead of retrieving entities first and storing all entities in memory before we can perform operations on them and finally commit them to SQL. We can do this now with just one operation, which generates an SQL command.

Let's see what it looks like in the code.

set the scene

Before we delve into the example, let's first configure our SQL database and populate 3 tables:

  • Persons: People
  • Addresses: Address (one person has one address)
  • Pets: Pets (one person can have many 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 and ExecuteDeleteAsync

既然我们已经解决了这个问题,让我们深入研究ExecuteDeleteExecuteDeleteAsync

要批量删除一组实体,请使用Where方法过滤掉要删除的实体(与之前类似)。然后,调用ExecuteDelete方法删除实体集合。

using (var context = new NewInEFContext())
{
    SetupAndPopulate(context);

    context.Pets
           .Where(p => p.Name.Contains("1"))
           .ExecuteDelete();
}

Let's also take a look at the SQL statements it generates:

DELETE FROM [p]
FROM [Pets] AS [p]
WHERE [p].[Name] LIKE N'%1%'

As you can see, it just generates an SQL statement to delete eligible entities. These entities are no longer kept in memory. Not bad, simple and efficient!

cascade delete

Let's look at another example, let's remove some people who hold addresses and pet citations. By deleting people, we also delete addresses and pets because delete statements cascade to external tables.

using (var context = new NewInEFContext())
{
    SetupAndPopulate(context);

    context.Persons
           .Where(p => p.PersonId <= 500)
           .ExecuteDelete();
}

Similar to before, this produces the following SQL statement:

DELETE FROM [p]
FROM [Persons] AS [p]
WHERE [p].[PersonId] <= CAST(500 AS bigint)

the number of rows affected

还可以查看删除操作影响了多少行,ExecuteDelete返回受影响的行数。

using (var context = new NewInEFContext())
{
    SetupAndPopulate(context);

    var personsDeleted =
        context.Persons
           .Where(p => p.PersonId <= 100)
           .ExecuteDelete();
}

在上面的表达式中,personsDeleted变量等于 100。

ExecuteUpdate and ExecuteUpdateAsync

现在我们已经了解了如何删除实体,让我们探索如何更新它们。就像ExecuteDelete,我们首先必须过滤我们想要更新的实体,然后调用ExecuteUpdate.

要更新实体,我们需要使用新SetProperty方法。SetProperty的第一个参数是通过 lambda 选择需要更新的属性,第二个参数也使用 lambda 选择该属性的新值,。

For example, let's set the person's last name to "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"));
}

This will generate the corresponding SQL statement:

UPDATE [p]
    SET [p].[LastName] = N'Updated'
FROM [Persons] AS [p]
WHERE [p].[PersonId] <= CAST(1000 AS bigint)

We can also access the value of the entity and use it to create new values.

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));
}

Produce the following SQL statement:

UPDATE [p]
    SET [p].[LastName] = N'Updated' + [p].[LastName]
FROM [Persons] AS [p]
WHERE [p].[PersonId] <= CAST(1000 AS bigint)

Update multiple values at once

我们甚至可以通过多次调用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));
}

Once again, the corresponding SQL statement:

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)

the number of rows affected

就像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"));
}

Please note that updating nested entities is not supported.

More updates in Entity Framework 7

有关新功能的完整列表,请参阅EF 7 计划

Keep Exploring

延伸阅读

更多文章