Browse Source

Added HardDelete with EfCoreRepository and MongoDbRepository tests

pull/2807/head
Galip Tolga Erdem 6 years ago
parent
commit
eef0f5f59a
  1. 30
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs
  2. 4
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs
  3. 56
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs
  4. 11
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/UnitOfWorkExtensionDataTypes.cs
  5. 34
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  6. 23
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs
  7. 3
      framework/src/Volo.Abp.Uow/Volo/Abp/Uow/ChildUnitOfWork.cs
  8. 2
      framework/src/Volo.Abp.Uow/Volo/Abp/Uow/IUnitOfWork.cs
  9. 7
      framework/src/Volo.Abp.Uow/Volo/Abp/Uow/IUnitOfWorkManagerAccessor.cs
  10. 8
      framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs
  11. 8
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/HardDelete_Tests.cs
  12. 11
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/HardDelete_Tests.cs
  13. 11
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Phone.cs
  14. 72
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HardDelete_Tests.cs

30
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using JetBrains.Annotations;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Reflection;
namespace Volo.Abp.Domain.Entities
{
@ -11,6 +13,7 @@ namespace Volo.Abp.Domain.Entities
/// </summary>
public static class EntityHelper
{
public static bool IsEntity([NotNull] Type type)
{
return typeof(IEntity).IsAssignableFrom(type);
@ -82,10 +85,10 @@ namespace Volo.Abp.Domain.Entities
var lambdaBody = Expression.Equal(leftExpression, rightExpression);
return Expression.Lambda<Func<TEntity, bool>>(lambdaBody, lambdaParam);
}
public static void TrySetId<TKey>(
IEntity<TKey> entity,
Func<TKey> idFactory,
IEntity<TKey> entity,
Func<TKey> idFactory,
bool checkForDisableGuidGenerationAttribute = false)
{
//TODO: Can be optimized (by caching per entity type)?
@ -109,5 +112,26 @@ namespace Volo.Abp.Domain.Entities
idProperty.SetValue(entity, idFactory());
}
public static object GetEntityId(object entity)
{
if (!IsEntity(entity.GetType()))
{
throw new AbpException(entity.GetType() + " is not an Entity !");
}
return ReflectionHelper.GetValueByPath(entity, entity.GetType(), "Id");
}
public static string GetHardDeleteKey(object entity, string tenantId)
{
//if (entity is IMultiTenant) // IsMultiTenantEntity
if (typeof(IMultiTenant).IsAssignableFrom(entity.GetType()))
{
var tenantIdString = !string.IsNullOrEmpty(tenantId) ? tenantId : "null";
return entity.GetType().FullName + ";TenantId=" + tenantIdString + ";Id=" + GetEntityId(entity);
}
return entity.GetType().FullName + ";Id=" + GetEntityId(entity);
}
}
}

4
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs

@ -8,15 +8,17 @@ using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
namespace Volo.Abp.Domain.Repositories
{
public abstract class RepositoryBase<TEntity> : BasicRepositoryBase<TEntity>, IRepository<TEntity>
public abstract class RepositoryBase<TEntity> : BasicRepositoryBase<TEntity>, IRepository<TEntity>, IUnitOfWorkManagerAccessor
where TEntity : class, IEntity
{
public IDataFilter DataFilter { get; set; }
public ICurrentTenant CurrentTenant { get; set; }
public IUnitOfWorkManager UnitOfWorkManager { get; set; }
public virtual Type ElementType => GetQueryable().ElementType;

56
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs

@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Entities;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Uow;
namespace Volo.Abp.Domain.Repositories
{
@ -41,5 +43,59 @@ namespace Volo.Abp.Domain.Repositories
await repo.EnsurePropertyLoadedAsync(entity, propertyExpression, cancellationToken);
}
}
public static async Task HardDeleteAsync<TEntity, TPrimaryKey>(this IRepository<TEntity, TPrimaryKey> repository, TEntity entity)
where TEntity : class, IEntity<TPrimaryKey>, ISoftDelete
{
var repo = ProxyHelper.UnProxy(repository) as IRepository<TEntity, TPrimaryKey>;
if (repo != null)
{
var uow = ((IUnitOfWorkManagerAccessor)repo).UnitOfWorkManager;
var baseRepository = ((RepositoryBase<TEntity>)repo);
var items = ((IUnitOfWorkManagerAccessor)repo).UnitOfWorkManager.Current.Items;
var hardDeleteEntities = items.GetOrAdd(UnitOfWorkExtensionDataTypes.HardDelete, () => new HashSet<string>()) as HashSet<string>;
var hardDeleteKey = EntityHelper.GetHardDeleteKey(entity, baseRepository.CurrentTenant?.Id?.ToString());
hardDeleteEntities.Add(hardDeleteKey);
await repo.DeleteAsync(entity);
}
}
public static async Task HardDeleteAsync<TEntity, TPrimaryKey>(this IRepository<TEntity, TPrimaryKey> repository, Expression<Func<TEntity, bool>> predicate)
where TEntity : class, IEntity<TPrimaryKey>, ISoftDelete
{
foreach (var entity in repository.Where(predicate).ToList())
{
await repository.HardDeleteAsync(entity);
}
}
public static void HardDelete<TEntity, TPrimaryKey>(this IRepository<TEntity, TPrimaryKey> repository, TEntity entity)
where TEntity : class, IEntity<TPrimaryKey>, ISoftDelete
{
var repo = ProxyHelper.UnProxy(repository) as IRepository<TEntity, TPrimaryKey>;
if (repo != null)
{
var uow = ((IUnitOfWorkManagerAccessor)repo).UnitOfWorkManager;
var baseRepository = ((RepositoryBase<TEntity>)repo);
var items = ((IUnitOfWorkManagerAccessor)repo).UnitOfWorkManager.Current.Items;
var hardDeleteEntities = items.GetOrAdd(UnitOfWorkExtensionDataTypes.HardDelete, () => new HashSet<string>()) as HashSet<string>;
var hardDeleteKey = EntityHelper.GetHardDeleteKey(entity, baseRepository.CurrentTenant?.Id?.ToString());
hardDeleteEntities.Add(hardDeleteKey);
Task.FromResult(repo.DeleteAsync(entity));
}
}
public static void HardDelete<TEntity, TPrimaryKey>(this IRepository<TEntity, TPrimaryKey> repository, Expression<Func<TEntity, bool>> predicate)
where TEntity : class, IEntity<TPrimaryKey>, ISoftDelete
{
foreach (var entity in repository.Where(predicate).ToList())
{
repository.HardDelete(entity);
}
}
}
}

11
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/UnitOfWorkExtensionDataTypes.cs

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Volo.Abp.Domain.Repositories
{
public class UnitOfWorkExtensionDataTypes
{
public static string HardDelete { get; } = "HardDelete";
}
}

34
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

@ -16,6 +16,7 @@ using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EntityFrameworkCore.EntityHistory;
using Volo.Abp.EntityFrameworkCore.Modeling;
using Volo.Abp.EntityFrameworkCore.ValueConverters;
@ -23,6 +24,7 @@ using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Reflection;
using Volo.Abp.Timing;
using Volo.Abp.Uow;
namespace Volo.Abp.EntityFrameworkCore
{
@ -48,6 +50,7 @@ namespace Volo.Abp.EntityFrameworkCore
public IEntityHistoryHelper EntityHistoryHelper { get; set; }
public IAuditingManager AuditingManager { get; set; }
public IUnitOfWorkManager UnitOfWorkManager { get; set; }
public IClock Clock { get; set; }
@ -102,7 +105,7 @@ namespace Volo.Abp.EntityFrameworkCore
.Invoke(this, new object[] { modelBuilder, entityType });
}
}
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
try
@ -196,12 +199,39 @@ namespace Volo.Abp.EntityFrameworkCore
protected virtual void ApplyAbpConceptsForDeletedEntity(EntityEntry entry, EntityChangeReport changeReport)
{
if (IsHardDeleteEntity(entry))
{
changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted));
return;
}
CancelDeletionForSoftDelete(entry);
UpdateConcurrencyStamp(entry);
SetDeletionAuditProperties(entry);
changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted));
}
protected virtual bool IsHardDeleteEntity(EntityEntry entry)
{
if (UnitOfWorkManager?.Current?.Items == null)
{
return false;
}
if (!UnitOfWorkManager.Current.Items.ContainsKey(UnitOfWorkExtensionDataTypes.HardDelete))
{
return false;
}
var hardDeleteItems = UnitOfWorkManager.Current.Items[UnitOfWorkExtensionDataTypes.HardDelete];
if (!(hardDeleteItems is HashSet<string> objects))
{
return false;
}
string hardDeleteKey = EntityHelper.GetHardDeleteKey(entry.Entity, CurrentTenantId?.ToString());
return objects.Contains(hardDeleteKey);
}
protected virtual void AddDomainEvents(EntityChangeReport changeReport, object entityAsObj)
{
var generatesDomainEventsEntity = entityAsObj as IGeneratesDomainEvents;
@ -382,7 +412,7 @@ namespace Volo.Abp.EntityFrameworkCore
return;
}
var idPropertyBuilder = modelBuilder.Entity<TEntity>().Property(x => ((IEntity<Guid>) x).Id);
var idPropertyBuilder = modelBuilder.Entity<TEntity>().Property(x => ((IEntity<Guid>)x).Id);
if (idPropertyBuilder.Metadata.PropertyInfo.IsDefined(typeof(DatabaseGeneratedAttribute), true))
{
return;

23
framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs

@ -111,7 +111,7 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
await ApplyAbpConceptsForDeletedEntityAsync(entity);
var oldConcurrencyStamp = SetNewConcurrencyStamp(entity);
if (entity is ISoftDelete softDeleteEntity)
if (entity is ISoftDelete softDeleteEntity && !IsHardDeleteEntity(entity))
{
softDeleteEntity.IsDeleted = true;
var result = await Collection.ReplaceOneAsync(
@ -175,6 +175,27 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
Collection.AsQueryable()
);
}
protected virtual bool IsHardDeleteEntity(TEntity entry)
{
if (UnitOfWorkManager?.Current?.Items == null)
{
return false;
}
if (!UnitOfWorkManager.Current.Items.ContainsKey(UnitOfWorkExtensionDataTypes.HardDelete))
{
return false;
}
var hardDeleteItems = UnitOfWorkManager.Current.Items[UnitOfWorkExtensionDataTypes.HardDelete];
if (!(hardDeleteItems is HashSet<string> objects))
{
return false;
}
string hardDeleteKey = EntityHelper.GetHardDeleteKey(entry, CurrentTenant?.Id.ToString());
return objects.Contains(hardDeleteKey);
}
protected virtual FilterDefinition<TEntity> CreateEntityFilter(TEntity entity, bool withConcurrencyStamp = false, string concurrencyStamp = null)
{

3
framework/src/Volo.Abp.Uow/Volo/Abp/Uow/ChildUnitOfWork.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
@ -26,6 +27,8 @@ namespace Volo.Abp.Uow
public IServiceProvider ServiceProvider => _parent.ServiceProvider;
public Dictionary<string, object> Items => _parent.Items;
private readonly IUnitOfWork _parent;
public ChildUnitOfWork([NotNull] IUnitOfWork parent)

2
framework/src/Volo.Abp.Uow/Volo/Abp/Uow/IUnitOfWork.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
@ -8,6 +9,7 @@ namespace Volo.Abp.Uow
public interface IUnitOfWork : IDatabaseApiContainer, ITransactionApiContainer, IDisposable
{
Guid Id { get; }
Dictionary<string, object> Items { get; }
//TODO: Switch to OnFailed (sync) and OnDisposed (sync) methods to be compatible with OnCompleted
event EventHandler<UnitOfWorkFailedEventArgs> Failed;

7
framework/src/Volo.Abp.Uow/Volo/Abp/Uow/IUnitOfWorkManagerAccessor.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.Uow
{
public interface IUnitOfWorkManagerAccessor
{
IUnitOfWorkManager UnitOfWorkManager { get; }
}
}

8
framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs

@ -31,6 +31,8 @@ namespace Volo.Abp.Uow
public IServiceProvider ServiceProvider { get; }
public Dictionary<string, object> Items { get; }
private readonly Dictionary<string, IDatabaseApi> _databaseApis;
private readonly Dictionary<string, ITransactionApi> _transactionApis;
private readonly AbpUnitOfWorkDefaultOptions _defaultOptions;
@ -46,6 +48,7 @@ namespace Volo.Abp.Uow
_databaseApis = new Dictionary<string, IDatabaseApi>();
_transactionApis = new Dictionary<string, ITransactionApi>();
Items = new Dictionary<string, object>();
}
public virtual void Initialize(AbpUnitOfWorkOptions options)
@ -317,5 +320,10 @@ namespace Volo.Abp.Uow
{
return $"[UnitOfWork {Id}]";
}
public Dictionary<string, object> GetHardDeleteItems()
{
return Items;
}
}
}

8
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/HardDelete_Tests.cs

@ -0,0 +1,8 @@
using Volo.Abp.TestApp.Testing;
namespace Volo.Abp.EntityFrameworkCore.DataFiltering
{
public class HardDelete_Tests : HardDelete_Tests<AbpEntityFrameworkCoreTestModule>
{
}
}

11
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/HardDelete_Tests.cs

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
using Volo.Abp.TestApp.Testing;
namespace Volo.Abp.MongoDB.DataFiltering
{
public class HardDelete_Tests : HardDelete_Tests<AbpMongoDbTestModule>
{
}
}

11
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Phone.cs

@ -7,7 +7,7 @@ using Volo.Abp.Domain.Entities;
namespace Volo.Abp.TestApp.Domain
{
[Table("AppPhones")]
public class Phone : Entity
public class Phone : Entity<Guid>
{
public virtual Guid PersonId { get; set; }
@ -17,11 +17,12 @@ namespace Volo.Abp.TestApp.Domain
private Phone()
{
}
public Phone(Guid personId, string number, PhoneType type = PhoneType.Mobile)
{
Id = Guid.NewGuid();
PersonId = personId;
Number = number;
Type = type;
@ -29,7 +30,7 @@ namespace Volo.Abp.TestApp.Domain
public override object[] GetKeys()
{
return new object[] {PersonId, Number};
return new object[] { PersonId, Number };
}
}
@ -45,7 +46,7 @@ namespace Volo.Abp.TestApp.Domain
protected Order()
{
}
public Order(Guid id, string referenceNo)
@ -104,7 +105,7 @@ namespace Volo.Abp.TestApp.Domain
public override object[] GetKeys()
{
return new object[] {OrderId, ProductId};
return new object[] { OrderId, ProductId };
}
}
}

72
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HardDelete_Tests.cs

@ -0,0 +1,72 @@
using Shouldly;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Modularity;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.Uow;
using Xunit;
namespace Volo.Abp.TestApp.Testing
{
public abstract class HardDelete_Tests<TStartupModule> : TestAppTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
protected readonly IRepository<Person, Guid> _personRepository;
protected readonly IDataFilter DataFilter;
protected readonly IUnitOfWorkManager _unitOfWorkManager;
public HardDelete_Tests()
{
_personRepository = GetRequiredService<IRepository<Person, Guid>>();
DataFilter = GetRequiredService<IDataFilter>();
_unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
}
[Fact]
public async Task Should_HardDelete_Entity_With_Collection()
{
using (var uow = _unitOfWorkManager.Begin())
{
using (DataFilter.Disable<ISoftDelete>())
{
var douglas = await _personRepository.FindAsync(TestDataBuilder.UserDouglasId);
await _personRepository.HardDeleteAsync(x => x.Id == TestDataBuilder.UserDouglasId);
await uow.CompleteAsync();
}
var deletedDougles = await _personRepository.FindAsync(TestDataBuilder.UserDouglasId);
deletedDougles.ShouldBeNull();
}
}
[Fact]
public async Task Should_HardDelete_Soft_Deleted_Entities()
{
var douglas = await _personRepository.GetAsync(TestDataBuilder.UserDouglasId);
await _personRepository.DeleteAsync(douglas);
douglas = await _personRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas.ShouldBeNull();
using (DataFilter.Disable<ISoftDelete>())
{
douglas = await _personRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas.ShouldNotBeNull();
douglas.IsDeleted.ShouldBeTrue();
douglas.DeletionTime.ShouldNotBeNull();
}
using (var uow = _unitOfWorkManager.Begin())
{
using (DataFilter.Disable<ISoftDelete>())
{
douglas = await _personRepository.GetAsync(TestDataBuilder.UserDouglasId);
await _personRepository.HardDeleteAsync(douglas);
await uow.CompleteAsync();
var deletedDougles = await _personRepository.FindAsync(TestDataBuilder.UserDouglasId);
deletedDougles.ShouldBeNull();
}
}
}
}
}
Loading…
Cancel
Save