Skip to content

Commit 4fb79e8

Browse files
authored
Merge pull request #27 from yv989c/feature/2-fast-2-furious
2 Fast 2 Furious (JSON Support)
2 parents e5bf791 + 3903fbe commit 4fb79e8

26 files changed

+1938
-743
lines changed

BlazarTech.QueryableValues.sln

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QueryableValues.SqlServer.E
3030
EndProject
3131
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}"
3232
EndProject
33+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QueryableValues.SqlServer.Benchmarks", "benchmarks\QueryableValues.SqlServer.Benchmarks\QueryableValues.SqlServer.Benchmarks.csproj", "{99FE31A0-BC7E-412C-82E2-DA19E8B68613}"
34+
EndProject
3335
Global
3436
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3537
Debug|Any CPU = Debug|Any CPU
@@ -110,6 +112,12 @@ Global
110112
{D5293D2F-4649-4A2B-A50D-E7DBD4AA4A5C}.Test_All|Any CPU.Build.0 = Test_All|Any CPU
111113
{D5293D2F-4649-4A2B-A50D-E7DBD4AA4A5C}.Test|Any CPU.ActiveCfg = Test|Any CPU
112114
{D5293D2F-4649-4A2B-A50D-E7DBD4AA4A5C}.Test|Any CPU.Build.0 = Test|Any CPU
115+
{99FE31A0-BC7E-412C-82E2-DA19E8B68613}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
116+
{99FE31A0-BC7E-412C-82E2-DA19E8B68613}.Release|Any CPU.ActiveCfg = Release|Any CPU
117+
{99FE31A0-BC7E-412C-82E2-DA19E8B68613}.Release|Any CPU.Build.0 = Release|Any CPU
118+
{99FE31A0-BC7E-412C-82E2-DA19E8B68613}.Test_All|Any CPU.ActiveCfg = Release|Any CPU
119+
{99FE31A0-BC7E-412C-82E2-DA19E8B68613}.Test_All|Any CPU.Build.0 = Release|Any CPU
120+
{99FE31A0-BC7E-412C-82E2-DA19E8B68613}.Test|Any CPU.ActiveCfg = Release|Any CPU
113121
EndGlobalSection
114122
GlobalSection(SolutionProperties) = preSolution
115123
HideSolutionNode = FALSE

README.md

Lines changed: 108 additions & 80 deletions
Large diffs are not rendered by default.

Version.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<Project>
22
<PropertyGroup>
3-
<VersionEFCore3>3.6.0</VersionEFCore3>
4-
<VersionEFCore5>5.6.0</VersionEFCore5>
5-
<VersionEFCore6>6.6.0</VersionEFCore6>
6-
<VersionEFCore7>7.1.0</VersionEFCore7>
3+
<VersionEFCore3>3.7.0</VersionEFCore3>
4+
<VersionEFCore5>5.7.0</VersionEFCore5>
5+
<VersionEFCore6>6.7.0</VersionEFCore6>
6+
<VersionEFCore7>7.2.0</VersionEFCore7>
77
</PropertyGroup>
88
</Project>

benchmarks/QueryableValues.SqlServer.Benchmarks/ContainsBenchmarks.cs

Lines changed: 144 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,41 @@
44
using BlazarTech.QueryableValues;
55
using Microsoft.Data.SqlClient;
66
using Microsoft.EntityFrameworkCore;
7+
using System.Text;
78

89
namespace QueryableValues.SqlServer.Benchmarks;
910

10-
[SimpleJob(RunStrategy.Monitoring, warmupCount: 1, targetCount: 25, invocationCount: 200)]
11+
[SimpleJob(RunStrategy.Monitoring, warmupCount: 1, iterationCount: 25, invocationCount: 200)]
1112
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
1213
[GcServer(true), MemoryDiagnoser]
1314
public class ContainsBenchmarks
1415
{
15-
#pragma warning disable CS8618
16-
private IQueryable<Int32Entity> _int32Query;
17-
private IQueryable<GuidEntity> _guidQuery;
18-
private IQueryable<Int32Entity> _queryableValuesInt32Query;
19-
private IQueryable<GuidEntity> _queryableValuesGuidQuery;
20-
#pragma warning restore CS8618
21-
22-
[Params(2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096)]
16+
private IQueryable<Int32Entity> _int32Query = default!;
17+
private IQueryable<GuidEntity> _guidQuery = default!;
18+
private IQueryable<StringEntity> _stringQuery = default!;
19+
20+
private IQueryable<Int32Entity> _queryableValuesJsonInt32Query = default!;
21+
private IQueryable<GuidEntity> _queryableValuesJsonGuidQuery = default!;
22+
private IQueryable<StringEntity> _queryableValuesJsonStringQuery = default!;
23+
24+
private IQueryable<Int32Entity> _queryableValuesXmlInt32Query = default!;
25+
private IQueryable<GuidEntity> _queryableValuesXmlGuidQuery = default!;
26+
private IQueryable<StringEntity> _queryableValuesXmlStringQuery = default!;
27+
28+
public enum DataType
29+
{
30+
Int32,
31+
Guid,
32+
String
33+
}
34+
35+
[Params(DataType.Int32, DataType.Guid, DataType.String)]
36+
//[Params(DataType.String)]
37+
public DataType Type { get; set; }
38+
39+
//[Params(512)]
40+
//[Params(2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096)]
41+
[Params(2, 8, 32, 128, 512, 2048)]
2342
public int NumberOfValues { get; set; }
2443

2544
private IEnumerable<int> GetIntValues()
@@ -38,87 +57,174 @@ private IEnumerable<Guid> GetGuidValues()
3857
}
3958
}
4059

60+
private IEnumerable<string> GetStringValues()
61+
{
62+
var sb = new StringBuilder();
63+
64+
for (int i = 0; i < NumberOfValues; i++)
65+
{
66+
sb.Clear();
67+
var length = Random.Shared.Next(0, 50);
68+
for (int x = 0; x < length; x++)
69+
{
70+
sb.Append((char)Random.Shared.Next(32, 126));
71+
}
72+
yield return sb.ToString();
73+
}
74+
}
75+
4176
[GlobalSetup]
4277
public void GlobalSetup()
4378
{
4479
Console.WriteLine("Initializing...");
4580

46-
var dbContext = new MyDbContext();
81+
var dbContextXml = new MyDbContext(SqlServerSerialization.UseXml);
82+
var dbContextJson = new MyDbContext(SqlServerSerialization.UseJson);
4783

4884
#region Init db
4985
{
50-
var wasCreated = dbContext.Database.EnsureCreated();
86+
var wasCreated = dbContextXml.Database.EnsureCreated();
5187

5288
if (wasCreated)
5389
{
54-
for (int i = 0; i < 1000; i++)
90+
const int itemsCount = 1000;
91+
92+
for (int i = 0; i < itemsCount; i++)
93+
{
94+
dbContextXml.Add(new Int32Entity());
95+
dbContextXml.Add(new GuidEntity());
96+
}
97+
98+
var i2 = 0;
99+
100+
foreach (var value in GetStringValues())
55101
{
56-
dbContext.Add(new Int32Entity());
57-
dbContext.Add(new GuidEntity());
102+
i2++;
103+
104+
dbContextXml.Add(new StringEntity { Id = $"{value}{i2}" });
105+
106+
if (i2 == itemsCount)
107+
{
108+
break;
109+
}
58110
}
59111

60-
dbContext.SaveChanges();
112+
dbContextXml.SaveChanges();
61113
}
62114

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

68-
dbContext.Database.ExecuteSqlRaw("SET @Version = @@VERSION;", versionParam);
120+
dbContextXml.Database.ExecuteSqlRaw("SET @Version = @@VERSION;", versionParam);
69121

70122
Console.WriteLine(versionParam.Value);
71123

72-
dbContext.Database.ExecuteSqlRaw("DBCC FREEPROCCACHE; DBCC DROPCLEANBUFFERS;");
124+
dbContextXml.Database.ExecuteSqlRaw("DBCC FREEPROCCACHE; DBCC DROPCLEANBUFFERS;");
73125
}
74126
#endregion
75127

76128
#region Int32 Queries
77129
{
78130
var intValues = GetIntValues();
79131

80-
_int32Query = dbContext.Int32Entities
132+
_int32Query = dbContextXml.Int32Entities
81133
.Where(i => intValues.Contains(i.Id));
82134

83-
_queryableValuesInt32Query = dbContext.Int32Entities
84-
.Where(i => dbContext.AsQueryableValues(intValues).Contains(i.Id));
135+
_queryableValuesXmlInt32Query = dbContextXml.Int32Entities
136+
.Where(i => dbContextXml.AsQueryableValues(intValues).Contains(i.Id));
137+
138+
_queryableValuesJsonInt32Query = dbContextJson.Int32Entities
139+
.Where(i => dbContextJson.AsQueryableValues(intValues).Contains(i.Id));
85140
}
86141
#endregion
87142

88143
#region Guid Queries
89144
{
90145
var guidValues = GetGuidValues();
91146

92-
_guidQuery = dbContext.GuidEntities
147+
_guidQuery = dbContextXml.GuidEntities
93148
.Where(i => guidValues.Contains(i.Id));
94149

95-
_queryableValuesGuidQuery = dbContext.GuidEntities
96-
.Where(i => dbContext.AsQueryableValues(guidValues).Contains(i.Id));
150+
_queryableValuesXmlGuidQuery = dbContextXml.GuidEntities
151+
.Where(i => dbContextXml.AsQueryableValues(guidValues).Contains(i.Id));
152+
153+
_queryableValuesJsonGuidQuery = dbContextJson.GuidEntities
154+
.Where(i => dbContextJson.AsQueryableValues(guidValues).Contains(i.Id));
97155
}
98156
#endregion
99-
}
100157

101-
[Benchmark(Baseline = true), BenchmarkCategory("Int32")]
102-
public void Without_Int32()
103-
{
104-
_int32Query.Any();
158+
#region String Queries
159+
{
160+
var stringValues = GetStringValues();
161+
162+
_stringQuery = dbContextXml.StringEntities
163+
.Where(i => stringValues.Contains(i.Id));
164+
165+
_queryableValuesXmlStringQuery = dbContextXml.StringEntities
166+
.Where(i => dbContextXml.AsQueryableValues(stringValues, true).Contains(i.Id));
167+
168+
_queryableValuesJsonStringQuery = dbContextJson.StringEntities
169+
.Where(i => dbContextJson.AsQueryableValues(stringValues, true).Contains(i.Id));
170+
}
171+
#endregion
105172
}
106173

107-
[Benchmark, BenchmarkCategory("Int32")]
108-
public void With_Int32()
174+
[Benchmark(Baseline = true)]
175+
public void Without()
109176
{
110-
_queryableValuesInt32Query.Any();
177+
switch (Type)
178+
{
179+
case DataType.Int32:
180+
_int32Query.Any();
181+
break;
182+
case DataType.Guid:
183+
_guidQuery.Any();
184+
break;
185+
case DataType.String:
186+
_stringQuery.Any();
187+
break;
188+
default:
189+
throw new NotImplementedException();
190+
}
111191
}
112192

113-
[Benchmark(Baseline = true), BenchmarkCategory("Guid")]
114-
public void Without_Guid()
193+
[Benchmark]
194+
public void WithXml()
115195
{
116-
_guidQuery.Any();
196+
switch (Type)
197+
{
198+
case DataType.Int32:
199+
_queryableValuesXmlInt32Query.Any();
200+
break;
201+
case DataType.Guid:
202+
_queryableValuesXmlGuidQuery.Any();
203+
break;
204+
case DataType.String:
205+
_queryableValuesXmlStringQuery.Any();
206+
break;
207+
default:
208+
throw new NotImplementedException();
209+
}
117210
}
118211

119-
[Benchmark, BenchmarkCategory("Guid")]
120-
public void With_Guid()
212+
[Benchmark]
213+
public void WithJson()
121214
{
122-
_queryableValuesGuidQuery.Any();
215+
switch (Type)
216+
{
217+
case DataType.Int32:
218+
_queryableValuesJsonInt32Query.Any();
219+
break;
220+
case DataType.Guid:
221+
_queryableValuesJsonGuidQuery.Any();
222+
break;
223+
case DataType.String:
224+
_queryableValuesJsonStringQuery.Any();
225+
break;
226+
default:
227+
throw new NotImplementedException();
228+
}
123229
}
124-
}
230+
}

benchmarks/QueryableValues.SqlServer.Benchmarks/MyDbContext.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
using BlazarTech.QueryableValues;
22
using Microsoft.EntityFrameworkCore;
3+
using System.ComponentModel.DataAnnotations;
34

45
namespace QueryableValues.SqlServer.Benchmarks
56
{
67
public class MyDbContext : DbContext
78
{
8-
#pragma warning disable CS8618
9-
public DbSet<Int32Entity> Int32Entities { get; set; }
10-
public DbSet<GuidEntity> GuidEntities { get; set; }
11-
#pragma warning restore CS8618
9+
private readonly SqlServerSerialization _serializationOption;
10+
11+
public DbSet<Int32Entity> Int32Entities { get; set; } = default!;
12+
public DbSet<GuidEntity> GuidEntities { get; set; } = default!;
13+
public DbSet<StringEntity> StringEntities { get; set; } = default!;
14+
15+
public MyDbContext(SqlServerSerialization serializationOption)
16+
{
17+
_serializationOption = serializationOption;
18+
}
1219

1320
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
1421
{
1522
optionsBuilder.UseSqlServer(
16-
@"Server=.\SQLEXPRESS;Integrated Security=true;Database=QueryableValuesBenchmarks",
17-
builder => builder.UseQueryableValues()
23+
@"Server=.\SQLEXPRESS;Integrated Security=true;Database=QueryableValuesBenchmarks;Encrypt=False;",
24+
builder => builder.UseQueryableValues(options => options.Serialization(_serializationOption))
1825
);
1926
}
2027

@@ -33,4 +40,10 @@ public class GuidEntity
3340
{
3441
public Guid Id { get; set; }
3542
}
43+
44+
public class StringEntity
45+
{
46+
[MaxLength(100)]
47+
public string Id { get; set; } = default!;
48+
}
3649
}
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1-
using BenchmarkDotNet.Running;
1+
using BenchmarkDotNet.Configs;
2+
using BenchmarkDotNet.Running;
23

34
namespace QueryableValues.SqlServer.Benchmarks;
45

56
class Program
67
{
78
static void Main(string[] args)
89
{
9-
var summary = BenchmarkRunner.Run<ContainsBenchmarks>();
10+
var config = new ManualConfig();
11+
12+
config.Add(DefaultConfig.Instance);
13+
config.WithOptions(ConfigOptions.DisableOptimizationsValidator);
14+
15+
BenchmarkRunner.Run<ContainsBenchmarks>(config);
1016
}
1117
}

benchmarks/QueryableValues.SqlServer.Benchmarks/QueryableValues.SqlServer.Benchmarks.csproj

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
12-
<PackageReference Include="BlazarTech.QueryableValues.SqlServer" Version="6.3.0" />
13-
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.1" />
11+
<PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
12+
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.4" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="..\..\src\QueryableValues.SqlServer.EFCore7\QueryableValues.SqlServer.EFCore7.csproj" />
1417
</ItemGroup>
1518

1619
</Project>

docs/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
[![GitHub Stars](https://badgen.net/github/stars/yv989c/BlazarTech.QueryableValues?icon=github)][Repository]
55
[![Nuget Downloads](https://badgen.net/nuget/dt/BlazarTech.QueryableValues.SqlServer?icon=nuget)][NuGet Package]
66

7+
> 🤔💭 TLDR; By using QueryableValues, you can incorporate in-memory collections into your EF queries with outstanding performance and flexibility.
8+
79
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.
810

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

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

20+
> ✅ 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.
21+
1822
> 💡 Still on Entity Framework 6 (non-core)? Then [QueryableValues `EF6 Edition`](https://github.com/yv989c/BlazarTech.QueryableValues.EF6) is what you need.
1923
2024
## When Should You Use It?
@@ -75,6 +79,7 @@ public class Startup
7579
}
7680
}
7781
```
82+
> 💡 Pro-tip: `UseQueryableValues` offers an optional `options` delegate for additional configurations.
7883
7984
## How Do You Use It?
8085
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:

docs/images/benchmarks/v7.2.0.png

208 KB
Loading

0 commit comments

Comments
 (0)