Browse Source

Merge remote-tracking branch 'abpframework/dev' into Translate

pull/2845/head
liangshiwei 6 years ago
parent
commit
e558b858e4
  1. 3
      docs/en/Audit-Logging.md
  2. 26
      docs/en/Caching.md
  3. 21
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs
  4. 1
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs
  5. 61
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs
  6. 11
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/UnitOfWorkExtensionDataTypes.cs
  7. 7
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/UnitOfWorkItemNames.cs
  8. 4
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs
  9. 40
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  10. 23
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs
  11. 1
      framework/src/Volo.Abp.Uow/Volo/Abp/Uow/IUnitOfWork.cs
  12. 8
      framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs
  13. 55
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HardDelete_Tests.cs

3
docs/en/Audit-Logging.md

@ -39,6 +39,7 @@ Here, a list of the options you can configure:
* `IsEnabled` (default: `true`): A root switch to enable or disable the auditing system. Other options is not used if this value is `false`.
* `HideErrors` (default: `true`): Audit log system hides and write regular [logs](Logging.md) if any error occurs while saving the audit log objects. If saving the audit logs is critical for your system, set this to `false` to throw exception in case of hiding the errors.
* `IsEnabledForAnonymousUsers` (default: `true`): If you want to write audit logs only for the authenticated users, set this to `false`. If you save audit logs for anonymous users, you will see `null` for `UserId` values for these users.
* `AlwaysLogOnException` (default: `true`): If you set to true, it always saves the audit log on an exception/error case without checking other options (except `IsEnabled`, which completely disables the audit logging).
* `IsEnabledForGetRequests` (default: `false`): HTTP GET requests should not make any change in the database normally and audit log system doesn't save audit log objects for GET request. Set this to `true` to enable it also for the GET requests.
* `ApplicationName`: If multiple applications saving audit logs into a single database, set this property to your application name, so you can distinguish the logs of different applications.
* `IgnoredTypes`: A list of `Type`s to be ignored for audit logging. If this is an entity type, changes for this type of entities will not be saved. This list is also used while serializing the action parameters.
@ -369,4 +370,4 @@ You can call other services, they may call others, they may change entities and
The Audit Logging Module basically implements the `IAuditingStore` to save the audit log objects to a database. It supports multiple database providers. This module is added to the startup templates by default.
See [the Audit Logging Module document](Modules/Audit-Logging.md) for more about it.
See [the Audit Logging Module document](Modules/Audit-Logging.md) for more about it.

26
docs/en/Caching.md

@ -2,6 +2,32 @@
ABP framework extends ASP.NET Core's distributed caching system.
## Volo.Abp.Caching Package
> This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually.
Volo.Abp.Caching is the core package of the caching system. Install it to your project using the package manager console (PMC):
```
Install-Package Volo.Abp.Caching
```
Then you can add **AbpCachingModule** dependency to your module:
```c#
using Volo.Abp.Modularity;
using Volo.Abp.Caching;
namespace MyCompany.MyProject
{
[DependsOn(typeof(AbpCachingModule))]
public class MyModule : AbpModule
{
//...
}
}
```
## `IDistributedCache` Interface
ASP.NET Core defines the `IDistributedCache` interface to get/set cache values. But it has some difficulties:

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

@ -112,26 +112,5 @@ 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);
}
}
}

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

@ -18,6 +18,7 @@ namespace Volo.Abp.Domain.Repositories
public IDataFilter DataFilter { get; set; }
public ICurrentTenant CurrentTenant { get; set; }
public IUnitOfWorkManager UnitOfWorkManager { get; set; }
public virtual Type ElementType => GetQueryable().ElementType;

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

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
@ -44,31 +43,55 @@ namespace Volo.Abp.Domain.Repositories
}
}
public static async Task HardDeleteAsync<TEntity, TPrimaryKey>(this IRepository<TEntity, TPrimaryKey> repository, TEntity entity)
where TEntity : class, IEntity<TPrimaryKey>, ISoftDelete
public static async Task HardDeleteAsync<TEntity>(
this IBasicRepository<TEntity> repository,
TEntity entity,
bool autoSave = false,
CancellationToken cancellationToken = default
)
where TEntity : class, IEntity, ISoftDelete
{
var repo = ProxyHelper.UnProxy(repository) as IRepository<TEntity, TPrimaryKey>;
if (repo != null)
if (!(ProxyHelper.UnProxy(repository) is IUnitOfWorkManagerAccessor unitOfWorkManagerAccessor))
{
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>;
throw new AbpException($"The given repository (of type {repository.GetType().AssemblyQualifiedName}) should implement the {typeof(IUnitOfWorkManagerAccessor).AssemblyQualifiedName} interface in order to invoke the {nameof(HardDeleteAsync)} method!");
}
var hardDeleteKey = EntityHelper.GetHardDeleteKey(entity, baseRepository.CurrentTenant?.Id?.ToString());
hardDeleteEntities.Add(hardDeleteKey);
var uowManager = unitOfWorkManagerAccessor.UnitOfWorkManager;
if (uowManager == null)
{
throw new AbpException($"{nameof(unitOfWorkManagerAccessor.UnitOfWorkManager)} property of the given {nameof(repository)} object is null!");
}
await repo.DeleteAsync(entity);
if (uowManager.Current == null)
{
using (var uow = uowManager.Begin())
{
await HardDeleteWithUnitOfWorkAsync(repository, entity, autoSave, cancellationToken, uowManager.Current);
await uow.CompleteAsync(cancellationToken);
}
}
}
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())
else
{
await repository.HardDeleteAsync(entity);
await HardDeleteWithUnitOfWorkAsync(repository, entity, autoSave, cancellationToken, uowManager.Current);
}
}
private static async Task HardDeleteWithUnitOfWorkAsync<TEntity>(
IBasicRepository<TEntity> repository,
TEntity entity,
bool autoSave,
CancellationToken cancellationToken, IUnitOfWork currentUow
)
where TEntity : class, IEntity, ISoftDelete
{
var hardDeleteEntities = (HashSet<IEntity>) currentUow.Items.GetOrAdd(
UnitOfWorkItemNames.HardDeletedEntities,
() => new HashSet<IEntity>()
);
hardDeleteEntities.Add(entity);
await repository.DeleteAsync(entity, autoSave, cancellationToken);
}
}
}

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

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

7
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/UnitOfWorkItemNames.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.Domain.Repositories
{
public static class UnitOfWorkItemNames
{
public const string HardDeletedEntities = "AbpHardDeletedEntities";
}
}

4
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs

@ -80,7 +80,7 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
{
return includeDetails
? await WithDetails().ToListAsync(GetCancellationToken(cancellationToken))
: await DbSet.ToListAsync(GetCancellationToken(cancellationToken));
: await DbSet.ToListAsync(GetCancellationToken(cancellationToken));
}
public override async Task<long> GetCountAsync(CancellationToken cancellationToken = default)
@ -208,7 +208,7 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
{
return includeDetails
? await WithDetails().FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken))
: await DbSet.FindAsync(new object[] { id }, GetCancellationToken(cancellationToken));
: await DbSet.FindAsync(new object[] {id}, GetCancellationToken(cancellationToken));
}
public virtual async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default)

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

@ -50,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; }
@ -199,37 +200,24 @@ namespace Volo.Abp.EntityFrameworkCore
protected virtual void ApplyAbpConceptsForDeletedEntity(EntityEntry entry, EntityChangeReport changeReport)
{
if (IsHardDeleteEntity(entry))
if (TryCancelDeletionForSoftDelete(entry))
{
changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted));
return;
UpdateConcurrencyStamp(entry);
SetDeletionAuditProperties(entry);
}
CancelDeletionForSoftDelete(entry);
UpdateConcurrencyStamp(entry);
SetDeletionAuditProperties(entry);
changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted));
}
protected virtual bool IsHardDeleteEntity(EntityEntry entry)
protected virtual bool IsHardDeleted(EntityEntry entry)
{
if (UnitOfWorkManager?.Current?.Items == null)
{
return false;
}
if (!UnitOfWorkManager.Current.Items.ContainsKey(UnitOfWorkExtensionDataTypes.HardDelete))
var hardDeletedEntities = UnitOfWorkManager?.Current?.Items.GetOrDefault(UnitOfWorkItemNames.HardDeletedEntities) as HashSet<IEntity>;
if (hardDeletedEntities == null)
{
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);
return hardDeletedEntities.Contains(entry.Entity);
}
protected virtual void AddDomainEvents(EntityChangeReport changeReport, object entityAsObj)
@ -283,16 +271,22 @@ namespace Volo.Abp.EntityFrameworkCore
entity.ConcurrencyStamp = Guid.NewGuid().ToString("N");
}
protected virtual void CancelDeletionForSoftDelete(EntityEntry entry)
protected virtual bool TryCancelDeletionForSoftDelete(EntityEntry entry)
{
if (!(entry.Entity is ISoftDelete))
{
return;
return false;
}
if (IsHardDeleted(entry))
{
return false;
}
entry.Reload();
entry.State = EntityState.Modified;
entry.Entity.As<ISoftDelete>().IsDeleted = true;
return true;
}
protected virtual void CheckAndSetId(EntityEntry entry)

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 && !IsHardDeleteEntity(entity))
if (entity is ISoftDelete softDeleteEntity && !IsHardDeleted(entity))
{
softDeleteEntity.IsDeleted = true;
var result = await Collection.ReplaceOneAsync(
@ -175,32 +175,21 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
Collection.AsQueryable()
);
}
protected virtual bool IsHardDeleteEntity(TEntity entry)
protected virtual bool IsHardDeleted(TEntity entity)
{
if (UnitOfWorkManager?.Current?.Items == null)
var hardDeletedEntities = UnitOfWorkManager?.Current?.Items.GetOrDefault(UnitOfWorkItemNames.HardDeletedEntities) as HashSet<IEntity>;
if (hardDeletedEntities == 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);
return hardDeletedEntities.Contains(entity);
}
protected virtual FilterDefinition<TEntity> CreateEntityFilter(TEntity entity, bool withConcurrencyStamp = false, string concurrencyStamp = null)
{
throw new NotImplementedException(
$"{nameof(CreateEntityFilter)} is not implemented for MongoDB by default. It should be overrided and implemented by the deriving class!"
$"{nameof(CreateEntityFilter)} is not implemented for MongoDB by default. It should be overriden and implemented by the deriving class!"
);
}

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

@ -9,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

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

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
@ -31,6 +32,7 @@ namespace Volo.Abp.Uow
public IServiceProvider ServiceProvider { get; }
[NotNull]
public Dictionary<string, object> Items { get; }
private readonly Dictionary<string, IDatabaseApi> _databaseApis;
@ -48,6 +50,7 @@ namespace Volo.Abp.Uow
_databaseApis = new Dictionary<string, IDatabaseApi>();
_transactionApis = new Dictionary<string, ITransactionApi>();
Items = new Dictionary<string, object>();
}
@ -320,10 +323,5 @@ namespace Volo.Abp.Uow
{
return $"[UnitOfWork {Id}]";
}
public Dictionary<string, object> GetHardDeleteItems()
{
return Items;
}
}
}

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

@ -1,7 +1,5 @@
using Shouldly;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.Domain.Repositories;
@ -15,58 +13,57 @@ namespace Volo.Abp.TestApp.Testing
public abstract class HardDelete_Tests<TStartupModule> : TestAppTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
protected readonly IRepository<Person, Guid> _personRepository;
protected readonly IRepository<Person, Guid> PersonRepository;
protected readonly IDataFilter DataFilter;
protected readonly IUnitOfWorkManager _unitOfWorkManager;
public HardDelete_Tests()
protected readonly IUnitOfWorkManager UnitOfWorkManager;
protected HardDelete_Tests()
{
_personRepository = GetRequiredService<IRepository<Person, Guid>>();
PersonRepository = GetRequiredService<IRepository<Person, Guid>>();
DataFilter = GetRequiredService<IDataFilter>();
_unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
UnitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
}
[Fact]
public async Task Should_HardDelete_Entity_With_Collection()
public async Task Should_HardDelete_Entities()
{
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 douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId);
await PersonRepository.HardDeleteAsync(douglas);
var deletedDougles = await _personRepository.FindAsync(TestDataBuilder.UserDouglasId);
deletedDougles.ShouldBeNull();
}
douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas.ShouldBeNull();
}
[Fact]
public async Task Should_HardDelete_Soft_Deleted_Entities()
{
var douglas = await _personRepository.GetAsync(TestDataBuilder.UserDouglasId);
await _personRepository.DeleteAsync(douglas);
var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId);
await PersonRepository.DeleteAsync(douglas);
douglas = await _personRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas.ShouldBeNull();
using (DataFilter.Disable<ISoftDelete>())
{
douglas = await _personRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas.ShouldNotBeNull();
douglas.IsDeleted.ShouldBeTrue();
douglas.DeletionTime.ShouldNotBeNull();
}
using (var uow = _unitOfWorkManager.Begin())
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();
douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId);
}
await PersonRepository.HardDeleteAsync(douglas);
await uow.CompleteAsync();
}
douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas.ShouldBeNull();
}
}
}

Loading…
Cancel
Save