diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs index 4b8a531db1..8d597f85b5 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs @@ -21,10 +21,7 @@ public class AbpEntityEntry { return _isModified || EntityEntry.State == EntityState.Modified || NavigationEntries.Any(n => n.IsModified); } - set - { - _isModified = value; - } + set => _isModified = value; } public AbpEntityEntry(string id, EntityEntry entityEntry) @@ -46,14 +43,11 @@ public class AbpEntityEntry continue; } - var navigation = EntityEntry.Navigations.FirstOrDefault(n => n.Metadata.Name == navigationEntry.Name); - - var currentValue = AbpNavigationEntry.GetOriginalValue(navigation?.CurrentValue); + var currentValue = AbpNavigationEntry.GetOriginalValue(navigationEntry.NavigationEntry.CurrentValue); if (currentValue == null) { continue; } - switch (navigationEntry.OriginalValue) { case null: diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEfCoreNavigationHelper_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEfCoreNavigationHelper_Tests.cs new file mode 100644 index 0000000000..578bc94417 --- /dev/null +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEfCoreNavigationHelper_Tests.cs @@ -0,0 +1,96 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.TestApp.Domain; +using Xunit; + +namespace Volo.Abp.EntityFrameworkCore; + +public class AbpEfCoreNavigationHelper_Tests : EntityFrameworkCoreTestBase +{ + private readonly IRepository _blogRepository; + + public AbpEfCoreNavigationHelper_Tests() + { + _blogRepository = GetRequiredService>(); + } + + [Fact] + public async Task Performance_Test() + { + //These time taken varies on different machines. + //I used relatively large values, but it can also check for performance problem. + var batchUpdateTime = TimeSpan.FromSeconds(30); + var queryTime = TimeSpan.FromSeconds(10); + + if (!Environment.GetEnvironmentVariable("GITHUB_ACTIONS").IsNullOrWhiteSpace()) + { + batchUpdateTime = batchUpdateTime * 6; + queryTime = queryTime * 6; + } + + + var stopWatch = Stopwatch.StartNew(); + await WithUnitOfWorkAsync(async () => + { + for (var i = 0; i < 5 * 1000; i++) + { + await _blogRepository.InsertAsync( + new Blog(Guid.NewGuid()) + { + Name = "Blog" + i, + BlogPosts = + [ + new BlogPost(Guid.NewGuid()) + { + Title = "Post" + i + } + ] + }); + } + }); + stopWatch.Stop(); + stopWatch.Elapsed.ShouldBeLessThan(batchUpdateTime); + + + stopWatch.Restart(); + var blogs = await _blogRepository.GetListAsync(includeDetails: true); + blogs.Count.ShouldBe(5 * 1000); + blogs.SelectMany(x => x.BlogPosts).Count().ShouldBe(5 * 1000); + stopWatch.Stop(); + stopWatch.Elapsed.ShouldBeLessThan(queryTime); + + + var blogId = blogs.First().Id; + stopWatch.Restart(); + await WithUnitOfWorkAsync(async () => + { + var blog = await _blogRepository.GetAsync(blogId); + blog.ShouldNotBeNull(); + for (var i = 0; i < 5 * 1000; i++) + { + blog.BlogPosts.Add( + new BlogPost(Guid.NewGuid()) + { + Title = "NewPost" + i + }); + } + await _blogRepository.UpdateAsync(blog); + }); + stopWatch.Stop(); + stopWatch.Elapsed.ShouldBeLessThan(batchUpdateTime); + + + var cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.CancelAfter(batchUpdateTime); + stopWatch.Restart(); + var blog = await _blogRepository.GetAsync(blogId, cancellationToken: cancellationTokenSource.Token); + blog.BlogPosts.Count.ShouldBe(5 * 1000 + 1); + stopWatch.Stop(); + stopWatch.Elapsed.ShouldBeLessThan(queryTime); + } +} diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs index e75af9a556..22c0b92477 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs @@ -53,6 +53,11 @@ public class AbpEntityFrameworkCoreTestModule : AbpModule { opt.DefaultWithDetailsFunc = q => q.Include(p => p.OneToOne).ThenInclude(x => x.OneToOne).Include(p => p.OneToMany).ThenInclude(x => x.OneToMany).Include(p => p.ManyToMany); }); + + options.Entity(opt => + { + opt.DefaultWithDetailsFunc = q => q.Include(p => p.BlogPosts); + }); }); context.Services.AddAbpDbContext(options => 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 6841f8d504..d05e367ff7 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 @@ -30,6 +30,9 @@ public class TestMigrationsDbContext : AbpDbContext public DbSet AppEntityWithNavigationsForeign { get; set; } + public DbSet Blogs { get; set; } + public DbSet BlogPosts { get; set; } + public TestMigrationsDbContext(DbContextOptions options) : base(options) { @@ -97,5 +100,18 @@ public class TestMigrationsDbContext : AbpDbContext { b.ConfigureByConvention(); }); + + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + b.HasMany(bp => bp.BlogPosts) + .WithOne(bp => bp.Blog) + .HasForeignKey(bp => bp.BlogId); + }); + + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + }); } } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/Domain/BlogPost.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/Domain/BlogPost.cs new file mode 100644 index 0000000000..9ef826176b --- /dev/null +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/Domain/BlogPost.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Volo.Abp.TestApp.Domain; + +public class Blog : FullAuditedAggregateRoot +{ + public Blog(Guid id) + : base(id) + { + } + + public string Name { get; set; } + + public List BlogPosts { get; set; } +} + +public class BlogPost : Entity +{ + public BlogPost(Guid id) + : base(id) + { + } + + public Guid BlogId { get; set; } + public Blog Blog { get; set; } + + public string Title { get; set; } +} 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 fbbac40844..242809bf7c 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,6 +1,7 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.Extensions.Logging; using Volo.Abp.DependencyInjection; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.Modeling; @@ -36,6 +37,9 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, public DbSet AppEntityWithNavigationsForeign { get; set; } + public DbSet Blogs { get; set; } + public DbSet BlogPosts { get; set; } + public TestAppDbContext(DbContextOptions options) : base(options) { @@ -123,6 +127,19 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, b.ConfigureByConvention(); }); + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + b.HasMany(bp => bp.BlogPosts) + .WithOne(bp => bp.Blog) + .HasForeignKey(bp => bp.BlogId); + }); + + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + }); + modelBuilder.TryConfigureObjectExtensions(); } }