Skip to content

Commit 7cdb674

Browse files
authored
Merge pull request #64 from cnblogs/63-feat-allow-theninclude-to-work-in-getasync
feat: allow string based includes
2 parents 05168dd + 855ce25 commit 7cdb674

File tree

9 files changed

+145
-17
lines changed

9 files changed

+145
-17
lines changed

src/Cnblogs.Architecture.Ddd.Domain.Abstractions/INavigationRepository.cs

+9-1
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,12 @@ public interface INavigationRepository<TEntity, TKey> : IRepository<TEntity, TKe
1818
/// <param name="includes">要额外加载的其他实体。</param>
1919
/// <returns><paramref name="key" /> 对应的实体。</returns>
2020
Task<TEntity?> GetAsync(TKey key, params Expression<Func<TEntity, object?>>[] includes);
21-
}
21+
22+
/// <summary>
23+
/// Get entity by key.
24+
/// </summary>
25+
/// <param name="key">The key of entity.</param>
26+
/// <param name="includes">Include strings.</param>
27+
/// <returns>The entity with key equals to <paramref name="key"/>.</returns>
28+
Task<TEntity?> GetAsync(TKey key, params string[] includes);
29+
}

src/Cnblogs.Architecture.Ddd.Infrastructure.EntityFramework/BaseRepository.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ public async Task<TEnumerable> AddRangeAsync<TEnumerable>(TEnumerable entities)
7777
return await Context.Set<TEntity>().AggregateIncludes(includes).FirstOrDefaultAsync(e => e.Id.Equals(key));
7878
}
7979

80+
/// <inheritdoc />
81+
public async Task<TEntity?> GetAsync(TKey key, params string[] includes)
82+
{
83+
return await Context.Set<TEntity>().AggregateIncludes(includes).FirstOrDefaultAsync(e => e.Id.Equals(key));
84+
}
85+
8086
/// <inheritdoc />
8187
public async Task<TEntity> UpdateAsync(TEntity entity)
8288
{
@@ -179,4 +185,4 @@ private void CallBeforeUpdate()
179185
.ToList();
180186
domainEntities.ForEach(x => x.Entity.BeforeUpdate());
181187
}
182-
}
188+
}

src/Cnblogs.Architecture.Ddd.Infrastructure.EntityFramework/Cnblogs.Architecture.Ddd.Infrastructure.EntityFramework.csproj

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.3"/>
12+
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.3" />
1313
</ItemGroup>
1414

1515
<ItemGroup>
16-
<ProjectReference Include="..\Cnblogs.Architecture.Ddd.Infrastructure.Abstractions\Cnblogs.Architecture.Ddd.Infrastructure.Abstractions.csproj"/>
16+
<ProjectReference Include="..\Cnblogs.Architecture.Ddd.Infrastructure.Abstractions\Cnblogs.Architecture.Ddd.Infrastructure.Abstractions.csproj" />
1717
</ItemGroup>
1818

1919
</Project>

src/Cnblogs.Architecture.Ddd.Infrastructure.EntityFramework/QueryableExtensions.cs

+16-1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,19 @@ public static IQueryable<TEntity> AggregateIncludes<TEntity>(
2323
{
2424
return includes.Aggregate(query, (queryable, include) => queryable.Include(include));
2525
}
26-
}
26+
27+
/// <summary>
28+
/// Apply multiple includes to <paramref name="query"/>.
29+
/// </summary>
30+
/// <param name="query">The source queryable.</param>
31+
/// <param name="includes">Include strings.</param>
32+
/// <typeparam name="TEntity">The type of entity.</typeparam>
33+
/// <returns></returns>
34+
public static IQueryable<TEntity> AggregateIncludes<TEntity>(
35+
this IQueryable<TEntity> query,
36+
IEnumerable<string> includes)
37+
where TEntity : class
38+
{
39+
return includes.Aggregate(query, (queryable, include) => queryable.Include(include));
40+
}
41+
}

test/Cnblogs.Architecture.UnitTests/Infrastructure/EntityFramework/BaseRepositoryTests.cs

+92-9
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,94 @@
11
using Cnblogs.Architecture.Ddd.Domain.Abstractions;
22
using Cnblogs.Architecture.TestShared;
33
using Cnblogs.Architecture.UnitTests.Infrastructure.FakeObjects;
4-
54
using FluentAssertions;
6-
75
using MediatR;
8-
96
using Microsoft.EntityFrameworkCore;
10-
117
using Moq;
128

139
namespace Cnblogs.Architecture.UnitTests.Infrastructure.EntityFramework;
1410

1511
public class BaseRepositoryTests
1612
{
13+
[Fact]
14+
public async Task GetEntityAsync_Include_GetEntityAsync()
15+
{
16+
// Arrange
17+
var entity = new EntityGenerator<FakeBlog>(new FakeBlog())
18+
.Setup(x => x.DateUpdated = DateTimeOffset.Now.AddDays(-1))
19+
.HasManyForEachEntity(
20+
x => x.Posts,
21+
x => x.Blog,
22+
new EntityGenerator<FakePost>(new FakePost())
23+
.Setup(x => x.DateUpdated = DateTimeOffset.Now.AddDays(-1)))
24+
.GenerateSingle();
25+
var db = new FakeDbContext(
26+
new DbContextOptionsBuilder<FakeDbContext>().UseInMemoryDatabase("inmemory").Options);
27+
db.Add(entity);
28+
await db.SaveChangesAsync();
29+
var repository = new TestRepository(Mock.Of<IMediator>(), db);
30+
31+
// Act
32+
var got = await repository.GetAsync(entity.Id, e => e.Posts);
33+
34+
// Assert
35+
got.Should().NotBeNull();
36+
got!.Posts.Should().BeEquivalentTo(entity.Posts);
37+
}
38+
39+
[Fact]
40+
public async Task GetEntityAsync_StringBasedInclude_NotNullAsync()
41+
{
42+
// Arrange
43+
var entity = new EntityGenerator<FakeBlog>(new FakeBlog())
44+
.Setup(x => x.DateUpdated = DateTimeOffset.Now.AddDays(-1))
45+
.HasManyForEachEntity(
46+
x => x.Posts,
47+
x => x.Blog,
48+
new EntityGenerator<FakePost>(new FakePost())
49+
.Setup(x => x.DateUpdated = DateTimeOffset.Now.AddDays(-1)))
50+
.GenerateSingle();
51+
var db = new FakeDbContext(
52+
new DbContextOptionsBuilder<FakeDbContext>().UseInMemoryDatabase("inmemory").Options);
53+
db.Add(entity);
54+
await db.SaveChangesAsync();
55+
var repository = new TestRepository(Mock.Of<IMediator>(), db);
56+
57+
// Act
58+
var got = await repository.GetAsync(entity.Id, nameof(entity.Posts));
59+
60+
// Assert
61+
got.Should().NotBeNull();
62+
got!.Posts.Should().BeEquivalentTo(entity.Posts);
63+
}
64+
65+
[Fact]
66+
public async Task GetEntityAsync_ThenInclude_NotNullAsync()
67+
{
68+
// Arrange
69+
var entity = new EntityGenerator<FakeBlog>(new FakeBlog())
70+
.Setup(x => x.DateUpdated = DateTimeOffset.Now.AddDays(-1))
71+
.HasManyForEachEntity(
72+
x => x.Posts,
73+
x => x.Blog,
74+
new EntityGenerator<FakePost>(new FakePost())
75+
.HasManyForEachEntity(x => x.Tags, new EntityGenerator<FakeTag>(new FakeTag()))
76+
.Setup(x => x.DateUpdated = DateTimeOffset.Now.AddDays(-1)))
77+
.GenerateSingle();
78+
var db = new FakeDbContext(
79+
new DbContextOptionsBuilder<FakeDbContext>().UseInMemoryDatabase("inmemory").Options);
80+
db.Add(entity);
81+
await db.SaveChangesAsync();
82+
var repository = new TestRepository(Mock.Of<IMediator>(), db);
83+
84+
// Act
85+
var got = await repository.GetAsync(entity.Id, "Posts.Tags");
86+
87+
// Assert
88+
got.Should().NotBeNull();
89+
got!.Posts.Should().BeEquivalentTo(entity.Posts);
90+
}
91+
1792
[Fact]
1893
public async Task SaveEntitiesAsync_CallBeforeUpdateForRelatedEntityAsync()
1994
{
@@ -69,10 +144,14 @@ public async Task SaveEntitiesAsync_DispatchEntityDomainEventsAsync()
69144

70145
// Assert
71146
mediator.Verify(
72-
x => x.Publish(It.Is<IDomainEvent>(d => ((FakeDomainEvent)d).FakeValue == 1), It.IsAny<CancellationToken>()),
147+
x => x.Publish(
148+
It.Is<IDomainEvent>(d => ((FakeDomainEvent)d).FakeValue == 1),
149+
It.IsAny<CancellationToken>()),
73150
Times.Once);
74151
mediator.Verify(
75-
x => x.Publish(It.Is<IDomainEvent>(d => ((FakeDomainEvent)d).FakeValue == 2), It.IsAny<CancellationToken>()),
152+
x => x.Publish(
153+
It.Is<IDomainEvent>(d => ((FakeDomainEvent)d).FakeValue == 2),
154+
It.IsAny<CancellationToken>()),
76155
Times.Once);
77156
}
78157

@@ -104,10 +183,14 @@ public async Task SaveEntitiesAsync_DispatchRelatedEntityDomainEventsAsync()
104183

105184
// Assert
106185
mediator.Verify(
107-
x => x.Publish(It.Is<IDomainEvent>(d => ((FakeDomainEvent)d).FakeValue == 1), It.IsAny<CancellationToken>()),
186+
x => x.Publish(
187+
It.Is<IDomainEvent>(d => ((FakeDomainEvent)d).FakeValue == 1),
188+
It.IsAny<CancellationToken>()),
108189
Times.Once);
109190
mediator.Verify(
110-
x => x.Publish(It.Is<IDomainEvent>(d => ((FakeDomainEvent)d).FakeValue == 2), It.IsAny<CancellationToken>()),
191+
x => x.Publish(
192+
It.Is<IDomainEvent>(d => ((FakeDomainEvent)d).FakeValue == 2),
193+
It.IsAny<CancellationToken>()),
111194
Times.Once);
112195
}
113196

@@ -145,4 +228,4 @@ public async Task SaveEntitiesAsync_DispatchEntityDomainEventsWithGeneratedIdAsy
145228
It.IsAny<CancellationToken>()),
146229
Times.Exactly(entity.Posts.Count));
147230
}
148-
}
231+
}

test/Cnblogs.Architecture.UnitTests/Infrastructure/FakeObjects/FakeDbContext.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
1818

1919
modelBuilder.Entity<FakePost>().HasKey(x => x.Id);
2020
modelBuilder.Entity<FakePost>().Property(x => x.Id).ValueGeneratedOnAdd();
21+
modelBuilder.Entity<FakePost>().HasMany(x => x.Tags).WithOne().HasForeignKey(x => x.PostId);
22+
23+
modelBuilder.Entity<FakeTag>().HasKey(x => x.Id);
24+
modelBuilder.Entity<FakeTag>().Property(x => x.Id).ValueGeneratedOnAdd();
2125
}
22-
}
26+
}

test/Cnblogs.Architecture.UnitTests/Infrastructure/FakeObjects/FakeMongoDbContext.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public FakeMongoDbContext(Mock<IMongoContextOptions> mockOptionsMock, Mock<IMong
4141
protected override void ConfigureModels(MongoModelBuilder builder)
4242
{
4343
builder.Entity<FakeBlog>("fakeBlog");
44+
builder.Entity<FakePost>("fakePost");
45+
builder.Entity<FakeTag>("fakeTag");
4446
}
4547

4648
private static Mock<IMongoContextOptions> MockOptions()
@@ -54,4 +56,4 @@ private static Mock<IMongoDatabase> MockDatabase(Mock<IMongoContextOptions> mong
5456
mongoContextOptionsMock.Setup(x => x.GetDatabase()).Returns(mock.Object);
5557
return mock;
5658
}
57-
}
59+
}

test/Cnblogs.Architecture.UnitTests/Infrastructure/FakeObjects/FakePost.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ public class FakePost : Entity<int>
99

1010
// navigations
1111
public FakeBlog Blog { get; set; } = null!;
12-
}
12+
public List<FakeTag> Tags { get; set; } = null!;
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Cnblogs.Architecture.Ddd.Domain.Abstractions;
2+
3+
namespace Cnblogs.Architecture.UnitTests.Infrastructure.FakeObjects;
4+
5+
public class FakeTag : Entity<int>
6+
{
7+
public int BlogId { get; set; }
8+
public int PostId { get; set; }
9+
}

0 commit comments

Comments
 (0)