Skip to content

feat: allow string based includes #64

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,12 @@ public interface INavigationRepository<TEntity, TKey> : IRepository<TEntity, TKe
/// <param name="includes">要额外加载的其他实体。</param>
/// <returns><paramref name="key" /> 对应的实体。</returns>
Task<TEntity?> GetAsync(TKey key, params Expression<Func<TEntity, object?>>[] includes);
}

/// <summary>
/// Get entity by key.
/// </summary>
/// <param name="key">The key of entity.</param>
/// <param name="includes">Include strings.</param>
/// <returns>The entity with key equals to <paramref name="key"/>.</returns>
Task<TEntity?> GetAsync(TKey key, params string[] includes);
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ public async Task<TEnumerable> AddRangeAsync<TEnumerable>(TEnumerable entities)
return await Context.Set<TEntity>().AggregateIncludes(includes).FirstOrDefaultAsync(e => e.Id.Equals(key));
}

/// <inheritdoc />
public async Task<TEntity?> GetAsync(TKey key, params string[] includes)
{
return await Context.Set<TEntity>().AggregateIncludes(includes).FirstOrDefaultAsync(e => e.Id.Equals(key));
}

/// <inheritdoc />
public async Task<TEntity> UpdateAsync(TEntity entity)
{
Expand Down Expand Up @@ -179,4 +185,4 @@ private void CallBeforeUpdate()
.ToList();
domainEntities.ForEach(x => x.Entity.BeforeUpdate());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.3"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.3" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Cnblogs.Architecture.Ddd.Infrastructure.Abstractions\Cnblogs.Architecture.Ddd.Infrastructure.Abstractions.csproj"/>
<ProjectReference Include="..\Cnblogs.Architecture.Ddd.Infrastructure.Abstractions\Cnblogs.Architecture.Ddd.Infrastructure.Abstractions.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,19 @@ public static IQueryable<TEntity> AggregateIncludes<TEntity>(
{
return includes.Aggregate(query, (queryable, include) => queryable.Include(include));
}
}

/// <summary>
/// Apply multiple includes to <paramref name="query"/>.
/// </summary>
/// <param name="query">The source queryable.</param>
/// <param name="includes">Include strings.</param>
/// <typeparam name="TEntity">The type of entity.</typeparam>
/// <returns></returns>
public static IQueryable<TEntity> AggregateIncludes<TEntity>(
this IQueryable<TEntity> query,
IEnumerable<string> includes)
where TEntity : class
{
return includes.Aggregate(query, (queryable, include) => queryable.Include(include));
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,94 @@
using Cnblogs.Architecture.Ddd.Domain.Abstractions;
using Cnblogs.Architecture.TestShared;
using Cnblogs.Architecture.UnitTests.Infrastructure.FakeObjects;

using FluentAssertions;

using MediatR;

using Microsoft.EntityFrameworkCore;

using Moq;

namespace Cnblogs.Architecture.UnitTests.Infrastructure.EntityFramework;

public class BaseRepositoryTests
{
[Fact]
public async Task GetEntityAsync_Include_GetEntityAsync()
{
// Arrange
var entity = new EntityGenerator<FakeBlog>(new FakeBlog())
.Setup(x => x.DateUpdated = DateTimeOffset.Now.AddDays(-1))
.HasManyForEachEntity(
x => x.Posts,
x => x.Blog,
new EntityGenerator<FakePost>(new FakePost())
.Setup(x => x.DateUpdated = DateTimeOffset.Now.AddDays(-1)))
.GenerateSingle();
var db = new FakeDbContext(
new DbContextOptionsBuilder<FakeDbContext>().UseInMemoryDatabase("inmemory").Options);
db.Add(entity);
await db.SaveChangesAsync();
var repository = new TestRepository(Mock.Of<IMediator>(), db);

// Act
var got = await repository.GetAsync(entity.Id, e => e.Posts);

// Assert
got.Should().NotBeNull();
got!.Posts.Should().BeEquivalentTo(entity.Posts);
}

[Fact]
public async Task GetEntityAsync_StringBasedInclude_NotNullAsync()
{
// Arrange
var entity = new EntityGenerator<FakeBlog>(new FakeBlog())
.Setup(x => x.DateUpdated = DateTimeOffset.Now.AddDays(-1))
.HasManyForEachEntity(
x => x.Posts,
x => x.Blog,
new EntityGenerator<FakePost>(new FakePost())
.Setup(x => x.DateUpdated = DateTimeOffset.Now.AddDays(-1)))
.GenerateSingle();
var db = new FakeDbContext(
new DbContextOptionsBuilder<FakeDbContext>().UseInMemoryDatabase("inmemory").Options);
db.Add(entity);
await db.SaveChangesAsync();
var repository = new TestRepository(Mock.Of<IMediator>(), db);

// Act
var got = await repository.GetAsync(entity.Id, nameof(entity.Posts));

// Assert
got.Should().NotBeNull();
got!.Posts.Should().BeEquivalentTo(entity.Posts);
}

[Fact]
public async Task GetEntityAsync_ThenInclude_NotNullAsync()
{
// Arrange
var entity = new EntityGenerator<FakeBlog>(new FakeBlog())
.Setup(x => x.DateUpdated = DateTimeOffset.Now.AddDays(-1))
.HasManyForEachEntity(
x => x.Posts,
x => x.Blog,
new EntityGenerator<FakePost>(new FakePost())
.HasManyForEachEntity(x => x.Tags, new EntityGenerator<FakeTag>(new FakeTag()))
.Setup(x => x.DateUpdated = DateTimeOffset.Now.AddDays(-1)))
.GenerateSingle();
var db = new FakeDbContext(
new DbContextOptionsBuilder<FakeDbContext>().UseInMemoryDatabase("inmemory").Options);
db.Add(entity);
await db.SaveChangesAsync();
var repository = new TestRepository(Mock.Of<IMediator>(), db);

// Act
var got = await repository.GetAsync(entity.Id, "Posts.Tags");

// Assert
got.Should().NotBeNull();
got!.Posts.Should().BeEquivalentTo(entity.Posts);
}

[Fact]
public async Task SaveEntitiesAsync_CallBeforeUpdateForRelatedEntityAsync()
{
Expand Down Expand Up @@ -69,10 +144,14 @@ public async Task SaveEntitiesAsync_DispatchEntityDomainEventsAsync()

// Assert
mediator.Verify(
x => x.Publish(It.Is<IDomainEvent>(d => ((FakeDomainEvent)d).FakeValue == 1), It.IsAny<CancellationToken>()),
x => x.Publish(
It.Is<IDomainEvent>(d => ((FakeDomainEvent)d).FakeValue == 1),
It.IsAny<CancellationToken>()),
Times.Once);
mediator.Verify(
x => x.Publish(It.Is<IDomainEvent>(d => ((FakeDomainEvent)d).FakeValue == 2), It.IsAny<CancellationToken>()),
x => x.Publish(
It.Is<IDomainEvent>(d => ((FakeDomainEvent)d).FakeValue == 2),
It.IsAny<CancellationToken>()),
Times.Once);
}

Expand Down Expand Up @@ -104,10 +183,14 @@ public async Task SaveEntitiesAsync_DispatchRelatedEntityDomainEventsAsync()

// Assert
mediator.Verify(
x => x.Publish(It.Is<IDomainEvent>(d => ((FakeDomainEvent)d).FakeValue == 1), It.IsAny<CancellationToken>()),
x => x.Publish(
It.Is<IDomainEvent>(d => ((FakeDomainEvent)d).FakeValue == 1),
It.IsAny<CancellationToken>()),
Times.Once);
mediator.Verify(
x => x.Publish(It.Is<IDomainEvent>(d => ((FakeDomainEvent)d).FakeValue == 2), It.IsAny<CancellationToken>()),
x => x.Publish(
It.Is<IDomainEvent>(d => ((FakeDomainEvent)d).FakeValue == 2),
It.IsAny<CancellationToken>()),
Times.Once);
}

Expand Down Expand Up @@ -145,4 +228,4 @@ public async Task SaveEntitiesAsync_DispatchEntityDomainEventsWithGeneratedIdAsy
It.IsAny<CancellationToken>()),
Times.Exactly(entity.Posts.Count));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)

modelBuilder.Entity<FakePost>().HasKey(x => x.Id);
modelBuilder.Entity<FakePost>().Property(x => x.Id).ValueGeneratedOnAdd();
modelBuilder.Entity<FakePost>().HasMany(x => x.Tags).WithOne().HasForeignKey(x => x.PostId);

modelBuilder.Entity<FakeTag>().HasKey(x => x.Id);
modelBuilder.Entity<FakeTag>().Property(x => x.Id).ValueGeneratedOnAdd();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public FakeMongoDbContext(Mock<IMongoContextOptions> mockOptionsMock, Mock<IMong
protected override void ConfigureModels(MongoModelBuilder builder)
{
builder.Entity<FakeBlog>("fakeBlog");
builder.Entity<FakePost>("fakePost");
builder.Entity<FakeTag>("fakeTag");
}

private static Mock<IMongoContextOptions> MockOptions()
Expand All @@ -54,4 +56,4 @@ private static Mock<IMongoDatabase> MockDatabase(Mock<IMongoContextOptions> mong
mongoContextOptionsMock.Setup(x => x.GetDatabase()).Returns(mock.Object);
return mock;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ public class FakePost : Entity<int>

// navigations
public FakeBlog Blog { get; set; } = null!;
}
public List<FakeTag> Tags { get; set; } = null!;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Cnblogs.Architecture.Ddd.Domain.Abstractions;

namespace Cnblogs.Architecture.UnitTests.Infrastructure.FakeObjects;

public class FakeTag : Entity<int>
{
public int BlogId { get; set; }
public int PostId { get; set; }
}