Skip to content

Commit 567ec2c

Browse files
committed
Adds in-line optional wrap support
1 parent 7693d1d commit 567ec2c

File tree

4 files changed

+63
-113
lines changed

4 files changed

+63
-113
lines changed

src/Codehard.Functional/Codehard.Functional.EntityFramework/Codehard.Functional.EntityFramework.csproj

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@
44
<TargetFramework>net6.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
7-
<Version>2.5.0-rc1.1</Version>
8-
<Description>A functional extensions for Entity Framework.</Description>
7+
<Version>2.4.0</Version>
8+
<Description>A functional extensions for Entity Framework queryable extensions.</Description>
99
<PackageProjectUrl>https://github.com/codehardth/Codehard.Common/tree/main/src/Codehard.Functional</PackageProjectUrl>
1010
<RepositoryUrl>https://github.com/codehardth/Codehard.Common/tree/main/src/Codehard.Functional</RepositoryUrl>
11-
<PackageReleaseNotes>Adds Option type support.</PackageReleaseNotes>
11+
<PackageReleaseNotes>Initial release.</PackageReleaseNotes>
1212
<GenerateDocumentationFile>true</GenerateDocumentationFile>
1313
</PropertyGroup>
1414

1515
<ItemGroup>
16-
<PackageReference Include="LanguageExt.Core" Version="4.4.2"/>
17-
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.2"/>
18-
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.2"/>
16+
<PackageReference Include="LanguageExt.Core" Version="4.4.2" />
17+
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.2" />
18+
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.2" />
1919
</ItemGroup>
2020

2121
</Project>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System.Collections;
2+
using System.Data.Common;
3+
using LanguageExt;
4+
using Microsoft.EntityFrameworkCore.Diagnostics;
5+
6+
namespace Codehard.Functional.EntityFramework.Interceptors;
7+
8+
public sealed class OptionalQueryInterceptor : DbCommandInterceptor
9+
{
10+
public override InterceptionResult<DbDataReader> ReaderExecuting(
11+
DbCommand command,
12+
CommandEventData eventData,
13+
InterceptionResult<DbDataReader> result)
14+
{
15+
foreach (DbParameter param in command.Parameters)
16+
{
17+
var type = param.Value?.GetType();
18+
19+
if (type is not { IsGenericType: true })
20+
{
21+
continue;
22+
}
23+
24+
if (type.GetGenericTypeDefinition() == typeof(Option<>))
25+
{
26+
var enumerable = (IEnumerable)param.Value!;
27+
var enumerator = enumerable.GetEnumerator();
28+
var hasValue = enumerator.MoveNext();
29+
30+
param.Value = hasValue ? enumerator.Current : DBNull.Value;
31+
}
32+
}
33+
34+
return base.ReaderExecuting(command, eventData, result);
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
using System.Collections;
2-
using System.Data.Common;
3-
using System.Linq.Expressions;
1+
using System.Linq.Expressions;
42
using Codehard.Functional.EntityFramework.Visitors;
5-
using LanguageExt;
63
using Microsoft.EntityFrameworkCore.Diagnostics;
74

85
namespace Codehard.Functional.EntityFramework.Interceptors;
@@ -13,56 +10,17 @@ Expression IQueryExpressionInterceptor.QueryCompilationStarting(
1310
Expression queryExpression,
1411
QueryExpressionEventData eventData)
1512
{
16-
var exprVisitor = new OptionExpressionVisitor();
13+
var nodeType = queryExpression.Type;
1714

18-
return exprVisitor.Visit(queryExpression)!;
19-
}
20-
}
21-
22-
public sealed class OptionalQueryInterceptor : DbCommandInterceptor
23-
{
24-
/// <summary>
25-
/// Called just before EF intends to call <see cref="M:System.Data.Common.DbCommand.ExecuteReader" />.
26-
/// </summary>
27-
/// <param name="command">The command.</param>
28-
/// <param name="eventData">Contextual information about the command and execution.</param>
29-
/// <param name="result">
30-
/// Represents the current result if one exists.
31-
/// This value will have <see cref="P:Microsoft.EntityFrameworkCore.Diagnostics.InterceptionResult`1.HasResult" /> set to <see langword="true" /> if some previous
32-
/// interceptor suppressed execution by calling <see cref="M:Microsoft.EntityFrameworkCore.Diagnostics.InterceptionResult`1.SuppressWithResult(`0)" />.
33-
/// This value is typically used as the return value for the implementation of this method.
34-
/// </param>
35-
/// <returns>
36-
/// If <see cref="P:Microsoft.EntityFrameworkCore.Diagnostics.InterceptionResult`1.HasResult" /> is false, the EF will continue as normal.
37-
/// If <see cref="P:Microsoft.EntityFrameworkCore.Diagnostics.InterceptionResult`1.HasResult" /> is true, then EF will suppress the operation it
38-
/// was about to perform and use <see cref="P:Microsoft.EntityFrameworkCore.Diagnostics.InterceptionResult`1.Result" /> instead.
39-
/// An implementation of this method for any interceptor that is not attempting to change the result
40-
/// is to return the <paramref name="result" /> value passed in.
41-
/// </returns>
42-
public override InterceptionResult<DbDataReader> ReaderExecuting(
43-
DbCommand command,
44-
CommandEventData eventData,
45-
InterceptionResult<DbDataReader> result)
46-
{
47-
foreach (DbParameter param in command.Parameters)
48-
{
49-
var type = param.Value?.GetType();
50-
51-
if (type is not { IsGenericType: true })
52-
{
53-
continue;
54-
}
15+
var entityType =
16+
nodeType.IsGenericType && nodeType.GetGenericTypeDefinition() == typeof(IQueryable<>)
17+
? nodeType.GenericTypeArguments[0]
18+
: nodeType;
5519

56-
if (type.GetGenericTypeDefinition() == typeof(Option<>))
57-
{
58-
var enumerable = (IEnumerable)param.Value!;
59-
var enumerator = enumerable.GetEnumerator();
60-
var hasValue = enumerator.MoveNext();
20+
var exprVisitor = new OptionExpressionVisitor(entityType);
6121

62-
param.Value = hasValue ? enumerator.Current : DBNull.Value;
63-
}
64-
}
22+
var translatedExpression = exprVisitor.Visit(queryExpression)!;
6523

66-
return base.ReaderExecuting(command, eventData, result);
24+
return translatedExpression;
6725
}
6826
}

src/Codehard.Functional/Codehard.Functional.EntityFramework/Visitors/OptionExpressionVisitor.cs

Lines changed: 12 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
using System.Linq.Expressions;
1+
using System.Collections;
2+
using System.Linq.Expressions;
23
using LanguageExt;
3-
using LanguageExt.UnsafeValueAccess;
44
using Microsoft.EntityFrameworkCore;
55

66
namespace Codehard.Functional.EntityFramework.Visitors;
@@ -9,37 +9,11 @@ public class OptionExpressionVisitor : ExpressionVisitor
99
{
1010
private Type? entityType;
1111

12-
/// <summary>Dispatches the expression to one of the more specialized visit methods in this class.</summary>
13-
/// <param name="node">The expression to visit.</param>
14-
/// <returns>The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.</returns>
15-
public override Expression? Visit(Expression? node)
12+
public OptionExpressionVisitor(Type entityType)
1613
{
17-
if (this.entityType != null)
18-
{
19-
return base.Visit(node);
20-
}
21-
22-
var isGenericType = node?.Type.IsGenericType ?? false;
23-
var isQueryable = isGenericType && node?.Type.GetGenericTypeDefinition() == typeof(IQueryable<>);
24-
25-
if (!isQueryable)
26-
{
27-
return base.Visit(node);
28-
}
29-
30-
if (node != null)
31-
{
32-
var type = node.Type.GenericTypeArguments[0];
33-
34-
this.entityType = type;
35-
}
36-
37-
return base.Visit(node);
14+
this.entityType = entityType;
3815
}
3916

40-
/// <summary>Visits the children of the <see cref="T:System.Linq.Expressions.BinaryExpression" />.</summary>
41-
/// <param name="node">The expression to visit.</param>
42-
/// <returns>The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.</returns>
4317
protected override Expression VisitBinary(BinaryExpression node)
4418
{
4519
var isLeftOpt = IsOptionType(node.Left);
@@ -71,9 +45,6 @@ protected override Expression VisitBinary(BinaryExpression node)
7145
};
7246
}
7347

74-
/// <summary>Visits the children of the <see cref="T:System.Linq.Expressions.MethodCallExpression" />.</summary>
75-
/// <param name="node">The expression to visit.</param>
76-
/// <returns>The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.</returns>
7748
protected override Expression VisitMethodCall(MethodCallExpression node)
7849
{
7950
if (node.Method.ReflectedType == typeof(StringOptionDbFunctionsExtensions))
@@ -143,22 +114,11 @@ protected override Expression VisitConstant(ConstantExpression node)
143114
}
144115

145116
var genericType = node.Type.GenericTypeArguments[0];
117+
var enumerable = (IEnumerable)node.Value!;
118+
var enumerator = enumerable.GetEnumerator();
119+
var hasValue = enumerator.MoveNext();
146120

147-
object value = genericType switch
148-
{
149-
_ when genericType == typeof(bool) => ((Option<bool>)node.Value!).ValueUnsafe(),
150-
_ when genericType == typeof(int) => ((Option<int>)node.Value!).ValueUnsafe(),
151-
_ when genericType == typeof(float) => ((Option<float>)node.Value!).ValueUnsafe(),
152-
_ when genericType == typeof(double) => ((Option<double>)node.Value!).ValueUnsafe(),
153-
_ when genericType == typeof(decimal) => ((Option<decimal>)node.Value!).ValueUnsafe(),
154-
_ when genericType == typeof(Guid) => ((Option<Guid>)node.Value!).ValueUnsafe(),
155-
_ when genericType == typeof(string) => ((Option<string>)node.Value!).ValueUnsafe(),
156-
_ when genericType == typeof(DateTime) => ((Option<DateTime>)node.Value!).ValueUnsafe(),
157-
_ when genericType == typeof(DateTimeOffset) => ((Option<DateTimeOffset>)node.Value!).ValueUnsafe(),
158-
_ when genericType == typeof(DateOnly) => ((Option<DateOnly>)node.Value!).ValueUnsafe(),
159-
_ when genericType == typeof(TimeOnly) => ((Option<TimeOnly>)node.Value!).ValueUnsafe(),
160-
_ => throw new NotSupportedException($"Type '{genericType}' is not supported in this context."),
161-
};
121+
var value = hasValue ? enumerator.Current : null;
162122

163123
var type =
164124
genericType.IsPrimitive ? typeof(Nullable<>).MakeGenericType(genericType) : genericType;
@@ -200,19 +160,15 @@ protected override Expression VisitParameter(ParameterExpression node)
200160
return base.VisitParameter(node);
201161
}
202162

203-
var actualType = node.Type.GenericTypeArguments[0];
204-
var newType = actualType.IsPrimitive || actualType.IsValueType
205-
? typeof(Nullable<>).MakeGenericType(actualType)
206-
: actualType;
207-
208-
var newParam = Expression.Parameter(newType, node.Name);
163+
var genericType = node.Type.GenericTypeArguments[0];
164+
var type =
165+
genericType.IsPrimitive ? typeof(Nullable<>).MakeGenericType(genericType) : genericType;
209166

210-
return newParam;
167+
return Expression.Parameter(type, node.Name);
211168
}
212169

213170
private static ParameterExpression GetParameterExpression(Expression expression)
214171
{
215-
// This will need more intensive tests.
216172
return expression switch
217173
{
218174
ParameterExpression parameterExpression => parameterExpression,

0 commit comments

Comments
 (0)