© Eric Vogel 2021
E. VogelBeginning Entity Framework Core 5https://doi.org/10.1007/978-1-4842-6882-7_10

10. Deleting Data

Eric Vogel1  
(1)
Okemos, MI, USA
 

In this chapter, I will cover how to delete data using Entity Framework Core 5. There are generally two ways to delete a record. These two methods are often referred to as soft delete and hard delete. A soft delete is flagging a record as deleted and can be achieved by doing a database update and setting a flag to mark a record as deleted. A hard delete is removing the record from the database, and it can no longer be retrieved afterward. In this chapter, I will be covering how to implement a hard delete.

Deleting the Root Entity

There are two steps to removing an item. The first is retrieving that item, and the second is removing that record by using the Remove method on DbSet<T> like this:
var existing = _context.Persons.Single(x => x.FirstName == "Clarke" && x.LastName == "Kent");
_context.Persons.Remove(existing);
_context.SaveChanges();

As you can see, removing a root entity is simple.

Deleting a Child Entity

By default, Entity Framework Core 5 will delete child entities when the root entity is deleted. In our database, if we delete a person record, its associated address records will also be deleted. You can also configure how you want to handle deleting child entities in OnModelCreating in our custom AppDbContext class. The options you have are cascade, client set null, restrict, and set null. These options are specified through the DeleteBehavior enum. I will now cover how each of these options works one by one.

Cascade Delete

Cascade delete is the default behavior for removing child entities. Cascade means that all related child entities will be deleted when the parent entity is deleted. In our case, when a person is deleted, its address records will also be deleted. In order to explicitly define this behavior, we need to finish the one-to-many mapping between Address and Person. We will do this by adding a Person type property on the Address entity named Person as seen in Listing 10-1.
namespace EFCore5WebApp.Core.Entities
{
    public class Address
    {
        public int Id { get; set; }
        public string AddressLine1 { get; set; }
        public string AddressLine2 { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Country { get; set; }
        public string ZipCode { get; set; }
        public int PersonId { get; set; }
        public Person Person { get; set; }
    }
}
Listing 10-1

Address Class Updated with Person Navigation Property

You can also explicitly define this behavior in the OnModelCreating event like Listing 10-2.
modelBuilder.Entity<Person>(entity =>
{
    entity.HasMany(x => x.Addresses)
   .WithOne(x => x.Person)
        .OnDelete(DeleteBehavior.Cascade);
});
Listing 10-2

Define Cascade Delete for Person Address Records

You can see in our code that we defined that a person has many addresses via the Addresses property and that an address has a single person mapped via the Person property on the Address entity. Lastly, we defined the delete behavior as cascade.

Client Set Null Delete Behavior

The client set null delete behavior will set any foreign key properties to null on the child entities instead of removing the child entities. This will only set the foreign key properties to null in memory. You have to explicitly call SaveChanges for this to be persisted to the database. In order to use this behavior in our code, we would need to set the PersonId property to be null in the Address entity. This update would look like Listing 10-3.
namespace EFCore5WebApp.Core.Entities
{
    public class Address
    {
        public int Id { get; set; }
        public string AddressLine1 { get; set; }
        public string AddressLine2 { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Country { get; set; }
        public string ZipCode { get; set; }
        public int? PersonId { get; set; }
        public Person Person { get; set; }
    }
}
Listing 10-3

Address with Nullable PersonId

The next step would be to update the OnModelCreating method in AppDbContext to set the delete behavior to be ClientSetNull as seen in Listing 10-4.
modelBuilder.Entity<Person>(entity =>
{
    entity.HasMany(x => x.Addresses)
   .WithOne(x => x.Person)
   .HasForeignKey(x => x.PersonId)
        .OnDelete(DeleteBehavior.Cascade);
});
Listing 10-4

Client Set Null Delete Behavior on Person’s Addresses

As you can see in the code example, we also explicitly state the foreign key property to PersonId to help Entity Framework Core know what property value to null out.

Restrict Delete Behavior

The restrict delete behavior specifies not to perform a cascade delete. This forces you to handle deleting the child entities yourself in your code. To specify the restrict delete behavior, we would update the OnModelCreating method in our AppDbContext to set the Addresses delete behavior to Restrict on the Person entity as seen in Listing 10-5.
modelBuilder.Entity<Person>(entity =>
 {
     entity.HasMany(x => x.Addresses)
    .WithOne(x => x.Person)
    .HasForeignKey(x => x.PersonId)
         .OnDelete(DeleteBehavior.Restrict);
 });
Listing 10-5

Restrict Delete Behavior on Person’s Addresses

You can see that the code is the same as client set null except we set the delete behavior enum value to DeleteBehavior.Restrict.

Set Null Delete Behavior

The set null delete behavior is the same as client set null except the foreign key properties are automatically set to null without your intervention when the parent entity is deleted. Look at Listing 10-6 to see the needed code in the OnModelCreating method for our AppDbContext.
modelBuilder.Entity<Person>(entity =>
{
    entity.HasMany(x => x.Addresses)
   .WithOne(x => x.Person)
   .HasForeignKey(x => x.PersonId)
        .OnDelete(DeleteBehavior.SetNull);
});
Listing 10-6

Set Null Delete for Person’s Addresses

As you can see, the code is the exact same as ClientSetNull except we pass in the DeleteBehavior.SetNull value into the OnDelete() chained method.

Integration Test

Now that we have seen how to delete an entity record using Entity Framework Core 5, let us see this in action by creating an integration test. Open the DAL.Tests project and create a new class named DeleteTests. See Listing 10-7 for the unit test class.
using System;
using System.Collections.Generic;
using System.Linq;
using EFCore5WebApp.Core.Entities;
using Microsoft.EntityFrameworkCore;
using NUnit.Framework;
namespace EFCore5WebApp.DAL.Tests
{
    [TestFixture]
    public class DeleteTests
    {
        private AppDbContext _context;
        [SetUp]
        public void SetUp()
        {
            _context = new AppDbContext(new DbContextOptionsBuilder<AppDbContext>()
    .UseSqlServer("Server=(localdb)\mssqllocaldb;Database=EfCore5WebApp;Trusted_Connection=True;MultipleActiveResultSets=true")
               .Options);
            // add person record
            var record = new Person()
            {
                FirstName = "Clarke",
                LastName = "Kent",
                CreatedOn = DateTime.Now,
                EmailAddress = "[email protected]",
                Addresses = new List<Address>
                {
                    new Address
                    {
                        AddressLine1 = "1234 Fake Street",
                        AddressLine2 = "Suite 1",
                        City = "Chicago",
                        State = "IL",
                        ZipCode = "60652",
                        Country = "United States"
                    },
                    new Address
                    {
                        AddressLine1 = "555 Waverly Street",
                        AddressLine2 = "APT B2",
                        City = "Mt. Pleasant",
                        State = "MI",
                        ZipCode = "48858",
                        Country = "USA"
                    }
                }
            };
            _context.Persons.Add(record);
            _context.SaveChanges();
        }
        [Test]
        public void DeletePerson()
        {
            var existing = _context.Persons.Single(x => x.FirstName == "Clarke" && x.LastName == "Kent");
            var personId = existing.Id;
            _context.Persons.Remove(existing);
            _context.SaveChanges();
            var found = _context.Persons.SingleOrDefault(x => x.FirstName == "Clarke" && x.LastName == "Kent");
            Assert.IsNull(found);
            var addresses = _context.Addresses.Where(x => x.PersonId == personId);
            Assert.AreEqual(0, addresses.Count());
        }
    }
}
Listing 10-7

DeleteTests Class File

Like the other unit tests, we first get a connection to our SQL Server in the SetUp method. Next, I add a person record with two addresses. In the DeletePerson method, we test the delete functionality by retrieving the person record. Then we remove the record and commit the change. After the person record is removed, we make sure it can no longer be retrieved and that its addresses have also been removed.

Summary

In this chapter, I have covered how to delete a root entity and its child entities. By default, Entity Framework Core 5 will delete child entities when the root entity is removed. I also covered how you can change this default behavior if desired. After that, I covered how to test this functionality through an integration test. In the next chapter, I will cover navigation properties in depth.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.134.102.182