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..b878920776 --- /dev/null +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEfCoreNavigationHelper_Tests.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +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; + private readonly IRepository _postRepository; + + public AbpEfCoreNavigationHelper_Tests() + { + _blogRepository = GetRequiredService>(); + _postRepository = GetRequiredService>(); + } + + [Fact] + public async Task Performance_Test() + { + 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 List + { + new BlogPost + { + Post = new Post(Guid.NewGuid()) + { + Title = "Post" + i + } + } + } + }); + } + }); + + stopWatch.Stop(); + stopWatch.Elapsed.ShouldBeLessThan(TimeSpan.FromSeconds(106)); + + stopWatch.Restart(); + var blogs = await _blogRepository.GetListAsync(includeDetails: true); + var posts = await _postRepository.GetListAsync(includeDetails: true); + blogs.Count.ShouldBe(5 * 1000); + blogs.SelectMany(x => x.BlogPosts.Select(y => y.Post)).Count().ShouldBe(5 * 1000); + posts.Count.ShouldBe(5 * 1000); + posts.SelectMany(x => x.BlogPosts.Select(y => y.Blog)).Count().ShouldBe(5 * 1000); + + stopWatch.Stop(); + stopWatch.Elapsed.ShouldBeLessThan(TimeSpan.FromSeconds(1)); + } +} 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..691383643d 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,15 @@ 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).ThenInclude(bp => bp.Post); + }); + options.Entity(opt => + { + opt.DefaultWithDetailsFunc = q => q.Include(p => p.BlogPosts).ThenInclude(bp => bp.Blog); + }); }); 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..aac69f3935 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,10 @@ public class TestMigrationsDbContext : AbpDbContext public DbSet AppEntityWithNavigationsForeign { get; set; } + public DbSet Blogs { get; set; } + public DbSet Posts { get; set; } + public DbSet BlogPosts { get; set; } + public TestMigrationsDbContext(DbContextOptions options) : base(options) { @@ -97,5 +101,27 @@ 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(); + b.HasMany(bp => bp.BlogPosts) + .WithOne(bp => bp.Post) + .HasForeignKey(bp => bp.PostId); + }); + + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + b.HasKey(p => new { p.BlogId, p.PostId }); + }); } } 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..e08f7f0c64 --- /dev/null +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/Domain/BlogPost.cs @@ -0,0 +1,44 @@ +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 Post : FullAuditedAggregateRoot +{ + public Post(Guid id) + : base(id) + { + } + + public string Title { get; set; } + + public List BlogPosts { get; set; } +} + +public class BlogPost : Entity +{ + public Guid BlogId { get; set; } + public Blog Blog { get; set; } + + public Guid PostId { get; set; } + public Post Post { get; set; } + + public override object[] GetKeys() + { + return new object[] { BlogId, PostId }; + } +} 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..8791dde46b 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,10 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, public DbSet AppEntityWithNavigationsForeign { get; set; } + public DbSet Blogs { get; set; } + public DbSet Posts { get; set; } + public DbSet BlogPosts { get; set; } + public TestAppDbContext(DbContextOptions options) : base(options) { @@ -123,6 +128,28 @@ 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(); + b.HasMany(bp => bp.BlogPosts) + .WithOne(bp => bp.Post) + .HasForeignKey(bp => bp.PostId); + }); + + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + b.HasKey(p => new { p.BlogId, p.PostId }); + }); + modelBuilder.TryConfigureObjectExtensions(); } }