Skip to content
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

Feature/optimize #42

Merged
merged 7 commits into from
Dec 7, 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
198 changes: 93 additions & 105 deletions README.md

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions Version.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<Project>
<PropertyGroup>
<VersionEFCore3>3.9.1</VersionEFCore3>
<VersionEFCore5>5.9.1</VersionEFCore5>
<VersionEFCore6>6.9.1</VersionEFCore6>
<VersionEFCore7>7.4.1</VersionEFCore7>
<VersionEFCore8>8.0.0</VersionEFCore8>
<VersionEFCore3>3.9.2</VersionEFCore3>
<VersionEFCore5>5.9.2</VersionEFCore5>
<VersionEFCore6>6.9.2</VersionEFCore6>
<VersionEFCore7>7.4.2</VersionEFCore7>
<VersionEFCore8>8.1.0</VersionEFCore8>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

namespace QueryableValues.SqlServer.Benchmarks;

//[SimpleJob(RunStrategy.Monitoring, warmupCount: 1, iterationCount: 1, invocationCount: 6)]
[SimpleJob(RunStrategy.Monitoring, warmupCount: 1, iterationCount: 25, invocationCount: 200)]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[GcServer(true), MemoryDiagnoser]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.4" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
</ItemGroup>

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

</Project>
7 changes: 3 additions & 4 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ For a detailed explanation of the problem solved by QueryableValues, please cont

> 💡 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?
The `AsQueryableValues` extension method is intended for queries that are dependent upon a *non-constant* sequence of external values. It provides a solution to the following [EF Core issue](https://github.com/dotnet/efcore/issues/13617) and enables other currently unsupported scenarios; like the ability to efficiently create joins with in-memory data.

## Your Support is Appreciated!
If you feel that this solution has provided you some value, please consider [buying me a ☕][BuyMeACoffee].

Expand Down Expand Up @@ -88,7 +85,7 @@ Below are a few examples composing a query using the values provided by an [IEnu

### Simple Type Examples

> 💡 Supports [Byte], [Int16], [Int32], [Int64], [Decimal], [Single], [Double], [DateTime], [DateTimeOffset], [Guid], [Char], [String], and [Enum].
> 💡 Supports [Byte], [Int16], [Int32], [Int64], [Decimal], [Single], [Double], [DateTime], [DateTimeOffset], [DateOnly], [TimeOnly], [Guid], [Char], [String], and [Enum].

Using the [Contains][ContainsQueryable] LINQ method:

Expand Down Expand Up @@ -215,6 +212,8 @@ Please take a look at the [repository][Repository].
[Double]: https://docs.microsoft.com/en-us/dotnet/api/system.double
[DateTime]: https://docs.microsoft.com/en-us/dotnet/api/system.datetime
[DateTimeOffset]: https://docs.microsoft.com/en-us/dotnet/api/system.datetimeoffset
[DateOnly]: https://docs.microsoft.com/en-us/dotnet/api/system.dateonly
[TimeOnly]: https://docs.microsoft.com/en-us/dotnet/api/system.timeonly
[Guid]: https://docs.microsoft.com/en-us/dotnet/api/system.guid
[Char]: https://docs.microsoft.com/en-us/dotnet/api/system.char
[String]: https://docs.microsoft.com/en-us/dotnet/api/system.string
Expand Down
File renamed without changes
Binary file added docs/benchmarks/images/v8.1.0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
120 changes: 120 additions & 0 deletions docs/benchmarks/v7.2.0.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../SharedProjectProperties.xml" />
<Import Project="../../Version.xml" />
<Import Project="../SharedProjectProperties.xml" />
<Import Project="../../Version.xml" />

<PropertyGroup>
<VersionPrefix>$(VersionEFCore3)</VersionPrefix>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<LangVersion>9.0</LangVersion>
<Configurations>Debug;Release;Test</Configurations>
<DefineConstants>$(DefineConstants);EFCORE;EFCORE3</DefineConstants>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="[3.1.14,5.0)" />
</ItemGroup>
<PropertyGroup>
<VersionPrefix>$(VersionEFCore3)</VersionPrefix>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<LangVersion>9.0</LangVersion>
<Configurations>Debug;Release;Test</Configurations>
<DefineConstants>$(DefineConstants);EFCORE;EFCORE3</DefineConstants>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
<PackageReference Include="System.Text.Json" Version="4.7.2" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="[3.1.14,5.0)" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../SharedProjectProperties.xml" />
<Import Project="../../Version.xml" />

<PropertyGroup>
<VersionPrefix>$(VersionEFCore5)</VersionPrefix>
<TargetFrameworks>netstandard2.1;net6.0</TargetFrameworks>
<LangVersion>9.0</LangVersion>
<Configurations>Debug;Release;Test</Configurations>
<DefineConstants>$(DefineConstants);EFCORE;EFCORE5</DefineConstants>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="[5.0,6.0)" />
</ItemGroup>
<Import Project="../SharedProjectProperties.xml" />
<Import Project="../../Version.xml" />

<PropertyGroup>
<VersionPrefix>$(VersionEFCore5)</VersionPrefix>
<TargetFrameworks>netstandard2.1;net6.0</TargetFrameworks>
<LangVersion>9.0</LangVersion>
<Configurations>Debug;Release;Test</Configurations>
<DefineConstants>$(DefineConstants);EFCORE;EFCORE5</DefineConstants>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.1'">
<PackageReference Include="System.Text.Json" Version="4.7.2" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="[5.0,6.0)" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="[6.0,7.0)" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../SharedProjectProperties.xml" />
<Import Project="../../Version.xml" />

<PropertyGroup>
<VersionPrefix>$(VersionEFCore7)</VersionPrefix>
<TargetFramework>net6.0</TargetFramework>
<Configurations>Debug;Release;Test</Configurations>
<DefineConstants>$(DefineConstants);EFCORE;EFCORE7</DefineConstants>
</PropertyGroup>
<Import Project="../SharedProjectProperties.xml" />
<Import Project="../../Version.xml" />

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="[7.0,)" />
</ItemGroup>
<PropertyGroup>
<VersionPrefix>$(VersionEFCore7)</VersionPrefix>
<TargetFramework>net6.0</TargetFramework>
<Configurations>Debug;Release;Test</Configurations>
<DefineConstants>$(DefineConstants);EFCORE;EFCORE7</DefineConstants>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="[7.0,)" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../SharedProjectProperties.xml" />
<Import Project="../../Version.xml" />

<PropertyGroup>
<VersionPrefix>$(VersionEFCore8)</VersionPrefix>
<TargetFramework>net8.0</TargetFramework>
<Configurations>Debug;Release;Test</Configurations>
<DefineConstants>$(DefineConstants);EFCORE;EFCORE8</DefineConstants>
</PropertyGroup>
<Import Project="../SharedProjectProperties.xml" />
<Import Project="../../Version.xml" />

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="[8.0,)" />
</ItemGroup>
<PropertyGroup>
<VersionPrefix>$(VersionEFCore8)</VersionPrefix>
<TargetFramework>net8.0</TargetFramework>
<Configurations>Debug;Release;Test</Configurations>
<DefineConstants>$(DefineConstants);EFCORE;EFCORE8</DefineConstants>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="[8.0,)" />
</ItemGroup>
</Project>
5 changes: 3 additions & 2 deletions src/QueryableValues.SqlServer/DeferredValues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

namespace BlazarTech.QueryableValues
{
internal sealed class DeferredValues<T, T2> : IDeferredValues
internal sealed class DeferredValues<T, T2, TEntity> : IDeferredValues
where T : notnull
where T2 : notnull
where TEntity : QueryableValuesEntity
{
private readonly ISerializer _serializer;
private readonly ValuesWrapper<T, T2> _valuesWrapper;
Expand All @@ -25,7 +26,7 @@ public DeferredValues(ISerializer serializer, ValuesWrapper<T, T2> valuesWrapper
{
_serializer = serializer;
_valuesWrapper = valuesWrapper;
Mappings = EntityPropertyMapping.GetMappings<T2>();
Mappings = EntityPropertyMapping.GetMappings<T2, TEntity>();
}

public string ToString(IFormatProvider? provider) => _serializer.Serialize(_valuesWrapper.ProjectedValues, Mappings);
Expand Down
47 changes: 38 additions & 9 deletions src/QueryableValues.SqlServer/EntityPropertyMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ internal sealed class EntityPropertyMapping
{
internal static readonly IReadOnlyDictionary<Type, EntityPropertyTypeName> SimpleTypes;

private static readonly PropertyInfo[] EntityProperties = typeof(QueryableValuesEntity).GetProperties().Where(i => i.Name != QueryableValuesEntity.IndexPropertyName).ToArray();
private static readonly ConcurrentDictionary<Type, IReadOnlyList<EntityPropertyMapping>> MappingCache = new ConcurrentDictionary<Type, IReadOnlyList<EntityPropertyMapping>>();
private static readonly ConcurrentDictionary<Type, IReadOnlyList<PropertyInfo>> TargetTypePropertyCache = new ConcurrentDictionary<Type, IReadOnlyList<PropertyInfo>>();
private static readonly ConcurrentDictionary<(Type, Type), IReadOnlyList<EntityPropertyMapping>> MappingCache = new ConcurrentDictionary<(Type, Type), IReadOnlyList<EntityPropertyMapping>>();

public PropertyInfo Source { get; }
public PropertyInfo Target { get; }
Expand All @@ -35,7 +35,11 @@ static EntityPropertyMapping()
{ typeof(DateTimeOffset), EntityPropertyTypeName.DateTimeOffset },
{ typeof(Guid), EntityPropertyTypeName.Guid },
{ typeof(char), EntityPropertyTypeName.Char },
{ typeof(string), EntityPropertyTypeName.String }
{ typeof(string), EntityPropertyTypeName.String },
#if EFCORE8
{ typeof(DateOnly), EntityPropertyTypeName.DateOnly },
{ typeof(TimeOnly), EntityPropertyTypeName.TimeOnly }
#endif
};
}

Expand Down Expand Up @@ -87,9 +91,33 @@ public static bool IsSimpleType(Type type)
return SimpleTypes.ContainsKey(normalizedType);
}

public static IReadOnlyList<EntityPropertyMapping> GetMappings(Type sourceType)
private static IReadOnlyList<PropertyInfo> GetTargetTypeProperties(Type targetType)
{
if (MappingCache.TryGetValue(sourceType, out IReadOnlyList<EntityPropertyMapping>? mappingsFromCache))
if (TargetTypePropertyCache.TryGetValue(targetType, out IReadOnlyList<PropertyInfo>? properties))
{
return properties;
}

if (!typeof(QueryableValuesEntity).IsAssignableFrom(targetType))
{
throw new InvalidOperationException();
}

properties = targetType
.GetProperties()
.Where(i => i.Name != QueryableValuesEntity.IndexPropertyName)
.ToArray();

TargetTypePropertyCache.TryAdd(targetType, properties);

return properties;
}

public static IReadOnlyList<EntityPropertyMapping> GetMappings(Type sourceType, Type targetType)
{
var mappingCacheKey = (sourceType, targetType);

if (MappingCache.TryGetValue(mappingCacheKey, out IReadOnlyList<EntityPropertyMapping>? mappingsFromCache))
{
return mappingsFromCache;
}
Expand All @@ -104,7 +132,7 @@ public static IReadOnlyList<EntityPropertyMapping> GetMappings(Type sourceType)
var mappings = new List<EntityPropertyMapping>(sourceProperties.Length);

var targetPropertiesByType = (
from i in EntityProperties
from i in GetTargetTypeProperties(targetType)
group i by GetNormalizedType(i.PropertyType) into g
select g
)
Expand Down Expand Up @@ -136,14 +164,15 @@ select g
}
}

MappingCache.TryAdd(sourceType, mappings);
MappingCache.TryAdd(mappingCacheKey, mappings);

return mappings;
}

public static IReadOnlyList<EntityPropertyMapping> GetMappings<T>()
public static IReadOnlyList<EntityPropertyMapping> GetMappings<TSource, TTargetEntity>()
where TTargetEntity : QueryableValuesEntity
{
return GetMappings(typeof(T));
return GetMappings(typeof(TSource), typeof(TTargetEntity));
}

public object? GetSourceNormalizedValue(object objectInstance)
Expand Down
6 changes: 5 additions & 1 deletion src/QueryableValues.SqlServer/EntityPropertyTypeName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ internal enum EntityPropertyTypeName
DateTimeOffset,
Guid,
Char,
String
String,
#if EFCORE8
DateOnly,
TimeOnly
#endif
}
}
5 changes: 5 additions & 0 deletions src/QueryableValues.SqlServer/IQueryableFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,10 @@ public IQueryable<TEnum> Create<TEnum>(DbContext dbContext, IEnumerable<TEnum> v
where TEnum : struct, Enum;
IQueryable<TSource> Create<TSource>(DbContext dbContext, IEnumerable<TSource> values, Action<EntityOptionsBuilder<TSource>>? configure)
where TSource : notnull;

#if EFCORE8
IQueryable<DateOnly> Create(DbContext dbContext, IEnumerable<DateOnly> values);
IQueryable<TimeOnly> Create(DbContext dbContext, IEnumerable<TimeOnly> values);
#endif
}
}
Loading