<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mijalski</title>
    <description>The latest articles on DEV Community by Mijalski (@mijalski).</description>
    <link>https://dev.to/mijalski</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F344547%2Fec94a144-6920-4883-b6fa-488aae0ae565.png</url>
      <title>DEV Community: Mijalski</title>
      <link>https://dev.to/mijalski</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mijalski"/>
    <language>en</language>
    <item>
      <title>EF Core Implementing Soft Delete</title>
      <dc:creator>Mijalski</dc:creator>
      <pubDate>Sun, 13 Jun 2021 12:47:27 +0000</pubDate>
      <link>https://dev.to/mijalski/ef-core-implementing-soft-delete-hgb</link>
      <guid>https://dev.to/mijalski/ef-core-implementing-soft-delete-hgb</guid>
      <description>&lt;p&gt;Soft deleting is an easy way of deleting data without actually removing it from the database. Instead of performing &lt;code&gt;DELETE&lt;/code&gt; on the database we mark it as deleted and filter it out by default on the application side. We want our soft delete mechanism to function seamlessly with EFCore, after all, it's just the implementation detail, so we need to intercept all &lt;code&gt;DbContext&lt;/code&gt; &lt;code&gt;Remove&lt;/code&gt; calls.&lt;/p&gt;

&lt;p&gt;So, to achieve this we need two parts: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;filtering out all data that has the deleted flag set, so that it is impossible for our Application to read deleted data&lt;/li&gt;
&lt;li&gt;intercepting all &lt;code&gt;SaveChanges&lt;/code&gt; or &lt;code&gt;SaveChangesAsync&lt;/code&gt; calls and replacing the usual delete with our custom Soft Delete mechanism&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, we need our &lt;code&gt;ISoftDelete.cs&lt;/code&gt; interface, which all our entities will implement. It's a good practice to put in the time stamp as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
public interface ISoftDelete
{
    bool IsDeleted { get; set; }
    DateTimeOffset? DeletionDateTime { get; set; }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that in our &lt;code&gt;ApplicationDbContext.cs&lt;/code&gt; we need to create the filter with the help of some reflection magic. We need to set the filter on each entity, so we iterate over them and then check if they implement the &lt;code&gt;ISoftDelte&lt;/code&gt; interface to see if they are a candidate for the filter. After that, we set the filtering by getting the property and creating a lambda expression for it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
protected void SetGlobalSoftDeleteQueryFilter(ModelBuilder modelBuilder)
{
    foreach (var entityType in modelBuilder.Model.GetEntityTypes())
    {
        var isDeletedProperty = entityType.FindProperty("IsDeleted");
        if (isDeletedProperty != null 
            &amp;amp;&amp;amp; isDeletedProperty.ClrType == typeof(bool))
        {
            var parameter = Expression.Parameter(
                entityType.ClrType, "p");
            var prop = Expression.Property(parameter, 
                isDeletedProperty.PropertyInfo);
            var filter = Expression.Lambda(Expression.Not(prop),
                parameter);
            entityType.SetQueryFilter(filter);
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to register it in our &lt;code&gt;OnModelCreating&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    SetGlobalQueryForSoftDelete(modelBuilder);

    // ...
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are halfway there, now we need to set those columns by intercepting the &lt;code&gt;SaveChanges&lt;/code&gt; calls. For this purpose let's create a method, that will check the EFCore internal &lt;code&gt;ChangeTracker&lt;/code&gt; for any candidates implementing &lt;code&gt;ISoftDelete&lt;/code&gt; interface to be removed and replacing their state with &lt;code&gt;EntityState.Modified&lt;/code&gt; and setting the appropriate columns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
private void SetSoftDeleteColumns()
{
    var entriesDeleted = ChangeTracker
        .Entries()
        .Where(e =&amp;gt; e.Entity is ISoftDelete 
                &amp;amp;&amp;amp; e.State == EntityState.Deleted);

    foreach (var entityEntry in entriesDeleted)
    {
        ((ISoftDelete)entityEntry.Entity).IsDeleted = true;
        ((ISoftDelete)entityEntry.Entity).DeletionDateTime = 
                DateTimeOffset.Now;
        entityEntry.State = EntityState.Modified;
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to override &lt;code&gt;SaveChanges&lt;/code&gt; and &lt;code&gt;SaveChangesAsync&lt;/code&gt; methods in our &lt;code&gt;ApplicationDbContext.cs&lt;/code&gt; to perform our custom logic and that's it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
public override int SaveChanges()
{
    SetSoftDeleteColumns();

    return base.SaveChanges();
}

public override Task&amp;lt;int&amp;gt; SaveChangesAsync(
    CancellationToken cancellationToken = default)
{
    SetSoftDeleteColumns();

    return base.SaveChangesAsync(cancellationToken);
}

You can checkout more from me [on my blog](https://www.hmijalski.com).

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
  </channel>
</rss>
