Browse Source

Introduce `IHasEntityVersion` audit property

pull/14197/head
gdlcf88 4 years ago
parent
commit
336ddf5178
  1. 12
      framework/src/Volo.Abp.Auditing.Contracts/Volo/Abp/Auditing/IHasEntityVersion.cs
  2. 10
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditPropertySetter.cs
  3. 2
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditPropertySetter.cs
  4. 33
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/ExternalEntitySynchronizer.cs
  5. 11
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/IHasRemoteModificationTime.cs
  6. 6
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  7. 6
      framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs
  8. 6
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs
  9. 3
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AuditPropertySetterTestBase.cs
  10. 21
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AuditPropertySetter_EntityVersion_Tests.cs
  11. 10
      framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/Events/Distributed/ExternalEntitySynchronizers/Book.cs
  12. 42
      framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/Events/Distributed/ExternalEntitySynchronizers/BookEntityJsonConverter.cs
  13. 3
      framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/Events/Distributed/ExternalEntitySynchronizers/BookSynchronizer.cs
  14. 37
      framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/Events/Distributed/ExternalEntitySynchronizers/ExternalEntitySynchronizer_Tests.cs
  15. 7
      framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/Events/Distributed/ExternalEntitySynchronizers/RemoteBookEto.cs
  16. 5
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Person.cs
  17. 16
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Auditing_Tests.cs

12
framework/src/Volo.Abp.Auditing.Contracts/Volo/Abp/Auditing/IHasEntityVersion.cs

@ -0,0 +1,12 @@
namespace Volo.Abp.Auditing;
/// <summary>
/// An entity version property that auto-increments when the entity changes.
/// </summary>
public interface IHasEntityVersion
{
/// <summary>
/// An entity version property that auto-increments when the entity changes.
/// </summary>
int EntityVersion { get; }
}

10
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditPropertySetter.cs

@ -39,6 +39,14 @@ public class AuditPropertySetter : IAuditPropertySetter, ITransientDependency
SetDeleterId(targetObject);
}
public virtual void IncrementEntityVersionProperty(object targetObject)
{
if (targetObject is IHasEntityVersion objectWithEntityVersion)
{
ObjectHelper.TrySetProperty(objectWithEntityVersion, x => x.EntityVersion, x => x.EntityVersion + 1);
}
}
protected virtual void SetCreationTime(object targetObject)
{
if (!(targetObject is IHasCreationTime objectWithCreationTime))
@ -177,4 +185,4 @@ public class AuditPropertySetter : IAuditPropertySetter, ITransientDependency
ObjectHelper.TrySetProperty(deletionAuditedObject, x => x.DeleterId, () => CurrentUser.Id);
}
}
}

2
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditPropertySetter.cs

@ -7,4 +7,6 @@ public interface IAuditPropertySetter
void SetModificationProperties(object targetObject);
void SetDeletionProperties(object targetObject);
void IncrementEntityVersionProperty(object targetObject);
}

33
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/ExternalEntitySynchronizer.cs

@ -13,8 +13,8 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed;
public abstract class ExternalEntitySynchronizer<TEntity, TKey, TExternalEntityEto> :
ExternalEntitySynchronizer<TEntity, TExternalEntityEto>
where TEntity : class, IEntity<TKey>, IHasRemoteModificationTime
where TExternalEntityEto : EntityEto, IHasModificationTime
where TEntity : class, IEntity<TKey>, IHasEntityVersion
where TExternalEntityEto : EntityEto, IHasEntityVersion
{
private readonly IRepository<TEntity, TKey> _repository;
@ -48,8 +48,8 @@ public abstract class ExternalEntitySynchronizer<TEntity, TExternalEntityEto> :
IDistributedEventHandler<EntityUpdatedEto<TExternalEntityEto>>,
IDistributedEventHandler<EntityDeletedEto<TExternalEntityEto>>,
IUnitOfWorkEnabled
where TEntity : class, IEntity, IHasRemoteModificationTime
where TExternalEntityEto : EntityEto, IHasModificationTime
where TEntity : class, IEntity, IHasEntityVersion
where TExternalEntityEto : EntityEto, IHasEntityVersion
{
protected IObjectMapper ObjectMapper { get; }
private readonly IRepository<TEntity> _repository;
@ -73,7 +73,7 @@ public abstract class ExternalEntitySynchronizer<TEntity, TExternalEntityEto> :
return;
}
await CreateOrUpdateEntityAsync(eventData.Entity);
await TryCreateOrUpdateEntityAsync(eventData.Entity);
}
public virtual async Task HandleEventAsync(EntityUpdatedEto<TExternalEntityEto> eventData)
@ -83,7 +83,7 @@ public abstract class ExternalEntitySynchronizer<TEntity, TExternalEntityEto> :
return;
}
await CreateOrUpdateEntityAsync(eventData.Entity);
await TryCreateOrUpdateEntityAsync(eventData.Entity);
}
public virtual async Task HandleEventAsync(EntityDeletedEto<TExternalEntityEto> eventData)
@ -96,29 +96,31 @@ public abstract class ExternalEntitySynchronizer<TEntity, TExternalEntityEto> :
await TryDeleteEntityAsync(eventData.Entity);
}
protected virtual async Task CreateOrUpdateEntityAsync(TExternalEntityEto eto)
protected virtual async Task<bool> TryCreateOrUpdateEntityAsync(TExternalEntityEto eto)
{
var localEntity = await FindLocalEntityAsync(eto);
if (!await IsEtoNewerAsync(eto, localEntity))
{
return;
return false;
}
if (localEntity == null)
{
localEntity = await MapToEntityAsync(eto);
ObjectHelper.TrySetProperty(localEntity, x => x.RemoteLastModificationTime, () => eto.LastModificationTime);
ObjectHelper.TrySetProperty(localEntity, x => x.EntityVersion, () => eto.EntityVersion);
await _repository.InsertAsync(localEntity, true);
}
else
{
await MapToEntityAsync(eto, localEntity);
ObjectHelper.TrySetProperty(localEntity, x => x.RemoteLastModificationTime, () => eto.LastModificationTime);
ObjectHelper.TrySetProperty(localEntity, x => x.EntityVersion, () => eto.EntityVersion);
await _repository.UpdateAsync(localEntity, true);
}
return true;
}
protected virtual Task<TEntity> MapToEntityAsync(TExternalEntityEto eto)
@ -132,16 +134,18 @@ public abstract class ExternalEntitySynchronizer<TEntity, TExternalEntityEto> :
return Task.CompletedTask;
}
protected virtual async Task TryDeleteEntityAsync(TExternalEntityEto eto)
protected virtual async Task<bool> TryDeleteEntityAsync(TExternalEntityEto eto)
{
var localEntity = await FindLocalEntityAsync(eto);
if (localEntity == null)
{
return;
return false;
}
await _repository.DeleteAsync(localEntity, true);
return true;
}
[ItemCanBeNull]
@ -149,9 +153,6 @@ public abstract class ExternalEntitySynchronizer<TEntity, TExternalEntityEto> :
protected virtual Task<bool> IsEtoNewerAsync(TExternalEntityEto eto, [CanBeNull] TEntity localEntity)
{
return Task.FromResult(
localEntity?.RemoteLastModificationTime == null ||
eto.LastModificationTime > localEntity.RemoteLastModificationTime
);
return Task.FromResult(localEntity == null || eto.EntityVersion > localEntity.EntityVersion);
}
}

11
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/IHasRemoteModificationTime.cs

@ -1,11 +0,0 @@
using System;
namespace Volo.Abp.Domain.Entities.Events.Distributed;
public interface IHasRemoteModificationTime
{
/// <summary>
/// The last modified time for the synchronized remote entity.
/// </summary>
DateTime? RemoteLastModificationTime { get; }
}

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

@ -459,6 +459,7 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
{
if (entry.State == EntityState.Modified && entry.Properties.Any(x => x.IsModified && x.Metadata.ValueGenerated == ValueGenerated.Never))
{
IncrementEntityVersionProperty(entry);
SetModificationAuditProperties(entry);
if (entry.Entity is ISoftDelete && entry.Entity.As<ISoftDelete>().IsDeleted)
@ -574,6 +575,11 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
AuditPropertySetter?.SetDeletionProperties(entry.Entity);
}
protected virtual void IncrementEntityVersionProperty(EntityEntry entry)
{
AuditPropertySetter?.IncrementEntityVersionProperty(entry.Entity);
}
protected virtual void ConfigureBaseProperties<TEntity>(ModelBuilder modelBuilder, IMutableEntityType mutableEntityType)
where TEntity : class
{

6
framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs

@ -157,6 +157,11 @@ public class MemoryDbRepository<TMemoryDbContext, TEntity> : RepositoryBase<TEnt
AuditPropertySetter.SetDeletionProperties(entity);
}
protected virtual void IncrementEntityVersionProperty(TEntity entity)
{
AuditPropertySetter.IncrementEntityVersionProperty(entity);
}
protected virtual void TriggerEntityCreateEvents(TEntity entity)
{
EntityChangeEventHelper.PublishEntityCreatedEvent(entity);
@ -222,6 +227,7 @@ public class MemoryDbRepository<TMemoryDbContext, TEntity> : RepositoryBase<TEnt
bool autoSave = false,
CancellationToken cancellationToken = default)
{
IncrementEntityVersionProperty(entity);
SetModificationAuditProperties(entity);
if (entity is ISoftDelete softDeleteEntity && softDeleteEntity.IsDeleted)

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

@ -177,6 +177,7 @@ public class MongoDbRepository<TMongoDbContext, TEntity>
{
cancellationToken = GetCancellationToken(cancellationToken);
IncrementEntityVersionProperty(entity);
SetModificationAuditProperties(entity);
if (entity is ISoftDelete softDeleteEntity && softDeleteEntity.IsDeleted)
@ -654,6 +655,11 @@ public class MongoDbRepository<TMongoDbContext, TEntity>
AuditPropertySetter.SetDeletionProperties(entity);
}
protected virtual void IncrementEntityVersionProperty(TEntity entity)
{
AuditPropertySetter.IncrementEntityVersionProperty(entity);
}
protected virtual void TriggerDomainEvents(object entity)
{
var generatesDomainEventsEntity = entity as IGeneratesDomainEvents;

3
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AuditPropertySetterTestBase.cs

@ -48,7 +48,7 @@ public class AuditPropertySetterTestBase
}
public class MyAuditedObject : IMultiTenant, IFullAuditedObject
public class MyAuditedObject : IMultiTenant, IFullAuditedObject, IHasEntityVersion
{
public Guid? TenantId { get; set; }
public DateTime CreationTime { get; set; }
@ -58,5 +58,6 @@ public class AuditPropertySetterTestBase
public bool IsDeleted { get; set; }
public DateTime? DeletionTime { get; set; }
public Guid? DeleterId { get; set; }
public int EntityVersion { get; set; }
}
}

21
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AuditPropertySetter_EntityVersion_Tests.cs

@ -0,0 +1,21 @@
using Shouldly;
using Xunit;
namespace Volo.Abp.Auditing;
public class AuditPropertySetter_EntityVersion_Tests : AuditPropertySetterTestBase
{
[Fact]
public void Should_Do_Nothing_For_Non_Audited_Entity()
{
AuditPropertySetter.IncrementEntityVersionProperty(new MyEmptyObject());
}
[Fact]
public void Should_Increment_EntityVersion()
{
AuditPropertySetter.IncrementEntityVersionProperty(TargetObject);
TargetObject.EntityVersion.ShouldBe(1);
}
}

10
framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/Events/Distributed/ExternalEntitySynchronizers/Book.cs

@ -1,19 +1,21 @@
using System;
using Volo.Abp.Auditing;
namespace Volo.Abp.Domain.Entities.Events.Distributed.ExternalEntitySynchronizers;
public class Book : Entity<Guid>, IHasRemoteModificationTime
public class Book : Entity<Guid>, IHasEntityVersion
{
public virtual DateTime? RemoteLastModificationTime { get; protected set; }
public virtual int Sold { get; set; }
public virtual int EntityVersion { get; protected set; }
protected Book()
{
}
public Book(Guid id, int sold) : base(id)
public Book(Guid id, int sold, int entityVersion) : base(id)
{
Sold = sold;
EntityVersion = entityVersion;
}
}

42
framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/Events/Distributed/ExternalEntitySynchronizers/BookEntityJsonConverter.cs

@ -1,42 +0,0 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Volo.Abp.Domain.Entities.Events.Distributed.ExternalEntitySynchronizers;
public class BookEntityJsonConverter : JsonConverter<Book>
{
private JsonSerializerOptions _writeJsonSerializerOptions;
public override Book Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var jsonDocument = JsonDocument.ParseValue(ref reader);
if (jsonDocument.RootElement.ValueKind != JsonValueKind.Object)
{
throw new JsonException("RootElement's ValueKind is not Object!");
}
var entity = (Book)jsonDocument.RootElement.Deserialize(typeToConvert);
if (entity == null)
{
throw new JsonException("RootElement's ValueKind is not Object!");
}
ObjectHelper.TrySetProperty(entity, x => x.RemoteLastModificationTime, () =>
{
var property = jsonDocument.RootElement.GetProperty("RemoteLastModificationTime");
return property.ValueKind == JsonValueKind.Null ? null : property.GetDateTime();
});
return entity;
}
public override void Write(Utf8JsonWriter writer, Book value, JsonSerializerOptions options)
{
_writeJsonSerializerOptions ??= JsonSerializerOptionsHelper.Create(options, this);
JsonSerializer.Serialize(writer, value, _writeJsonSerializerOptions);
}
}

3
framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/Events/Distributed/ExternalEntitySynchronizers/BookSynchronizer.cs

@ -7,7 +7,8 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed.ExternalEntitySynchronizer
public class BookSynchronizer : ExternalEntitySynchronizer<Book, Guid, RemoteBookEto>, ITransientDependency
{
public BookSynchronizer(IObjectMapper objectMapper, IRepository<Book, Guid> repository) : base(objectMapper, repository)
public BookSynchronizer(IObjectMapper objectMapper, IRepository<Book, Guid> repository)
: base(objectMapper, repository)
{
}
}

37
framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/Events/Distributed/ExternalEntitySynchronizers/ExternalEntitySynchronizer_Tests.cs

@ -8,7 +8,6 @@ using Volo.Abp.Autofac;
using Volo.Abp.AutoMapper;
using Volo.Abp.Data;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Repositories.MemoryDb;
using Volo.Abp.MemoryDb;
using Volo.Abp.Modularity;
using Volo.Abp.Testing;
@ -32,15 +31,13 @@ public class ExternalEntitySynchronizer_Tests : AbpIntegratedTest<ExternalEntity
(await repository.FindAsync(bookId)).ShouldBeNull();
var remoteBookEto = new RemoteBookEto {
KeysAsString = bookId.ToString(), LastModificationTime = DateTime.Now, Sold = 1
};
var remoteBookEto = new RemoteBookEto { KeysAsString = bookId.ToString(), EntityVersion = 0, Sold = 1 };
await bookSynchronizer.HandleEventAsync(new EntityCreatedEto<RemoteBookEto>(remoteBookEto));
var book = await repository.FindAsync(bookId);
book.ShouldNotBeNull();
book.RemoteLastModificationTime.ShouldBe(remoteBookEto.LastModificationTime);
book.EntityVersion.ShouldBe(remoteBookEto.EntityVersion);
book.Sold.ShouldBe(1);
}
@ -57,37 +54,34 @@ public class ExternalEntitySynchronizer_Tests : AbpIntegratedTest<ExternalEntity
(await repository.FindAsync(bookId)).ShouldBeNull();
var remoteBookEto = new RemoteBookEto {
KeysAsString = bookId.ToString(), LastModificationTime = DateTime.Now, Sold = 1
};
var remoteBookEto = new RemoteBookEto { KeysAsString = bookId.ToString(), EntityVersion = 0, Sold = 1 };
await bookSynchronizer.HandleEventAsync(new EntityUpdatedEto<RemoteBookEto>(remoteBookEto));
var book = await repository.FindAsync(bookId);
book.ShouldNotBeNull();
book.RemoteLastModificationTime.ShouldBe(remoteBookEto.LastModificationTime);
book.EntityVersion.ShouldBe(remoteBookEto.EntityVersion);
book.Sold.ShouldBe(1);
remoteBookEto.LastModificationTime = DateTime.Now;
remoteBookEto.EntityVersion = 1;
remoteBookEto.Sold = 2;
await bookSynchronizer.HandleEventAsync(new EntityUpdatedEto<RemoteBookEto>(remoteBookEto));
book = await repository.FindAsync(bookId);
book.ShouldNotBeNull();
book.RemoteLastModificationTime.ShouldBe(remoteBookEto.LastModificationTime);
book.EntityVersion.ShouldBe(remoteBookEto.EntityVersion);
book.Sold.ShouldBe(2);
// Should skip synchronizing older remote entities.
var originalLastModificationTime = remoteBookEto.LastModificationTime;
remoteBookEto.LastModificationTime = remoteBookEto.LastModificationTime.Value.AddTicks(-1);
remoteBookEto.EntityVersion = 0;
remoteBookEto.Sold = 3;
await bookSynchronizer.HandleEventAsync(new EntityUpdatedEto<RemoteBookEto>(remoteBookEto));
// Should skip synchronizing older remote entities.
book = await repository.FindAsync(bookId);
book.ShouldNotBeNull();
book.RemoteLastModificationTime.ShouldBe(originalLastModificationTime);
book.EntityVersion.ShouldBe(1);
book.Sold.ShouldBe(2);
}
@ -102,16 +96,14 @@ public class ExternalEntitySynchronizer_Tests : AbpIntegratedTest<ExternalEntity
var bookSynchronizer = GetRequiredService<BookSynchronizer>();
var repository = GetRequiredService<IRepository<Book, Guid>>();
await repository.InsertAsync(new Book(bookId, 1), true);
await repository.InsertAsync(new Book(bookId, 1, 0), true);
var book = await repository.FindAsync(bookId);
book.ShouldNotBeNull();
book.Id.ShouldBe(bookId);
book.RemoteLastModificationTime.ShouldBeNull();
book.EntityVersion.ShouldBe(0);
var remoteBookEto = new RemoteBookEto {
KeysAsString = bookId.ToString(), LastModificationTime = DateTime.Now, Sold = 1
};
var remoteBookEto = new RemoteBookEto { KeysAsString = bookId.ToString(), EntityVersion = 0, Sold = 1 };
await bookSynchronizer.HandleEventAsync(new EntityDeletedEto<RemoteBookEto>(remoteBookEto));
@ -149,11 +141,6 @@ public class ExternalEntitySynchronizer_Tests : AbpIntegratedTest<ExternalEntity
options.AddDefaultRepositories(includeAllEntities: true);
});
Configure<Utf8JsonMemoryDbSerializerOptions>(options =>
{
options.JsonSerializerOptions.Converters.Add(new BookEntityJsonConverter());
});
context.Services.AddAutoMapperObjectMapper<TestModule>();
Configure<AbpAutoMapperOptions>(options =>
{

7
framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/Events/Distributed/ExternalEntitySynchronizers/RemoteBookEto.cs

@ -1,11 +1,10 @@
using System;
using Volo.Abp.Auditing;
using Volo.Abp.Auditing;
namespace Volo.Abp.Domain.Entities.Events.Distributed.ExternalEntitySynchronizers;
public class RemoteBookEto : EntityEto, IHasModificationTime
public class RemoteBookEto : EntityEto, IHasEntityVersion
{
public DateTime? LastModificationTime { get; set; }
public int EntityVersion { get; set; }
public int Sold { get; set; }
}

5
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Person.cs

@ -1,12 +1,13 @@
using System;
using System.Collections.ObjectModel;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Timing;
namespace Volo.Abp.TestApp.Domain;
public class Person : FullAuditedAggregateRoot<Guid>, IMultiTenant
public class Person : FullAuditedAggregateRoot<Guid>, IMultiTenant, IHasEntityVersion
{
public virtual Guid? TenantId { get; set; }
@ -25,6 +26,8 @@ public class Person : FullAuditedAggregateRoot<Guid>, IMultiTenant
public virtual DateTime LastActiveTime { get; set; }
public int EntityVersion { get; set; }
private Person()
{
}

16
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Auditing_Tests.cs

@ -111,4 +111,20 @@ public abstract class Auditing_Tests<TStartupModule> : TestAppTestBase<TStartupM
douglas.DeleterId.ShouldBe(CurrentUserId);
}
}
[Fact]
public async Task Should_Increment_EntityVersion_Property()
{
var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId);
douglas.EntityVersion.ShouldBe(0);
douglas.Age++;
await PersonRepository.UpdateAsync(douglas);
douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas.ShouldNotBeNull();
douglas.EntityVersion.ShouldBe(1);
}
}

Loading…
Cancel
Save