Skip to content

Commit

Permalink
Merge pull request #27 from yv989c/feature/2-fast-2-furious
Browse files Browse the repository at this point in the history
2 Fast 2 Furious (JSON Support)
  • Loading branch information
yv989c committed Mar 27, 2023
2 parents e5bf791 + 3903fbe commit 4fb79e8
Show file tree
Hide file tree
Showing 26 changed files with 1,938 additions and 743 deletions.
8 changes: 8 additions & 0 deletions BlazarTech.QueryableValues.sln
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QueryableValues.SqlServer.E
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QueryableValues.SqlServer.Tests.EFCore7", "tests\QueryableValues.SqlServer.Tests.EFCore7\QueryableValues.SqlServer.Tests.EFCore7.csproj", "{D5293D2F-4649-4A2B-A50D-E7DBD4AA4A5C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QueryableValues.SqlServer.Benchmarks", "benchmarks\QueryableValues.SqlServer.Benchmarks\QueryableValues.SqlServer.Benchmarks.csproj", "{99FE31A0-BC7E-412C-82E2-DA19E8B68613}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -110,6 +112,12 @@ Global
{D5293D2F-4649-4A2B-A50D-E7DBD4AA4A5C}.Test_All|Any CPU.Build.0 = Test_All|Any CPU
{D5293D2F-4649-4A2B-A50D-E7DBD4AA4A5C}.Test|Any CPU.ActiveCfg = Test|Any CPU
{D5293D2F-4649-4A2B-A50D-E7DBD4AA4A5C}.Test|Any CPU.Build.0 = Test|Any CPU
{99FE31A0-BC7E-412C-82E2-DA19E8B68613}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{99FE31A0-BC7E-412C-82E2-DA19E8B68613}.Release|Any CPU.ActiveCfg = Release|Any CPU
{99FE31A0-BC7E-412C-82E2-DA19E8B68613}.Release|Any CPU.Build.0 = Release|Any CPU
{99FE31A0-BC7E-412C-82E2-DA19E8B68613}.Test_All|Any CPU.ActiveCfg = Release|Any CPU
{99FE31A0-BC7E-412C-82E2-DA19E8B68613}.Test_All|Any CPU.Build.0 = Release|Any CPU
{99FE31A0-BC7E-412C-82E2-DA19E8B68613}.Test|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
188 changes: 108 additions & 80 deletions README.md

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions Version.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<VersionEFCore3>3.6.0</VersionEFCore3>
<VersionEFCore5>5.6.0</VersionEFCore5>
<VersionEFCore6>6.6.0</VersionEFCore6>
<VersionEFCore7>7.1.0</VersionEFCore7>
<VersionEFCore3>3.7.0</VersionEFCore3>
<VersionEFCore5>5.7.0</VersionEFCore5>
<VersionEFCore6>6.7.0</VersionEFCore6>
<VersionEFCore7>7.2.0</VersionEFCore7>
</PropertyGroup>
</Project>
182 changes: 144 additions & 38 deletions benchmarks/QueryableValues.SqlServer.Benchmarks/ContainsBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,41 @@
using BlazarTech.QueryableValues;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using System.Text;

namespace QueryableValues.SqlServer.Benchmarks;

[SimpleJob(RunStrategy.Monitoring, warmupCount: 1, targetCount: 25, invocationCount: 200)]
[SimpleJob(RunStrategy.Monitoring, warmupCount: 1, iterationCount: 25, invocationCount: 200)]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[GcServer(true), MemoryDiagnoser]
public class ContainsBenchmarks
{
#pragma warning disable CS8618
private IQueryable<Int32Entity> _int32Query;
private IQueryable<GuidEntity> _guidQuery;
private IQueryable<Int32Entity> _queryableValuesInt32Query;
private IQueryable<GuidEntity> _queryableValuesGuidQuery;
#pragma warning restore CS8618

[Params(2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096)]
private IQueryable<Int32Entity> _int32Query = default!;
private IQueryable<GuidEntity> _guidQuery = default!;
private IQueryable<StringEntity> _stringQuery = default!;

private IQueryable<Int32Entity> _queryableValuesJsonInt32Query = default!;
private IQueryable<GuidEntity> _queryableValuesJsonGuidQuery = default!;
private IQueryable<StringEntity> _queryableValuesJsonStringQuery = default!;

private IQueryable<Int32Entity> _queryableValuesXmlInt32Query = default!;
private IQueryable<GuidEntity> _queryableValuesXmlGuidQuery = default!;
private IQueryable<StringEntity> _queryableValuesXmlStringQuery = default!;

public enum DataType
{
Int32,
Guid,
String
}

[Params(DataType.Int32, DataType.Guid, DataType.String)]
//[Params(DataType.String)]
public DataType Type { get; set; }

//[Params(512)]
//[Params(2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096)]
[Params(2, 8, 32, 128, 512, 2048)]
public int NumberOfValues { get; set; }

private IEnumerable<int> GetIntValues()
Expand All @@ -38,87 +57,174 @@ private IEnumerable<Guid> GetGuidValues()
}
}

private IEnumerable<string> GetStringValues()
{
var sb = new StringBuilder();

for (int i = 0; i < NumberOfValues; i++)
{
sb.Clear();
var length = Random.Shared.Next(0, 50);
for (int x = 0; x < length; x++)
{
sb.Append((char)Random.Shared.Next(32, 126));
}
yield return sb.ToString();
}
}

[GlobalSetup]
public void GlobalSetup()
{
Console.WriteLine("Initializing...");

var dbContext = new MyDbContext();
var dbContextXml = new MyDbContext(SqlServerSerialization.UseXml);
var dbContextJson = new MyDbContext(SqlServerSerialization.UseJson);

#region Init db
{
var wasCreated = dbContext.Database.EnsureCreated();
var wasCreated = dbContextXml.Database.EnsureCreated();

if (wasCreated)
{
for (int i = 0; i < 1000; i++)
const int itemsCount = 1000;

for (int i = 0; i < itemsCount; i++)
{
dbContextXml.Add(new Int32Entity());
dbContextXml.Add(new GuidEntity());
}

var i2 = 0;

foreach (var value in GetStringValues())
{
dbContext.Add(new Int32Entity());
dbContext.Add(new GuidEntity());
i2++;

dbContextXml.Add(new StringEntity { Id = $"{value}{i2}" });

if (i2 == itemsCount)
{
break;
}
}

dbContext.SaveChanges();
dbContextXml.SaveChanges();
}

var versionParam = new SqlParameter("@Version", System.Data.SqlDbType.NVarChar, -1)
{
Direction = System.Data.ParameterDirection.Output
};

dbContext.Database.ExecuteSqlRaw("SET @Version = @@VERSION;", versionParam);
dbContextXml.Database.ExecuteSqlRaw("SET @Version = @@VERSION;", versionParam);

Console.WriteLine(versionParam.Value);

dbContext.Database.ExecuteSqlRaw("DBCC FREEPROCCACHE; DBCC DROPCLEANBUFFERS;");
dbContextXml.Database.ExecuteSqlRaw("DBCC FREEPROCCACHE; DBCC DROPCLEANBUFFERS;");
}
#endregion

#region Int32 Queries
{
var intValues = GetIntValues();

_int32Query = dbContext.Int32Entities
_int32Query = dbContextXml.Int32Entities
.Where(i => intValues.Contains(i.Id));

_queryableValuesInt32Query = dbContext.Int32Entities
.Where(i => dbContext.AsQueryableValues(intValues).Contains(i.Id));
_queryableValuesXmlInt32Query = dbContextXml.Int32Entities
.Where(i => dbContextXml.AsQueryableValues(intValues).Contains(i.Id));

_queryableValuesJsonInt32Query = dbContextJson.Int32Entities
.Where(i => dbContextJson.AsQueryableValues(intValues).Contains(i.Id));
}
#endregion

#region Guid Queries
{
var guidValues = GetGuidValues();

_guidQuery = dbContext.GuidEntities
_guidQuery = dbContextXml.GuidEntities
.Where(i => guidValues.Contains(i.Id));

_queryableValuesGuidQuery = dbContext.GuidEntities
.Where(i => dbContext.AsQueryableValues(guidValues).Contains(i.Id));
_queryableValuesXmlGuidQuery = dbContextXml.GuidEntities
.Where(i => dbContextXml.AsQueryableValues(guidValues).Contains(i.Id));

_queryableValuesJsonGuidQuery = dbContextJson.GuidEntities
.Where(i => dbContextJson.AsQueryableValues(guidValues).Contains(i.Id));
}
#endregion
}

[Benchmark(Baseline = true), BenchmarkCategory("Int32")]
public void Without_Int32()
{
_int32Query.Any();
#region String Queries
{
var stringValues = GetStringValues();

_stringQuery = dbContextXml.StringEntities
.Where(i => stringValues.Contains(i.Id));

_queryableValuesXmlStringQuery = dbContextXml.StringEntities
.Where(i => dbContextXml.AsQueryableValues(stringValues, true).Contains(i.Id));

_queryableValuesJsonStringQuery = dbContextJson.StringEntities
.Where(i => dbContextJson.AsQueryableValues(stringValues, true).Contains(i.Id));
}
#endregion
}

[Benchmark, BenchmarkCategory("Int32")]
public void With_Int32()
[Benchmark(Baseline = true)]
public void Without()
{
_queryableValuesInt32Query.Any();
switch (Type)
{
case DataType.Int32:
_int32Query.Any();
break;
case DataType.Guid:
_guidQuery.Any();
break;
case DataType.String:
_stringQuery.Any();
break;
default:
throw new NotImplementedException();
}
}

[Benchmark(Baseline = true), BenchmarkCategory("Guid")]
public void Without_Guid()
[Benchmark]
public void WithXml()
{
_guidQuery.Any();
switch (Type)
{
case DataType.Int32:
_queryableValuesXmlInt32Query.Any();
break;
case DataType.Guid:
_queryableValuesXmlGuidQuery.Any();
break;
case DataType.String:
_queryableValuesXmlStringQuery.Any();
break;
default:
throw new NotImplementedException();
}
}

[Benchmark, BenchmarkCategory("Guid")]
public void With_Guid()
[Benchmark]
public void WithJson()
{
_queryableValuesGuidQuery.Any();
switch (Type)
{
case DataType.Int32:
_queryableValuesJsonInt32Query.Any();
break;
case DataType.Guid:
_queryableValuesJsonGuidQuery.Any();
break;
case DataType.String:
_queryableValuesJsonStringQuery.Any();
break;
default:
throw new NotImplementedException();
}
}
}
}
25 changes: 19 additions & 6 deletions benchmarks/QueryableValues.SqlServer.Benchmarks/MyDbContext.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
using BlazarTech.QueryableValues;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;

namespace QueryableValues.SqlServer.Benchmarks
{
public class MyDbContext : DbContext
{
#pragma warning disable CS8618
public DbSet<Int32Entity> Int32Entities { get; set; }
public DbSet<GuidEntity> GuidEntities { get; set; }
#pragma warning restore CS8618
private readonly SqlServerSerialization _serializationOption;

public DbSet<Int32Entity> Int32Entities { get; set; } = default!;
public DbSet<GuidEntity> GuidEntities { get; set; } = default!;
public DbSet<StringEntity> StringEntities { get; set; } = default!;

public MyDbContext(SqlServerSerialization serializationOption)
{
_serializationOption = serializationOption;
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
@"Server=.\SQLEXPRESS;Integrated Security=true;Database=QueryableValuesBenchmarks",
builder => builder.UseQueryableValues()
@"Server=.\SQLEXPRESS;Integrated Security=true;Database=QueryableValuesBenchmarks;Encrypt=False;",
builder => builder.UseQueryableValues(options => options.Serialization(_serializationOption))
);
}

Expand All @@ -33,4 +40,10 @@ public class GuidEntity
{
public Guid Id { get; set; }
}

public class StringEntity
{
[MaxLength(100)]
public string Id { get; set; } = default!;
}
}
10 changes: 8 additions & 2 deletions benchmarks/QueryableValues.SqlServer.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;

namespace QueryableValues.SqlServer.Benchmarks;

class Program
{
static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<ContainsBenchmarks>();
var config = new ManualConfig();

config.Add(DefaultConfig.Instance);
config.WithOptions(ConfigOptions.DisableOptimizationsValidator);

BenchmarkRunner.Run<ContainsBenchmarks>(config);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<PackageReference Include="BlazarTech.QueryableValues.SqlServer" Version="6.3.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.1" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.4" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\QueryableValues.SqlServer.EFCore7\QueryableValues.SqlServer.EFCore7.csproj" />
</ItemGroup>

</Project>
5 changes: 5 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
[![GitHub Stars](https://badgen.net/github/stars/yv989c/BlazarTech.QueryableValues?icon=github)][Repository]
[![Nuget Downloads](https://badgen.net/nuget/dt/BlazarTech.QueryableValues.SqlServer?icon=nuget)][NuGet Package]

> 🤔💭 TLDR; By using QueryableValues, you can incorporate in-memory collections into your EF queries with outstanding performance and flexibility.
This library allows you to efficiently compose an [IEnumerable&lt;T&gt;] in your [Entity Framework Core] queries when using the [SQL Server Database Provider]. This is accomplished by using the `AsQueryableValues` extension method available on the [DbContext] class. Everything is evaluated on the server with a single round trip, in a way that preserves the query's [execution plan], even when the values behind the [IEnumerable&lt;T&gt;] are changed on subsequent executions.

The supported types for `T` are:
Expand All @@ -15,6 +17,8 @@ The supported types for `T` are:

For a detailed explanation of the problem solved by QueryableValues, please continue reading [here][readme-background].

> ✅ QueryableValues boasts over 120 integration tests that are executed on every supported version of EF. These tests ensure reliability and compatibility, giving you added confidence.
> 💡 Still on Entity Framework 6 (non-core)? Then [QueryableValues `EF6 Edition`](https://github.com/yv989c/BlazarTech.QueryableValues.EF6) is what you need.
## When Should You Use It?
Expand Down Expand Up @@ -75,6 +79,7 @@ public class Startup
}
}
```
> 💡 Pro-tip: `UseQueryableValues` offers an optional `options` delegate for additional configurations.
## How Do You Use It?
The `AsQueryableValues` extension method is provided by the `BlazarTech.QueryableValues` namespace; therefore, you must add the following `using` directive to your source code file for it to appear as a method of your [DbContext] instance:
Expand Down
Binary file added docs/images/benchmarks/v7.2.0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 4fb79e8

Please sign in to comment.