From e2cfcacca6e46001bb4e3a169fa313a0182b30b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 26 Oct 2017 13:11:34 +0300 Subject: [PATCH] Implemented Soft Delete filter. --- .../Abp/EntityFrameworkCore/AbpDbContext.cs | 28 ++++++++ src/Volo.Abp/Volo/Abp/ISoftDelete.cs | 16 +++++ ...1049_Added_IsDeleted_To_Person.Designer.cs | 70 +++++++++++++++++++ ...0171026101049_Added_IsDeleted_To_Person.cs | 26 +++++++ .../TestAppDbContextModelSnapshot.cs | 2 + .../Repositories/SoftDelete_Filter_Tests.cs | 26 +++++++ .../Volo/Abp/TestApp/Domain/Person.cs | 4 +- .../Volo/Abp/TestApp/TestDataBuilder.cs | 2 + 8 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/Volo.Abp/Volo/Abp/ISoftDelete.cs create mode 100644 test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/20171026101049_Added_IsDeleted_To_Person.Designer.cs create mode 100644 test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/20171026101049_Added_IsDeleted_To_Person.cs create mode 100644 test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/SoftDelete_Filter_Tests.cs diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index bd6f64a529..9ed4a277eb 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -22,6 +22,7 @@ namespace Volo.Abp.EntityFrameworkCore public Guid? CurrentTenantId => CurrentTenant?.Id; protected virtual bool IsMayHaveTenantFilterEnabled => true; //TODO: Change this when data filtering system is full implemented + protected virtual bool IsSoftDeleteFilterEnabled => true; //TODO: Change this when data filtering system is full implemented public ICurrentTenant CurrentTenant { get; set; } @@ -115,7 +116,10 @@ namespace Volo.Abp.EntityFrameworkCore CheckAndSetId(entry); break; case EntityState.Modified: + HandleConcurrencyStamp(entry); + break; case EntityState.Deleted: + CancelDeletionForSoftDelete(entry); HandleConcurrencyStamp(entry); break; } @@ -133,6 +137,18 @@ namespace Volo.Abp.EntityFrameworkCore entity.ConcurrencyStamp = Guid.NewGuid().ToString(); } + protected virtual void CancelDeletionForSoftDelete(EntityEntry entry) + { + if (!(entry.Entity is ISoftDelete)) + { + return; + } + + entry.Reload(); + entry.State = EntityState.Modified; + entry.Entity.As().IsDeleted = true; + } + protected virtual void CheckAndSetId(EntityEntry entry) { //Set GUID Ids @@ -179,6 +195,18 @@ namespace Volo.Abp.EntityFrameworkCore { Expression> expression = null; + if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity))) + { + /* This condition should normally be defined as below: + * !IsSoftDeleteFilterEnabled || !((ISoftDelete) e).IsDeleted + * But this causes a problem with EF Core (see https://github.com/aspnet/EntityFrameworkCore/issues/9502) + * So, we made a workaround to make it working. It works same as above. + */ + + Expression> softDeleteFilter = e => !((ISoftDelete)e).IsDeleted || ((ISoftDelete)e).IsDeleted != IsSoftDeleteFilterEnabled; + expression = expression == null ? softDeleteFilter : CombineExpressions(expression, softDeleteFilter); + } + if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity))) { /* This condition should normally be defined as below: diff --git a/src/Volo.Abp/Volo/Abp/ISoftDelete.cs b/src/Volo.Abp/Volo/Abp/ISoftDelete.cs new file mode 100644 index 0000000000..cc0d0bdef9 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/ISoftDelete.cs @@ -0,0 +1,16 @@ +namespace Volo.Abp +{ + /// + /// Used to standardize soft deleting entities. + /// Soft-delete entities are not actually deleted, + /// marked as IsDeleted = true in the database, + /// but can not be retrieved to the application normally. + /// + public interface ISoftDelete + { + /// + /// Used to mark an Entity as 'Deleted'. + /// + bool IsDeleted { get; set; } + } +} \ No newline at end of file diff --git a/test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/20171026101049_Added_IsDeleted_To_Person.Designer.cs b/test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/20171026101049_Added_IsDeleted_To_Person.Designer.cs new file mode 100644 index 0000000000..cdb7a97811 --- /dev/null +++ b/test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/20171026101049_Added_IsDeleted_To_Person.Designer.cs @@ -0,0 +1,70 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using System; +using Volo.Abp.TestApp.Domain; +using Volo.Abp.TestApp.EntityFrameworkCore; + +namespace Volo.Abp.EntityFrameworkCore.Tests.Migrations +{ + [DbContext(typeof(TestAppDbContext))] + [Migration("20171026101049_Added_IsDeleted_To_Person")] + partial class Added_IsDeleted_To_Person + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("Volo.Abp.TestApp.Domain.Person", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Age"); + + b.Property("IsDeleted"); + + b.Property("Name"); + + b.Property("TenantId"); + + b.HasKey("Id"); + + b.ToTable("People"); + }); + + modelBuilder.Entity("Volo.Abp.TestApp.Domain.Phone", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Number"); + + b.Property("PersonId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("PersonId"); + + b.ToTable("AppPhones"); + }); + + modelBuilder.Entity("Volo.Abp.TestApp.Domain.Phone", b => + { + b.HasOne("Volo.Abp.TestApp.Domain.Person") + .WithMany("Phones") + .HasForeignKey("PersonId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/20171026101049_Added_IsDeleted_To_Person.cs b/test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/20171026101049_Added_IsDeleted_To_Person.cs new file mode 100644 index 0000000000..6a46d49d28 --- /dev/null +++ b/test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/20171026101049_Added_IsDeleted_To_Person.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Volo.Abp.EntityFrameworkCore.Tests.Migrations +{ + public partial class Added_IsDeleted_To_Person : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsDeleted", + table: "People", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsDeleted", + table: "People"); + } + } +} diff --git a/test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/TestAppDbContextModelSnapshot.cs b/test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/TestAppDbContextModelSnapshot.cs index 1ea7b55f65..36c10563a5 100644 --- a/test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/TestAppDbContextModelSnapshot.cs +++ b/test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/TestAppDbContextModelSnapshot.cs @@ -27,6 +27,8 @@ namespace Volo.Abp.EntityFrameworkCore.Tests.Migrations b.Property("Age"); + b.Property("IsDeleted"); + b.Property("Name"); b.Property("TenantId"); diff --git a/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/SoftDelete_Filter_Tests.cs b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/SoftDelete_Filter_Tests.cs new file mode 100644 index 0000000000..5892f88701 --- /dev/null +++ b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/SoftDelete_Filter_Tests.cs @@ -0,0 +1,26 @@ +using System.Linq; +using Shouldly; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.TestApp.Domain; +using Xunit; + +namespace Volo.Abp.EntityFrameworkCore.Repositories +{ + public class SoftDelete_Filter_Tests : EntityFrameworkCoreTestBase + { + private readonly IRepository _personRepository; + + public SoftDelete_Filter_Tests() + { + _personRepository = GetRequiredService>(); + } + + [Fact] + public void Should_Not_Get_Deleted_Entities_By_Default() + { + var people = _personRepository.GetList(); + people.Count.ShouldBe(1); + people.Any(p => p.Name == "Douglas").ShouldBeTrue(); + } + } +} diff --git a/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Person.cs b/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Person.cs index dd3e8f6b39..6f91db9c30 100644 --- a/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Person.cs +++ b/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Person.cs @@ -5,7 +5,7 @@ using Volo.Abp.MultiTenancy; namespace Volo.Abp.TestApp.Domain { - public class Person : AggregateRoot, IMultiTenant + public class Person : AggregateRoot, IMultiTenant, ISoftDelete { public virtual Guid? TenantId { get; set; } @@ -15,6 +15,8 @@ namespace Volo.Abp.TestApp.Domain public virtual Collection Phones { get; set; } + public bool IsDeleted { get; set; } + private Person() { diff --git a/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs b/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs index dcc16fbef0..80e6e8e2a7 100644 --- a/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs +++ b/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs @@ -30,6 +30,8 @@ namespace Volo.Abp.TestApp _personRepository.Insert(douglas); + _personRepository.Insert(new Person(Guid.NewGuid(), "John-Deleted", 33) { IsDeleted = true }); + var tenant1Person1 = new Person(Guid.NewGuid(), TenantId1 + "-Person1", 42, TenantId1); var tenant1Person2 = new Person(Guid.NewGuid(), TenantId1 + "-Person2", 43, TenantId1);