Skip to content

Commit

Permalink
Updating packages, using new .NET 8 syntax sugars and other refactori…
Browse files Browse the repository at this point in the history
…ng, upgrading to newest SDK
  • Loading branch information
AinoraZ committed Jun 4, 2024
1 parent 5a179d3 commit 2e93fd5
Show file tree
Hide file tree
Showing 20 changed files with 161 additions and 190 deletions.
1 change: 1 addition & 0 deletions Ainoraz.EFCore.IncludeBuilder.sln
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Build.props = Directory.Build.props
tests\Directory.Build.props = tests\Directory.Build.props
Directory.Packages.props = Directory.Packages.props
global.json = global.json
EndProjectSection
EndProject
Global
Expand Down
5 changes: 5 additions & 0 deletions Ainoraz.EFCore.IncludeBuilder.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ainoraz/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=enumerables/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=includable/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary>
14 changes: 7 additions & 7 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="AutoFixture.AutoNSubstitute" Version="4.18.0" />
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.0" />
<PackageVersion Include="BenchmarkDotNet" Version="0.13.10" />
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
<PackageVersion Include="Castle.Core" Version="5.1.1" />
<PackageVersion Include="coverlet.collector" Version="6.0.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="6.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="MockQueryable.NSubstitute" Version="7.0.0" />
<PackageVersion Include="xunit" Version="2.6.1" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.3" />
<PackageVersion Include="MockQueryable.NSubstitute" Version="7.0.1" />
<PackageVersion Include="xunit" Version="2.8.1" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.1" />
</ItemGroup>
</Project>
15 changes: 7 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,17 @@ EFCore.IncludeBuilder converts the includes you give it back to ```Include(...).

```UseIncludeBuilder``` adds very little overhead both time and memory wise:

| Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio |
|------------------ |---------:|---------:|---------:|------:|--------:|----------:|------------:|
| Include | 30.89 μs | 0.572 μs | 0.535 μs | 1.00 | 0.00 | 10.53 KB | 1.00 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio |
|-------------------|---------:|---------:|---------:|------:|--------:|----------:|------------:|
| Include | 30.89 μs | 0.572 μs | 0.535 μs | 1.00 | 0.00 | 10.53 KB | 1.00 |
| UseIncludeBuilder | 33.50 μs | 0.163 μs | 0.136 μs | 1.08 | 0.02 | 11.91 KB | 1.13 |


For larger queries that duplicate the same filters, it can even be the faster option:

| Method | Mean | Error | StdDev | Ratio | Allocated | Alloc Ratio |
|------------------------- |---------:|---------:|---------:|------:|----------:|------------:|
| Include | 69.77 μs | 0.140 μs | 0.109 μs | 1.00 | 23.8 KB | 1.00 |
| Method | Mean | Error | StdDev | Ratio | Allocated | Alloc Ratio |
|--------------------------|---------:|---------:|---------:|------:|----------:|------------:|
| Include | 69.77 μs | 0.140 μs | 0.109 μs | 1.00 | 23.8 KB | 1.00 |
| Include_DuplicatedFilter | 87.86 μs | 0.177 μs | 0.148 μs | 1.26 | 29.93 KB | 1.26 |
| UseIncludeBuilder | 78.62 μs | 0.213 μs | 0.167 μs | 1.13 | 25.34 KB | 1.07 |
| UseIncludeBuilder | 78.62 μs | 0.213 μs | 0.167 μs | 1.13 | 25.34 KB | 1.07 |

You can find the most up to date benchmarks in the build artifacts for each build.
7 changes: 7 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"sdk": {
"version": "8.0.206",
"rollForward": "disable",
"allowPrerelease": false
}
}
6 changes: 3 additions & 3 deletions src/Ainoraz.EFCore.IncludeBuilder/Appliers/IncludeApplier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ namespace Ainoraz.EFCore.IncludeBuilder.Appliers;

internal class IncludeApplier<TBase, TProperty> : IIncludeApplier<TBase> where TBase : class
{
private readonly Expression<Func<TBase, TProperty>> navigationPropertyPath;
private readonly Expression<Func<TBase, TProperty>> _navigationPropertyPath;

internal IncludeApplier(Expression<Func<TBase, TProperty>> navigationPropertyPath)
{
this.navigationPropertyPath = navigationPropertyPath;
_navigationPropertyPath = navigationPropertyPath;
}

public IQueryable<TBase> Apply(IQueryable<TBase> queryable) =>
queryable.Include(navigationPropertyPath);
queryable.Include(_navigationPropertyPath);
}
23 changes: 12 additions & 11 deletions src/Ainoraz.EFCore.IncludeBuilder/Appliers/ThenIncludeApplier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,25 @@

namespace Ainoraz.EFCore.IncludeBuilder.Appliers;

internal class ThenIncludeApplier<TBase, TEntity, TProperty> :
IIncludeApplier<TBase> where TBase : class
internal class ThenIncludeApplier<TBase, TEntity, TProperty> : IIncludeApplier<TBase> where TBase : class
{
private readonly Expression<Func<TEntity, TProperty>> navigationPropertyPath;
private readonly Expression<Func<TEntity, TProperty>> _navigationPropertyPath;

internal ThenIncludeApplier(Expression<Func<TEntity, TProperty>> navigationPropertyPath)
{
this.navigationPropertyPath = navigationPropertyPath;
_navigationPropertyPath = navigationPropertyPath;
}

public IQueryable<TBase> Apply(IQueryable<TBase> queryable)
{
if (queryable is IIncludableQueryable<TBase, IEnumerable<TEntity>> enumerableIncludableQueryable)
return enumerableIncludableQueryable.ThenInclude(navigationPropertyPath);

else if (queryable is IIncludableQueryable<TBase, TEntity> includableQueryable)
return includableQueryable.ThenInclude(navigationPropertyPath);

throw new IncludeConversionFailedException($"Found unsupported IQueryable type that cannot have .ThenInclude applied. Found type: {queryable.GetType()}.");
return queryable switch
{
IIncludableQueryable<TBase, IEnumerable<TEntity>> enumerableQueryable =>
enumerableQueryable.ThenInclude(_navigationPropertyPath),
IIncludableQueryable<TBase, TEntity> singleQueryable =>
singleQueryable.ThenInclude(_navigationPropertyPath),
_ => throw new IncludeConversionFailedException(
$"Found unsupported IQueryable type that cannot have .ThenInclude applied. Found type: {queryable.GetType()}.")
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Ainoraz.EFCore.IncludeBuilder.Builders;

internal abstract class BaseIncludeBuilder<TBase> where TBase : class
{
internal List<BaseIncludeBuilder<TBase>> ChildBuilders { get; } = new();
internal List<BaseIncludeBuilder<TBase>> ChildBuilders { get; } = [];
internal BaseIncludeBuilder<TBase>? ParentBuilder { get; }

internal BaseIncludeBuilder(BaseIncludeBuilder<TBase>? parentBuilder)
Expand All @@ -18,7 +18,7 @@ internal BaseIncludeBuilder(BaseIncludeBuilder<TBase>? parentBuilder)
internal IEnumerable<BaseIncludeBuilder<TBase>> GetLeafNodes()
{
if (!ChildBuilders.Any())
return new[] { this };
return [this];

return ChildBuilders.SelectMany(i => i.GetLeafNodes());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@

namespace Ainoraz.EFCore.IncludeBuilder.Builders;

internal class NestedIncludeBuilder<TBase, TCurrent> :
BaseIncludeBuilder<TBase>,
INestedIncludeBuilder<TBase, TCurrent>
internal class NestedIncludeBuilder<TBase, TCurrent> :
BaseIncludeBuilder<TBase>, INestedIncludeBuilder<TBase, TCurrent>
where TBase : class
{
/// <summary>
/// Include to apply in order to get from previous level to current one.
/// </summary>
private readonly IIncludeApplier<TBase> currentLevelIncludeApplier;
private readonly IIncludeApplier<TBase> _currentLevelIncludeApplier;

internal NestedIncludeBuilder(BaseIncludeBuilder<TBase> parentBuilder, IIncludeApplier<TBase> applier) : base(parentBuilder)
internal NestedIncludeBuilder(BaseIncludeBuilder<TBase> parentBuilder, IIncludeApplier<TBase> applier)
: base(parentBuilder)
{
currentLevelIncludeApplier = applier;
_currentLevelIncludeApplier = applier;
}

public INestedIncludeBuilder<TBase, TCurrent> Include<TNext>(
Expand Down Expand Up @@ -51,5 +51,5 @@ private INestedIncludeBuilder<TBase, TCurrent> IncludeWithApplier<TNext>(
}

internal override IQueryable<TBase> Apply(IQueryable<TBase> query) =>
currentLevelIncludeApplier.Apply(query);
_currentLevelIncludeApplier.Apply(query);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@
namespace Ainoraz.EFCore.IncludeBuilder.Builders;

internal class RootIncludeBuilder<TBase> :
BaseIncludeBuilder<TBase>,
IRootIncludeBuilder<TBase>
BaseIncludeBuilder<TBase>, IRootIncludeBuilder<TBase>
where TBase : class
{
private readonly IQueryable<TBase> source;
private readonly IQueryable<TBase> _source;

internal RootIncludeBuilder(IQueryable<TBase> source) : base(null)
{
this.source = source;
_source = source;
}

public IRootIncludeBuilder<TBase> Include<TNext>(
Expand All @@ -37,7 +36,7 @@ public IRootIncludeBuilder<TBase> Include<TNext>(

public IQueryable<TBase> Build()
{
IQueryable<TBase> builtSource = source;
IQueryable<TBase> builtSource = _source;
foreach (BaseIncludeBuilder<TBase> leafBuilder in GetLeafNodes())
{
IEnumerable<BaseIncludeBuilder<TBase>> chainToLeaf = GetAncestorChain(leafBuilder).Reverse();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public static class EntityFrameworkCoreExtensions
/// <typeparam name="TEntity">Entity type of source query.</typeparam>
/// <param name="source">Source query on which includes will be added.</param>
/// <returns>IncludeBuilder based on passed source query.</returns>
public static IRootIncludeBuilder<TEntity> UseIncludeBuilder<TEntity>(this IQueryable<TEntity> source) where TEntity : class
public static IRootIncludeBuilder<TEntity> UseIncludeBuilder<TEntity>(this IQueryable<TEntity> source)
where TEntity : class
{
return new RootIncludeBuilder<TEntity>(source);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,15 @@ namespace Ainoraz.EFCore.IncludeBuilder.Benchmarks;
[MemoryDiagnoser]
public class IncludeVsUseIncludeBuilder
{
private readonly TestDbContext testDbContext;

public IncludeVsUseIncludeBuilder()
{
testDbContext = new TestDbContext();
}
private readonly TestDbContext _testDbContext = new();

[GlobalCleanup]
public void GlobalCleanup()
{
testDbContext.Dispose();
}
public void GlobalCleanup() => _testDbContext.Dispose();

[Benchmark(Baseline = true)]
public string Include()
{
return testDbContext.Users
return _testDbContext.Users
.Include(u => u.OwnedBlog)
.ThenInclude(b => b.Posts.Where(p => p.PostDate > DateTime.UtcNow.AddDays(-7)))
.ThenInclude(p => p.Readers)
Expand All @@ -37,7 +29,7 @@ public string Include()
[Benchmark]
public string UseIncludeBuilder()
{
return testDbContext.Users
return _testDbContext.Users
.UseIncludeBuilder()
.Include(u => u.OwnedBlog, builder => builder
.Include(b => b.Posts.Where(p => p.PostDate > DateTime.UtcNow.AddDays(-7)), builder => builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,15 @@ namespace Ainoraz.EFCore.IncludeBuilder.Benchmarks;
[MemoryDiagnoser]
public class MultiIncludeVsUseIncludeBuilder
{
private readonly TestDbContext testDbContext;

public MultiIncludeVsUseIncludeBuilder()
{
testDbContext = new TestDbContext();
}
private readonly TestDbContext _testDbContext = new();

[GlobalCleanup]
public void GlobalCleanup()
{
testDbContext.Dispose();
}
public void GlobalCleanup() => _testDbContext.Dispose();

[Benchmark(Baseline = true)]
public string Include()
{
return testDbContext.Users
return _testDbContext.Users
.Include(u => u.OwnedBlog)
.ThenInclude(b => b.Posts.Where(p => p.PostDate > DateTime.UtcNow.AddDays(-7)))
.ThenInclude(p => p.Readers)
Expand All @@ -47,7 +39,7 @@ public string Include()
[Benchmark]
public string Include_DuplicatedFilter()
{
return testDbContext.Users
return _testDbContext.Users
.Include(u => u.OwnedBlog)
.ThenInclude(b => b.Posts.Where(p => p.PostDate > DateTime.UtcNow.AddDays(-7)))
.ThenInclude(p => p.Readers)
Expand All @@ -68,7 +60,7 @@ public string Include_DuplicatedFilter()
[Benchmark]
public string UseIncludeBuilder()
{
return testDbContext.Users
return _testDbContext.Users
.UseIncludeBuilder()
.Include(u => u.OwnedBlog, builder => builder
.Include(b => b.Posts.Where(p => p.PostDate > DateTime.UtcNow.AddDays(-7)), builder => builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@

namespace Ainoraz.EFCore.IncludeBuilder.Tests.Common.Customizations;

public class IncludeAutoDataAttribute : AutoDataAttribute
public class IncludeAutoDataAttribute() : AutoDataAttribute(GenerateFixture)
{
public IncludeAutoDataAttribute() : base(GenerateFixture)
{
}

public static IFixture GenerateFixture()
private static IFixture GenerateFixture()
{
var fixture = new Fixture();
var customization = new IncludeCustomization();
Expand Down
19 changes: 11 additions & 8 deletions tests/Ainoraz.EFCore.IncludeBuilder.Tests.Common/TestDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@

namespace Ainoraz.EFCore.IncludeBuilder.Tests.Common;

public class TestDbContext : DbContext
public class TestDbContext() : DbContext(BuildOptions())
{
public TestDbContext() : base(
new DbContextOptionsBuilder<TestDbContext>()
.UseSqlite(CreateInMemoryDatabase())
.Options)
{ }

public DbSet<User> Users => Set<User>();
public DbSet<Blog> Blogs => Set<Blog>();
public DbSet<Post> Posts => Set<Post>();

private static DbConnection CreateInMemoryDatabase()
private static DbContextOptions<TestDbContext> BuildOptions()
{
DbContextOptions<TestDbContext> options = new DbContextOptionsBuilder<TestDbContext>()
.UseSqlite(CreateInMemoryDatabase())
.Options;

return options;
}

private static SqliteConnection CreateInMemoryDatabase()
{
var connection = new SqliteConnection("Filename=:memory:");
connection.Open();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Ainoraz.EFCore.IncludeBuilder.Builders.Interfaces;
using Ainoraz.EFCore.IncludeBuilder.Tests.Common.Models;

namespace Ainoraz.EFCore.IncludeBuilder.Extensions;
namespace Ainoraz.EFCore.IncludeBuilder.Tests.Extensions;

public static class TestIncludeBuilderExtensions
{
Expand Down
Loading

0 comments on commit 2e93fd5

Please sign in to comment.