diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DomainEvents/UpdateAggregateRootWhenNavigationChanges_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DomainEvents/UpdateAggregateRootWhenNavigationChanges_Tests.cs index ebb380ce6e..8114288d71 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DomainEvents/UpdateAggregateRootWhenNavigationChanges_Tests.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DomainEvents/UpdateAggregateRootWhenNavigationChanges_Tests.cs @@ -15,6 +15,8 @@ public class UpdateAggregateRootWhenNavigationChanges_Tests : EntityFrameworkCor { private readonly IRepository _entityWithForeignKeyOnlyRepository; private readonly IRepository _childRepository; + private readonly IRepository _ownerRepository; + private readonly IRepository _entityChildRepository; private readonly IRepository _entityWithNavigationsRepository; private readonly IRepository _entityWithNavigationsForeignRepository; private readonly IUnitOfWorkManager _unitOfWorkManager; @@ -24,6 +26,8 @@ public class UpdateAggregateRootWhenNavigationChanges_Tests : EntityFrameworkCor { _entityWithForeignKeyOnlyRepository = GetRequiredService>(); _childRepository = GetRequiredService>(); + _ownerRepository = GetRequiredService>(); + _entityChildRepository = GetRequiredService>(); _entityWithNavigationsRepository = GetRequiredService>(); _entityWithNavigationsForeignRepository = GetRequiredService>(); _unitOfWorkManager = GetRequiredService(); @@ -63,6 +67,95 @@ public class UpdateAggregateRootWhenNavigationChanges_Tests : EntityFrameworkCor (await _entityWithForeignKeyOnlyRepository.GetAsync(principalId)).ConcurrencyStamp.ShouldBe(concurrencyStamp); } + [Fact] + public async Task Should_Not_Update_Principal_Entity_Without_Navigation_Property_On_Update_And_Delete() + { + var principalId = Guid.NewGuid(); + var childId = Guid.NewGuid(); + + await WithUnitOfWorkAsync(async () => + { + await _entityWithForeignKeyOnlyRepository.InsertAsync( + new AppEntityWithForeignKeyOnly(principalId, "Principal")); + await _childRepository.InsertAsync( + new AppEntityWithForeignKeyOnlyChild(childId, principalId, "Child")); + }); + + var concurrencyStamp = (await _entityWithForeignKeyOnlyRepository.GetAsync(principalId)).ConcurrencyStamp; + + await WithUnitOfWorkAsync(async () => + { + await _entityWithForeignKeyOnlyRepository.GetAsync(principalId); + + var child = await _childRepository.GetAsync(childId); + child.Name = "Child-Updated"; + await _childRepository.UpdateAsync(child); + }); + + (await _entityWithForeignKeyOnlyRepository.GetAsync(principalId)).ConcurrencyStamp.ShouldBe(concurrencyStamp); + + await WithUnitOfWorkAsync(async () => + { + await _entityWithForeignKeyOnlyRepository.GetAsync(principalId); + + await _childRepository.DeleteAsync(childId); + }); + + (await _entityWithForeignKeyOnlyRepository.GetAsync(principalId)).ConcurrencyStamp.ShouldBe(concurrencyStamp); + } + + [Fact] + public async Task Should_Update_The_Owner_But_Not_The_Referenced_Aggregate_Root_Of_A_Child_Entity() + { + var ownerId = Guid.NewGuid(); + var referencedId = Guid.NewGuid(); + + await WithUnitOfWorkAsync(async () => + { + await _ownerRepository.InsertAsync(new AppEntityWithForeignKeyOnlyOwner(ownerId, "Owner")); + await _entityWithForeignKeyOnlyRepository.InsertAsync( + new AppEntityWithForeignKeyOnly(referencedId, "Referenced")); + }); + + var ownerStamp = (await _ownerRepository.GetAsync(ownerId)).ConcurrencyStamp; + var referencedStamp = (await _entityWithForeignKeyOnlyRepository.GetAsync(referencedId)).ConcurrencyStamp; + + await WithUnitOfWorkAsync(async () => + { + await _ownerRepository.GetAsync(ownerId); + await _entityWithForeignKeyOnlyRepository.GetAsync(referencedId); + + await _entityChildRepository.InsertAsync( + new AppEntityWithForeignKeyOnlyEntityChild(Guid.NewGuid(), ownerId, referencedId, "Child")); + }); + + (await _ownerRepository.GetAsync(ownerId)).ConcurrencyStamp.ShouldNotBe(ownerStamp); + (await _entityWithForeignKeyOnlyRepository.GetAsync(referencedId)).ConcurrencyStamp.ShouldBe(referencedStamp); + } + + [Fact] + public async Task Should_Update_Aggregate_Root_When_Owned_Entity_Changes() + { + var entityId = Guid.NewGuid(); + + await WithUnitOfWorkAsync(async () => + { + await _entityWithNavigationsRepository.InsertAsync( + new AppEntityWithNavigations(entityId, "Entity")); + }); + + var concurrencyStamp = (await _entityWithNavigationsRepository.GetAsync(entityId)).ConcurrencyStamp; + + await WithUnitOfWorkAsync(async () => + { + var entity = await _entityWithNavigationsRepository.GetAsync(entityId); + entity.AppEntityWithValueObjectAddress = new AppEntityWithValueObjectAddress("Turkey"); + await _entityWithNavigationsRepository.UpdateAsync(entity); + }); + + (await _entityWithNavigationsRepository.GetAsync(entityId)).ConcurrencyStamp.ShouldNotBe(concurrencyStamp); + } + [Fact] public async Task Should_Update_Aggregate_Root_When_Navigation_Changes_By_Default() { 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 6db5871be9..b90a114195 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 @@ -42,6 +42,10 @@ public class TestMigrationsDbContext : AbpDbContext public DbSet AppEntityWithForeignKeyOnlyChild { get; set; } + public DbSet AppEntityWithForeignKeyOnlyOwner { get; set; } + + public DbSet AppEntityWithForeignKeyOnlyEntityChild { get; set; } + public DbSet Blogs { get; set; } public DbSet BlogPosts { get; set; } @@ -178,6 +182,19 @@ public class TestMigrationsDbContext : AbpDbContext b.HasOne().WithMany().HasForeignKey(x => x.AppEntityWithForeignKeyOnlyId); }); + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + b.HasMany(x => x.Children).WithOne().HasForeignKey(x => x.OwnerId); + }); + + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + // The owner has a navigation, the referenced aggregate root has not. + b.HasOne().WithMany().HasForeignKey(x => x.AppEntityWithForeignKeyOnlyId); + }); + modelBuilder.Entity(b => { b.ConfigureByConvention(); 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 5cf4c00ffd..18a99a8934 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 @@ -50,6 +50,10 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, public DbSet AppEntityWithForeignKeyOnlyChild { get; set; } + public DbSet AppEntityWithForeignKeyOnlyOwner { get; set; } + + public DbSet AppEntityWithForeignKeyOnlyEntityChild { get; set; } + public DbSet Blogs { get; set; } public DbSet BlogPosts { get; set; } @@ -177,6 +181,19 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, b.HasOne().WithMany().HasForeignKey(x => x.AppEntityWithForeignKeyOnlyId); }); + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + b.HasMany(x => x.Children).WithOne().HasForeignKey(x => x.OwnerId); + }); + + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + // The owner has a navigation, the referenced aggregate root has not. + b.HasOne().WithMany().HasForeignKey(x => x.AppEntityWithForeignKeyOnlyId); + }); + modelBuilder.Entity(b => { b.ConfigureByConvention(); diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs index 4d66fd7835..923bc4f96a 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs @@ -160,3 +160,47 @@ public class AppEntityWithForeignKeyOnlyChild : AggregateRoot public string Name { get; set; } } + +public class AppEntityWithForeignKeyOnlyOwner : AggregateRoot +{ + protected AppEntityWithForeignKeyOnlyOwner() + { + + } + + public AppEntityWithForeignKeyOnlyOwner(Guid id, string name) + : base(id) + { + Name = name; + } + + public string Name { get; set; } + + public virtual List Children { get; set; } +} + +/// +/// Belongs to the aggregate, +/// but references the aggregate root with a foreign key only. +/// +public class AppEntityWithForeignKeyOnlyEntityChild : Entity +{ + protected AppEntityWithForeignKeyOnlyEntityChild() + { + + } + + public AppEntityWithForeignKeyOnlyEntityChild(Guid id, Guid ownerId, Guid appEntityWithForeignKeyOnlyId, string name) + : base(id) + { + OwnerId = ownerId; + AppEntityWithForeignKeyOnlyId = appEntityWithForeignKeyOnlyId; + Name = name; + } + + public Guid OwnerId { get; set; } + + public Guid AppEntityWithForeignKeyOnlyId { get; set; } + + public string Name { get; set; } +}