-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature/enumerable count support (#33)
* feat: added support of count call on related entity Co-authored-by: Ilya Belyanskiy <win7user20@gmail.com>
- Loading branch information
1 parent
57f0b42
commit 44c9a38
Showing
51 changed files
with
687 additions
and
135 deletions.
There are no files selected for viewing
125 changes: 125 additions & 0 deletions
125
src/Laraue.EfCoreTriggers.Common/Converters/MethodCall/Enumerable/BaseEnumerableVisitor.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Linq.Expressions; | ||
using Laraue.EfCoreTriggers.Common.Services; | ||
using Laraue.EfCoreTriggers.Common.Services.Impl.ExpressionVisitors; | ||
using Laraue.EfCoreTriggers.Common.SqlGeneration; | ||
using Laraue.EfCoreTriggers.Common.TriggerBuilders; | ||
|
||
namespace Laraue.EfCoreTriggers.Common.Converters.MethodCall.Enumerable | ||
{ | ||
/// <summary> | ||
/// Base visitor for <see cref="IEnumerable{T}"/> extensions. | ||
/// </summary> | ||
public abstract class BaseEnumerableVisitor : BaseMethodCallVisitor | ||
{ | ||
/// <inheritdoc /> | ||
protected override Type ReflectedType => typeof(System.Linq.Enumerable); | ||
|
||
private readonly IDbSchemaRetriever _schemaRetriever; | ||
private readonly ISqlGenerator _sqlGenerator; | ||
private readonly IExpressionVisitorFactory _expressionVisitorFactory; | ||
|
||
/// <inheritdoc /> | ||
protected BaseEnumerableVisitor( | ||
IExpressionVisitorFactory visitorFactory, | ||
IDbSchemaRetriever schemaRetriever, | ||
ISqlGenerator sqlGenerator) | ||
: base(visitorFactory) | ||
{ | ||
_schemaRetriever = schemaRetriever; | ||
_sqlGenerator = sqlGenerator; | ||
_expressionVisitorFactory = visitorFactory; | ||
} | ||
|
||
public override SqlBuilder Visit(MethodCallExpression expression, ArgumentTypes argumentTypes, VisitedMembers visitedMembers) | ||
{ | ||
var whereExpressions = new HashSet<Expression>(); | ||
var exp = GetFlattenExpressions(expression, whereExpressions); | ||
|
||
var baseMember = exp as MemberExpression; | ||
var enumerableMemberType = baseMember.Type; | ||
|
||
var originalSetMemberExpression = (ParameterExpression) baseMember.Expression; | ||
var originalSetType = originalSetMemberExpression?.Type; | ||
|
||
if (!typeof(IEnumerable).IsAssignableFrom(enumerableMemberType) || !enumerableMemberType.IsGenericType) | ||
{ | ||
throw new NotSupportedException($"Don't know how to translate expression {expression}"); | ||
} | ||
|
||
var entityType = baseMember.Type.GetGenericArguments()[0]; | ||
|
||
var otherArguments = expression.Arguments | ||
.Skip(1) | ||
.ToArray(); | ||
|
||
var selectSql = Visit(otherArguments, argumentTypes, visitedMembers); | ||
|
||
if (selectSql.Item2 is not null) | ||
{ | ||
whereExpressions.Add(selectSql.Item2); | ||
} | ||
|
||
var finalSql = SqlBuilder.FromString("("); | ||
|
||
finalSql.WithIdent(x=> x | ||
.Append("SELECT ") | ||
.Append(selectSql.Item1) | ||
.AppendNewLine($"FROM {_schemaRetriever.GetTableName(entityType)}") | ||
.AppendNewLine($"INNER JOIN {_schemaRetriever.GetTableName(originalSetType)} ON ")); | ||
|
||
var keys = _schemaRetriever.GetForeignKeyMembers(entityType, originalSetType); | ||
|
||
var joinParts = new List<string>(); | ||
foreach (var key in keys) | ||
{ | ||
var column1Sql = _sqlGenerator.GetColumnSql(entityType, key.ForeignKey, ArgumentType.Default); | ||
|
||
var argument2Type = argumentTypes.Get(originalSetMemberExpression); | ||
|
||
var column2WhereSql = _sqlGenerator.GetVariableSql(originalSetType, key.PrincipalKey, argument2Type); | ||
visitedMembers.AddMember(argument2Type, key.PrincipalKey); | ||
|
||
var column2JoinSql = _sqlGenerator.GetColumnSql(originalSetType, key.PrincipalKey, ArgumentType.Default); | ||
|
||
joinParts.Add($"{column1Sql} = {column2JoinSql}"); | ||
joinParts.Add($"{column1Sql} = {column2WhereSql}"); | ||
} | ||
|
||
foreach (var e in whereExpressions) | ||
{ | ||
joinParts.Add(_expressionVisitorFactory.Visit(e, argumentTypes, visitedMembers)); | ||
} | ||
|
||
finalSql.AppendJoin(" AND ", joinParts); | ||
|
||
finalSql.Append(")"); | ||
|
||
return finalSql; | ||
} | ||
|
||
private Expression GetFlattenExpressions(MethodCallExpression methodCallExpression, HashSet<Expression> whereExpressions) | ||
{ | ||
while (true) | ||
{ | ||
if (methodCallExpression.Arguments[0] is not MethodCallExpression childCall || | ||
childCall.Method.Name != nameof(System.Linq.Enumerable.Where)) | ||
{ | ||
return methodCallExpression.Arguments[0]; | ||
} | ||
|
||
whereExpressions.Add(childCall.Arguments[1]); | ||
|
||
methodCallExpression = childCall; | ||
} | ||
} | ||
|
||
protected abstract (SqlBuilder, Expression) Visit( | ||
Expression[] arguments, | ||
ArgumentTypes argumentTypes, | ||
VisitedMembers visitedMembers); | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
src/Laraue.EfCoreTriggers.Common/Converters/MethodCall/Enumerable/Count/CountVisitor.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Linq.Expressions; | ||
using Laraue.EfCoreTriggers.Common.Services; | ||
using Laraue.EfCoreTriggers.Common.Services.Impl.ExpressionVisitors; | ||
using Laraue.EfCoreTriggers.Common.SqlGeneration; | ||
using Laraue.EfCoreTriggers.Common.TriggerBuilders; | ||
|
||
namespace Laraue.EfCoreTriggers.Common.Converters.MethodCall.Enumerable.Count; | ||
|
||
public class CountVisitor : BaseEnumerableVisitor | ||
{ | ||
protected override string MethodName => nameof(System.Linq.Enumerable.Count); | ||
|
||
private readonly IExpressionVisitorFactory _expressionVisitorFactory; | ||
|
||
public CountVisitor( | ||
IExpressionVisitorFactory visitorFactory, | ||
IDbSchemaRetriever schemaRetriever, | ||
ISqlGenerator sqlGenerator) | ||
: base(visitorFactory, schemaRetriever, sqlGenerator) | ||
{ | ||
_expressionVisitorFactory = visitorFactory; | ||
} | ||
|
||
protected override (SqlBuilder, Expression) Visit(Expression[] arguments, ArgumentTypes argumentTypes, VisitedMembers visitedMembers) | ||
{ | ||
return (SqlBuilder.FromString("count(*)"), arguments.FirstOrDefault()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
src/Laraue.EfCoreTriggers.Common/Services/Impl/ExpressionVisitors/LambdaExpressionVisitor.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
using System; | ||
using System.Linq.Expressions; | ||
using Laraue.EfCoreTriggers.Common.Services.Impl.SetExpressionVisitors; | ||
using Laraue.EfCoreTriggers.Common.SqlGeneration; | ||
using Laraue.EfCoreTriggers.Common.TriggerBuilders; | ||
|
||
namespace Laraue.EfCoreTriggers.Common.Services.Impl.ExpressionVisitors; | ||
|
||
public class LambdaExpressionVisitor : BaseExpressionVisitor<LambdaExpression> | ||
{ | ||
private readonly IExpressionVisitorFactory _factory; | ||
|
||
public LambdaExpressionVisitor(IExpressionVisitorFactory factory) | ||
{ | ||
_factory = factory; | ||
} | ||
|
||
public override SqlBuilder Visit(LambdaExpression expression, ArgumentTypes argumentTypes, VisitedMembers visitedMembers) | ||
{ | ||
return _factory.Visit(expression.Body, argumentTypes, visitedMembers); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.