Skip to content

Commit

Permalink
Merge pull request #37 from yv989c/develop
Browse files Browse the repository at this point in the history
New Features
  • Loading branch information
yv989c committed Jun 24, 2023
2 parents c90d110 + 1e3d4b1 commit d97c953
Show file tree
Hide file tree
Showing 21 changed files with 682 additions and 93 deletions.
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

0 comments on commit d97c953

Please sign in to comment.