diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en-GB.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en-GB.json index 82ffbf0f4c..442384bd0a 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en-GB.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en-GB.json @@ -32,6 +32,7 @@ "SeeDocuments": "See Documents", "Samples": "Samples", "FreeDDDBook": "Free DDD E-book", - "New": "New" + "New": "New", + "Volo.AbpIo.Domain:020005": "License extend year can not be lower than {MinExtendLicenseYear} year and greater than {MaxExtendLicenseYear} years " } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json index 646c2f9648..b9c8d23563 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json @@ -34,6 +34,7 @@ "Framework": "Framework", "Support": "Support", "FreeDDDBook": "Free DDD E-book", - "New": "New" + "New": "New", + "Volo.AbpIo.Domain:020005": "License extend year can not be lower than {MinExtendLicenseYear} year and greater than {MaxExtendLicenseYear} years " } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json index 2f932f299b..04c114a6c2 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json @@ -28,6 +28,7 @@ "Commercial": "Ticari", "SeeDocuments": "Dokümanlara Göz Atın", "FreeDDDBook": "Ücretsiz DDD Kitabı", - "New": "Yeni" + "New": "Yeni", + "Volo.AbpIo.Domain:020005": "Lisans uzatma yılı {MinExtendLicenseYear} yıldan az {MaxExtendLicenseYear} yıldan fazla olamaz." } } \ No newline at end of file diff --git a/docs/en/Domain-Driven-Design.md b/docs/en/Domain-Driven-Design.md index f18c1882ef..03a6232ba3 100644 --- a/docs/en/Domain-Driven-Design.md +++ b/docs/en/Domain-Driven-Design.md @@ -32,6 +32,6 @@ DDD mostly interest in the **Domain** and the **Application** layers, rather tha * [Data Transfer Objects (DTOs)](Data-Transfer-Objects.md) * [Unit of Work](Unit-Of-Work.md) -## The Ultimate DDD Implementation Guide +## Free E-Book: Implementing DDD -See the [Implementing Domain Driven Design](Domain-Driven-Design-Implementation-Guide.md) guide as a **complete reference**. The Guide explains the Domain Driven Design and introduces explicit **rules and examples** to give a deep understanding of the **implementation details**. \ No newline at end of file +See the [Implementing Domain Driven Design book](https://abp.io/books/implementing-domain-driven-design) as a **complete reference**. This book explains the Domain Driven Design and introduces explicit **rules and examples** to give a deep understanding of the **implementation details**. \ No newline at end of file diff --git a/docs/en/Index.md b/docs/en/Index.md index de88e2f2f8..8483d61612 100644 --- a/docs/en/Index.md +++ b/docs/en/Index.md @@ -29,8 +29,9 @@ ABP has a **comprehensive documentation** that not only explains the ABP Framewo ABP offers a complete, modular and layered software architecture based on [Domain Driven Design](Domain-Driven-Design.md) principles and patterns. It also provides the necessary infrastructure to implement this architecture. * See the [Modularity](Module-Development-Basics.md) document to understand the module system. -* [Implementing Domain Driven Design](Domain-Driven-Design-Implementation-Guide.md) document is an ultimate guide for who want to understand and implement the DDD. +* [Implementing Domain Driven Design book](https://abp.io/books/implementing-domain-driven-design?ref=doc) is an ultimate guide for who want to understand and implement the DDD with the ABP Framework. * [Microservice Architecture](Microservice-Architecture.md) document explains how ABP helps to create a microservice solution. +* [Multi-Tenancy](Multi-Tenancy.md) document introduces multi-tenancy and explores the ABP multi-tenancy infrastructure. ### Infrastructure @@ -55,6 +56,14 @@ See the [Application Modules](Modules/Index.md) document for all pre-built modul The [Startup templates](Startup-Templates/Index.md) are pre-built Visual Studio solution templates. You can create your own solution based on these templates to **immediately start your development**. +## Free E-Book: Implementing Domain Driven Design + +![Implementing Domain Driven Design](images/implementing-domain-driven-design-book.png) + +A **practical guide** for implementing Domain Driven Design (DDD). While the implementation details are **based on the ABP Framework** infrastructure, the basic concepts, principles and models can be applied to any solution, even if it is not a .NET solution. + +[Click here to get your free copy](https://abp.io/books/implementing-domain-driven-design?ref=doc). + ## ABP Community ### The Source Code diff --git a/docs/en/images/implementing-domain-driven-design-book.png b/docs/en/images/implementing-domain-driven-design-book.png new file mode 100644 index 0000000000..6568b46f66 Binary files /dev/null and b/docs/en/images/implementing-domain-driven-design-book.png differ diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/AbpCommonDbContextRegistrationOptions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/AbpCommonDbContextRegistrationOptions.cs index ee172b3409..886d8e020c 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/AbpCommonDbContextRegistrationOptions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/AbpCommonDbContextRegistrationOptions.cs @@ -15,7 +15,7 @@ namespace Volo.Abp.DependencyInjection public IServiceCollection Services { get; } - public List ReplacedDbContextTypes { get; } + public Dictionary ReplacedDbContextTypes { get; } public Type DefaultRepositoryDbContextType { get; protected set; } @@ -39,7 +39,7 @@ namespace Volo.Abp.DependencyInjection Services = services; DefaultRepositoryDbContextType = originalDbContextType; CustomRepositories = new Dictionary(); - ReplacedDbContextTypes = new List(); + ReplacedDbContextTypes = new Dictionary(); SpecifiedDefaultRepositories = new List(); } @@ -47,15 +47,20 @@ namespace Volo.Abp.DependencyInjection { return ReplaceDbContext(typeof(TOtherDbContext)); } + + public IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext() + { + return ReplaceDbContext(typeof(TOtherDbContext), typeof(TTargetDbContext)); + } - public IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext(Type otherDbContextType) + public IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext(Type otherDbContextType, Type targetDbContextType = null) { if (!otherDbContextType.IsAssignableFrom(OriginalDbContextType)) { throw new AbpException($"{OriginalDbContextType.AssemblyQualifiedName} should inherit/implement {otherDbContextType.AssemblyQualifiedName}!"); } - ReplacedDbContextTypes.AddIfNotContains(otherDbContextType); + ReplacedDbContextTypes[otherDbContextType] = targetDbContextType; return this; } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/IAbpCommonDbContextRegistrationOptionsBuilder.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/IAbpCommonDbContextRegistrationOptionsBuilder.cs index 34e74d90cd..248300ab62 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/IAbpCommonDbContextRegistrationOptionsBuilder.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/IAbpCommonDbContextRegistrationOptionsBuilder.cs @@ -75,9 +75,17 @@ namespace Volo.Abp.DependencyInjection IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext(); /// - /// Replaces given DbContext type with this DbContext type. + /// Replaces given DbContext type with the target DbContext type. + /// + /// The DbContext type to be replaced + /// The target DbContext type + IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext(); + + /// + /// Replaces given DbContext type with the given or this DbContext type. /// /// The DbContext type to be replaced - IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext(Type otherDbContextType); + /// The target DbContext type (optional, used this DbContext type if not provided) + IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext(Type otherDbContextType, Type targetDbContextType = null); } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs index a48575ddcc..658269a376 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs @@ -31,18 +31,21 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddTransient(DbContextOptionsFactory.Create); - foreach (var dbContextType in options.ReplacedDbContextTypes) + foreach (var entry in options.ReplacedDbContextTypes) { + var originalDbContextType = entry.Key; + var targetDbContextType = entry.Value ?? typeof(TDbContext); + services.Replace( ServiceDescriptor.Transient( - dbContextType, - sp => sp.GetRequiredService(typeof(TDbContext)) + originalDbContextType, + sp => sp.GetRequiredService(targetDbContextType) ) ); services.Configure(opts => { - opts.DbContextReplacements[dbContextType] = typeof(TDbContext); + opts.DbContextReplacements[originalDbContextType] = targetDbContextType; }); } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index 22f04542f1..9b488924e8 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -371,17 +371,20 @@ namespace Volo.Abp.EntityFrameworkCore protected virtual void ApplyAbpConceptsForModifiedEntity(EntityEntry entry, EntityChangeReport changeReport) { - UpdateConcurrencyStamp(entry); - SetModificationAuditProperties(entry); - - if (entry.Entity is ISoftDelete && entry.Entity.As().IsDeleted) - { - SetDeletionAuditProperties(entry); - changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted)); - } - else + if (entry.State == EntityState.Modified && entry.Properties.Any(x => x.IsModified && x.Metadata.ValueGenerated == ValueGenerated.Never)) { - changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Updated)); + UpdateConcurrencyStamp(entry); + SetModificationAuditProperties(entry); + + if (entry.Entity is ISoftDelete && entry.Entity.As().IsDeleted) + { + SetDeletionAuditProperties(entry); + changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted)); + } + else + { + changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Updated)); + } } } diff --git a/framework/src/Volo.Abp.MemoryDb/Microsoft/Extensions/DependencyInjection/AbpMemoryDbServiceCollectionExtensions.cs b/framework/src/Volo.Abp.MemoryDb/Microsoft/Extensions/DependencyInjection/AbpMemoryDbServiceCollectionExtensions.cs index 3cab95cb03..99ef1bd445 100644 --- a/framework/src/Volo.Abp.MemoryDb/Microsoft/Extensions/DependencyInjection/AbpMemoryDbServiceCollectionExtensions.cs +++ b/framework/src/Volo.Abp.MemoryDb/Microsoft/Extensions/DependencyInjection/AbpMemoryDbServiceCollectionExtensions.cs @@ -18,9 +18,17 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddSingleton(options.DefaultRepositoryDbContextType, sp => sp.GetRequiredService()); } - foreach (var dbContextType in options.ReplacedDbContextTypes) + foreach (var entry in options.ReplacedDbContextTypes) { - services.Replace(ServiceDescriptor.Singleton(dbContextType, sp => sp.GetRequiredService())); + var originalDbContextType = entry.Key; + var targetDbContextType = entry.Value ?? typeof(TMemoryDbContext); + + services.Replace( + ServiceDescriptor.Singleton( + originalDbContextType, + sp => sp.GetRequiredService(targetDbContextType) + ) + ); } new MemoryDbRepositoryRegistrar(options).AddRepositories(); diff --git a/framework/src/Volo.Abp.MongoDB/Microsoft/Extensions/DependencyInjection/AbpMongoDbServiceCollectionExtensions.cs b/framework/src/Volo.Abp.MongoDB/Microsoft/Extensions/DependencyInjection/AbpMongoDbServiceCollectionExtensions.cs index 7f9c6b8d42..c05dbe5b3a 100644 --- a/framework/src/Volo.Abp.MongoDB/Microsoft/Extensions/DependencyInjection/AbpMongoDbServiceCollectionExtensions.cs +++ b/framework/src/Volo.Abp.MongoDB/Microsoft/Extensions/DependencyInjection/AbpMongoDbServiceCollectionExtensions.cs @@ -25,23 +25,21 @@ namespace Microsoft.Extensions.DependencyInjection optionsBuilder?.Invoke(options); - foreach (var dbContextType in options.ReplacedDbContextTypes) - { - services.Replace(ServiceDescriptor.Transient(dbContextType, typeof(TMongoDbContext))); - } - - foreach (var dbContextType in options.ReplacedDbContextTypes) + foreach (var entry in options.ReplacedDbContextTypes) { + var originalDbContextType = entry.Key; + var targetDbContextType = entry.Value ?? typeof(TMongoDbContext); + services.Replace( ServiceDescriptor.Transient( - dbContextType, - sp => sp.GetRequiredService(typeof(TMongoDbContext)) + originalDbContextType, + sp => sp.GetRequiredService(targetDbContextType) ) ); services.Configure(opts => { - opts.DbContextReplacements[dbContextType] = typeof(TMongoDbContext); + opts.DbContextReplacements[originalDbContextType] = targetDbContextType; }); } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Auditing/Auditing_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Auditing/Auditing_Tests.cs index d1b1018e5c..ee1ca3572c 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Auditing/Auditing_Tests.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Auditing/Auditing_Tests.cs @@ -1,9 +1,53 @@ -using Volo.Abp.TestApp.Testing; +using System; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.TestApp; +using Volo.Abp.TestApp.Testing; +using Xunit; namespace Volo.Abp.EntityFrameworkCore.Auditing { public class Auditing_Tests : Auditing_Tests { + [Fact] + public async Task Should_Not_Set_Modification_If_Properties_Generated_By_Database() + { + await WithUnitOfWorkAsync((async () => + { + var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId); + douglas.LastActiveTime = DateTime.Now; + })); + await WithUnitOfWorkAsync((async () => + { + var douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId); + + douglas.ShouldNotBeNull(); + douglas.LastModificationTime.ShouldBeNull(); + douglas.LastModificationTime.ShouldBeNull(); + douglas.LastModifierId.ShouldBeNull(); + })); + } + + [Fact] + public async Task Should_Set_Modification_If_Properties_Not_Generated_By_Database() + { + await WithUnitOfWorkAsync((async () => + { + var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId); + douglas.LastActiveTime = DateTime.Now; + douglas.Age = 100; + })); + + await WithUnitOfWorkAsync((async () => + { + var douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId); + + douglas.ShouldNotBeNull(); + douglas.LastModificationTime.ShouldNotBeNull(); + douglas.LastModificationTime.Value.ShouldBeLessThanOrEqualTo(Clock.Now); + douglas.LastModifierId.ShouldBe(CurrentUserId); + })); + } } } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs index 23137f0f0c..516c7d5789 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.TestApp.SecondContext; using Volo.Abp.EntityFrameworkCore.TestApp.ThirdDbContext; @@ -16,10 +17,10 @@ namespace Volo.Abp.EntityFrameworkCore public DbSet Books { get; set; } public DbSet EntityWithIntPks { get; set; } - + public DbSet Author { get; set; } - - public TestMigrationsDbContext(DbContextOptions options) + + public TestMigrationsDbContext(DbContextOptions options) : base(options) { @@ -36,6 +37,12 @@ namespace Volo.Abp.EntityFrameworkCore b.HasKey(p => new { p.PersonId, p.Number }); }); + + modelBuilder.Entity(b => + { + b.Property(x => x.LastActiveTime).ValueGeneratedOnAddOrUpdate().HasDefaultValue(DateTime.Now); + }); + modelBuilder.Entity(b => { b.OwnsMany(c => c.Districts, d => diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs index 6263c116b8..014c9c42db 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore; +using System; +using Microsoft.EntityFrameworkCore; using Volo.Abp.DependencyInjection; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.Modeling; @@ -46,6 +47,11 @@ namespace Volo.Abp.TestApp.EntityFrameworkCore b.ApplyObjectExtensionMappings(); }); + modelBuilder.Entity(b => + { + b.Property(x => x.LastActiveTime).ValueGeneratedOnAddOrUpdate().HasDefaultValue(DateTime.Now); + }); + modelBuilder .Entity(p => { diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Person.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Person.cs index 9075740f11..f88e617072 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Person.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Person.cs @@ -23,9 +23,10 @@ namespace Volo.Abp.TestApp.Domain public virtual Collection Phones { get; set; } + public virtual DateTime LastActiveTime { get; set; } + private Person() { - } public Person(Guid id, string name, int age, Guid? tenantId = null, Guid? cityId = null) @@ -65,4 +66,4 @@ namespace Volo.Abp.TestApp.Domain ); } } -} \ No newline at end of file +} diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/models/internal/object-extensions.ts b/npm/ng-packs/packages/theme-shared/extensions/src/lib/models/internal/object-extensions.ts index ea8a9dad8f..776785ed4c 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/models/internal/object-extensions.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/models/internal/object-extensions.ts @@ -66,7 +66,7 @@ export interface ExtensionPropertyUiDto { onTable: ExtensionPropertyUiTableDto; onCreateForm: ExtensionPropertyUiFormDto; onEditForm: ExtensionPropertyUiFormDto; - lookup: ExtensionPropertyUiLookupDto; + lookup?: ExtensionPropertyUiLookupDto; } export interface ExtensionPropertyUiFormDto { diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts b/npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts index c0581aae45..f9538a25eb 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts @@ -148,7 +148,7 @@ function createMockEntities(): Record