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

New Features #37

Merged
merged 15 commits into from
Jun 24, 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
7 changes: 0 additions & 7 deletions .github/workflows/ci-workflow.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
# Adapted from: https://github.com/giraffe-fsharp/Giraffe/blob/master/.github/workflows/build.yml
name: CI/CD Workflow
on:
push:
branches:
- develop
- 'feature/**'
paths:
- 'src/**'
- 'Version.xml'
pull_request:
paths:
- 'src/**'
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,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], and [String].
> 💡 Supports [Byte], [Int16], [Int32], [Int64], [Decimal], [Single], [Double], [DateTime], [DateTimeOffset], [Guid], [Char], [String], and [Enum].

Using the [Contains][ContainsQueryable] LINQ method:

Expand Down Expand Up @@ -474,6 +474,7 @@ PRs are welcome! 🙂
[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
[Enum]: https://docs.microsoft.com/en-us/dotnet/api/system.enum
[BuyMeACoffee]: https://www.buymeacoffee.com/yv989c
[BuyMeACoffeeButton]: /docs/images/bmc-48.svg
[Repository]: https://github.com/yv989c/BlazarTech.QueryableValues
Expand Down
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.8.0</VersionEFCore3>
<VersionEFCore5>5.8.0</VersionEFCore5>
<VersionEFCore6>6.8.0</VersionEFCore6>
<VersionEFCore7>7.3.0</VersionEFCore7>
<VersionEFCore3>3.9.0</VersionEFCore3>
<VersionEFCore5>5.9.0</VersionEFCore5>
<VersionEFCore6>6.9.0</VersionEFCore6>
<VersionEFCore7>7.4.0</VersionEFCore7>
</PropertyGroup>
</Project>
3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,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], and [String].
> 💡 Supports [Byte], [Int16], [Int32], [Int64], [Decimal], [Single], [Double], [DateTime], [DateTimeOffset], [Guid], [Char], [String], and [Enum].

Using the [Contains][ContainsQueryable] LINQ method:

Expand Down Expand Up @@ -218,6 +218,7 @@ Please take a look at the [repository][Repository].
[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
[Enum]: https://docs.microsoft.com/en-us/dotnet/api/system.enum
[BuyMeACoffee]: https://www.buymeacoffee.com/yv989c
[BuyMeACoffeeButton]: https://raw.githubusercontent.com/yv989c/BlazarTech.QueryableValues/develop/docs/images/bmc-48.svg
[Repository]: https://github.com/yv989c/BlazarTech.QueryableValues
75 changes: 67 additions & 8 deletions src/QueryableValues.SqlServer/EntityPropertyMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal sealed class EntityPropertyMapping
public PropertyInfo Target { get; }
public Type NormalizedType { get; }
public EntityPropertyTypeName TypeName { get; }
public bool IsSourceEnum { get; }

static EntityPropertyMapping()
{
Expand All @@ -38,23 +39,47 @@ static EntityPropertyMapping()
};
}

private EntityPropertyMapping(PropertyInfo source, PropertyInfo target, Type normalizedType)
private EntityPropertyMapping(PropertyInfo source, PropertyInfo target, Type normalizedType, bool isSourceEnum)
{
Source = source;
Target = target;
NormalizedType = normalizedType;
TypeName = GetTypeName(normalizedType);
IsSourceEnum = isSourceEnum;

if (SimpleTypes.TryGetValue(normalizedType, out EntityPropertyTypeName typeName))
if (TypeName == EntityPropertyTypeName.Unknown)
{
TypeName = typeName;
throw new NotSupportedException($"{source.PropertyType.FullName} is not supported.");
}
}

public static EntityPropertyTypeName GetTypeName(Type type)
{
if (SimpleTypes.TryGetValue(type, out EntityPropertyTypeName typeName))
{
return typeName;
}
else
{
throw new NotSupportedException($"{source.PropertyType.FullName} is not supported.");
return EntityPropertyTypeName.Unknown;
}
}

private static Type GetNormalizedType(Type type) => Nullable.GetUnderlyingType(type) ?? type;
public static Type GetNormalizedType(Type type, out bool isEnum)
{
type = Nullable.GetUnderlyingType(type) ?? type;

isEnum = type.IsEnum;

if (isEnum)
{
type = Enum.GetUnderlyingType(type);
}

return type;
}

public static Type GetNormalizedType(Type type) => GetNormalizedType(type, out _);

public static bool IsSimpleType(Type type)
{
Expand Down Expand Up @@ -87,9 +112,9 @@ select g

foreach (var sourceProperty in sourceProperties)
{
var propertyType = GetNormalizedType(sourceProperty.PropertyType);
var sourcePropertyNormalizedType = GetNormalizedType(sourceProperty.PropertyType, out var isSourceEnum);

if (targetPropertiesByType.TryGetValue(propertyType, out Queue<PropertyInfo>? targetProperties))
if (targetPropertiesByType.TryGetValue(sourcePropertyNormalizedType, out Queue<PropertyInfo>? targetProperties))
{
if (targetProperties.Count == 0)
{
Expand All @@ -99,7 +124,8 @@ select g
var mapping = new EntityPropertyMapping(
sourceProperty,
targetProperties.Dequeue(),
propertyType
sourcePropertyNormalizedType,
isSourceEnum
);

mappings.Add(mapping);
Expand All @@ -119,5 +145,38 @@ public static IReadOnlyList<EntityPropertyMapping> GetMappings<T>()
{
return GetMappings(typeof(T));
}

public object? GetSourceNormalizedValue(object objectInstance)
{
var value = Source.GetValue(objectInstance);

if (value is null)
{
return null;
}

if (IsSourceEnum)
{
switch (TypeName)
{
case EntityPropertyTypeName.Int32:
value = (int)value;
break;
case EntityPropertyTypeName.Byte:
value = (byte)value;
break;
case EntityPropertyTypeName.Int16:
value = (short)value;
break;
case EntityPropertyTypeName.Int64:
value = (long)value;
break;
default:
throw new NotSupportedException($"The underlying type of {NormalizedType.FullName} ({Enum.GetUnderlyingType(NormalizedType).FullName}) is not supported.");
}
}

return value;
}
}
}
1 change: 1 addition & 0 deletions src/QueryableValues.SqlServer/EntityPropertyTypeName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{
internal enum EntityPropertyTypeName
{
Unknown,
Boolean,
Byte,
Int16,
Expand Down
6 changes: 3 additions & 3 deletions src/QueryableValues.SqlServer/IQueryableFactory.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#if EFCORE
using BlazarTech.QueryableValues.Builders;
using BlazarTech.QueryableValues.Builders;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
Expand All @@ -21,8 +20,9 @@ internal interface IQueryableFactory
IQueryable<char> Create(DbContext dbContext, IEnumerable<char> values, bool isUnicode);
IQueryable<string> Create(DbContext dbContext, IEnumerable<string> values, bool isUnicode);
IQueryable<Guid> Create(DbContext dbContext, IEnumerable<Guid> values);
public IQueryable<TEnum> Create<TEnum>(DbContext dbContext, IEnumerable<TEnum> values)
where TEnum : struct, Enum;
IQueryable<TSource> Create<TSource>(DbContext dbContext, IEnumerable<TSource> values, Action<EntityOptionsBuilder<TSource>>? configure)
where TSource : notnull;
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
<TargetFramework>netstandard2.1</TargetFramework>
<IsPackable>false</IsPackable>
<Configurations>Debug;Release;Test</Configurations>
</PropertyGroup>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#if EFCORE
using BlazarTech.QueryableValues.Builders;
using BlazarTech.QueryableValues.Builders;
using BlazarTech.QueryableValues.SqlServer;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
Expand Down Expand Up @@ -227,6 +226,27 @@ public static IQueryable<Guid> AsQueryableValues(this DbContext dbContext, IEnum
return GetQueryableFactory(dbContext).Create(dbContext, values);
}

/// <summary>
/// Allows an <see cref="IEnumerable{Enum}">IEnumerable&lt;Enum&gt;</see> to be composed in an Entity Framework query.
/// </summary>
/// <remarks>
/// The supported underlying types are: <see cref="byte"/>, <see cref="short"/>, <see cref="int"/>, and <see cref="long"/>.
/// <para>
/// More info: <see href="https://learn.microsoft.com/en-us/dotnet/api/system.enum#remarks"/>.
/// </para>
/// </remarks>
/// <param name="dbContext">The <see cref="DbContext"/> owning the query.</param>
/// <param name="values">The sequence of values to compose.</param>
/// <returns>An <see cref="IQueryable{Enum}">IQueryable&lt;Enum&gt;</see> that can be composed with other entities in the query.</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception>
public static IQueryable<TEnum> AsQueryableValues<TEnum>(this DbContext dbContext, IEnumerable<TEnum> values)
where TEnum : struct, Enum
{
ValidateParameters(dbContext, values);
return GetQueryableFactory(dbContext).Create(dbContext, values);
}

/// <summary>
/// Allows an <see cref="IEnumerable{T}"/> to be composed in an Entity Framework query.
/// </summary>
Expand All @@ -244,4 +264,3 @@ public static IQueryable<TSource> AsQueryableValues<TSource>(this DbContext dbCo
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#if EFCORE
using BlazarTech.QueryableValues.Builders;
using BlazarTech.QueryableValues.Builders;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -200,6 +199,21 @@ public static IQueryable<Guid> AsQueryableValues(this IQueryableValuesEnabledDbC
return GetDbContext(dbContext).AsQueryableValues(values);
}

/// <summary>
/// <inheritdoc cref="QueryableValuesDbContextExtensions.AsQueryableValues{TEnum}(DbContext, IEnumerable{TEnum})"/>
/// </summary>
/// <param name="dbContext"><inheritdoc cref="QueryableValuesDbContextExtensions.AsQueryableValues{TEnum}(DbContext, IEnumerable{TEnum})" path="/param[@name='dbContext']"/></param>
/// <param name="values"><inheritdoc cref="QueryableValuesDbContextExtensions.AsQueryableValues{TEnum}(DbContext, IEnumerable{TEnum})" path="/param[@name='values']"/></param>
/// <returns><inheritdoc cref="QueryableValuesDbContextExtensions.AsQueryableValues{TEnum}(DbContext, IEnumerable{TEnum})"/></returns>
/// <remarks><inheritdoc cref="QueryableValuesDbContextExtensions.AsQueryableValues{TEnum}(DbContext, IEnumerable{TEnum})"/></remarks>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception>
public static IQueryable<TEnum> AsQueryableValues<TEnum>(this IQueryableValuesEnabledDbContext dbContext, IEnumerable<TEnum> values)
where TEnum : struct, Enum
{
return GetDbContext(dbContext).AsQueryableValues(values);
}

/// <summary>
/// <inheritdoc cref="QueryableValuesDbContextExtensions.AsQueryableValues{TSource}(DbContext, IEnumerable{TSource}, Action{EntityOptionsBuilder{TSource}}?)"/>
/// </summary>
Expand All @@ -217,4 +231,3 @@ public static IQueryable<TSource> AsQueryableValues<TSource>(this IQueryableValu
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#if EFCORE
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System;

namespace BlazarTech.QueryableValues
{
/// <summary>
/// Provides extension methods to register core QueryableValues services.
/// </summary>
public static class QueryableValuesServiceCollectionExtensions
{
/// <summary>
/// Adds the services required by QueryableValues for the Microsoft SQL Server database provider.
/// </summary>
/// <remarks>
/// It is only needed when building the internal service provider for use with
/// the <see cref="DbContextOptionsBuilder.UseInternalServiceProvider" /> method.
/// This is not recommend other than for some advanced scenarios.
/// </remarks>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <returns>The same service collection so that multiple calls can be chained.</returns>
/// <exception cref="ArgumentNullException"></exception>
public static IServiceCollection AddQueryableValuesSqlServer(this IServiceCollection services)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}

for (var index = services.Count - 1; index >= 0; index--)
{
var descriptor = services[index];
if (descriptor.ServiceType != typeof(IModelCustomizer))
{
continue;
}

if (descriptor.ImplementationType is null)
{
continue;
}

// Replace theirs with ours.
services[index] = new ServiceDescriptor(
descriptor.ServiceType,
typeof(ModelCustomizer<>).MakeGenericType(descriptor.ImplementationType),
descriptor.Lifetime
);

// Add theirs as is, so we can inject it into ours.
services.Add(
new ServiceDescriptor(
descriptor.ImplementationType,
descriptor.ImplementationType,
descriptor.Lifetime
)
);
}

services.TryAddSingleton<Serializers.IXmlSerializer, Serializers.XmlSerializer>();
services.TryAddSingleton<Serializers.IJsonSerializer, Serializers.JsonSerializer>();
services.TryAddScoped<SqlServer.XmlQueryableFactory>();
services.TryAddScoped<SqlServer.JsonQueryableFactory>();
services.TryAddScoped<SqlServer.ExtensionOptions>();
services.TryAddScoped<SqlServer.QueryableFactoryFactory>();
services.TryAddScoped<IInterceptor, SqlServer.JsonSupportConnectionInterceptor>();

return services;
}
}
}
#endif
Loading