ExpressionQueryTranslator

Ecng.Data.Sql

https://stackoverflow.com/questions/7731905/how-to-convert-an-expression-tree-to-a-partial-sql-query

継承元: ExpressionVisitor

コンストラクター

ExpressionQueryTranslator
public ExpressionQueryTranslator(Schema meta)
expressionQueryTranslator = ExpressionQueryTranslator(meta)

https://stackoverflow.com/questions/7731905/how-to-convert-an-expression-tree-to-a-partial-sql-query

メソッド

AnalyseSubqueryShape
private static void AnalyseSubqueryShape(Expression maExp, Type itemType, string alias)
ExpressionQueryTranslator.AnalyseSubqueryShape(maExp, itemType, alias)

Walks the outermost LINQ chain in to extract (a) the element type of the sub-query result and (b) the lambda parameter name of the deepest source. Both are needed to set up the sub-query Context before the visitor descends.

Capture
public Query Capture(Action visit)
result = expressionQueryTranslator.Capture(visit)

Renders what emits into a query of its own, for a dialect that has to place the operands of a function itself. Columns inside still get the pending column wrappers.

EmitMaterialisedValue
private void EmitMaterialisedValue(MemberInfo leafMember, object value)
expressionQueryTranslator.EmitMaterialisedValue(leafMember, value)

Emits a value reified from a constant chain by Object@) into the current query builder: scalars become parameters, arrays expand into a comma-separated parameter list, IQueryable values are re-visited so their expression tree contributes joins and parameters, and nulls collapse to NULL.

GetAlias
private string GetAlias(ParameterExpression parameter)
result = expressionQueryTranslator.GetAlias(parameter)

Lookup overload that resolves a ParameterExpression directly through Aliases when the parameter has been registered by the enclosing LINQ-method handler. The stored value is already the canonical alias and must NOT be piped through the string-based fallback — that would remap every alias to TableAlias and erase outer-scope references in sub-queries that inherit the parent's alias map. Falls back to the string-based String) when the parameter is not registered, so callers do not need to know whether registration happened.

IsAnyOverSubquery
private static bool IsAnyOverSubquery(Expression expr)
result = ExpressionQueryTranslator.IsAnyOverSubquery(expr)

True when is a correlated Any() over a sub-query. Used only in a VALUE position (a comparison/arithmetic operand), where the Any() result is a boolean value and must render as a scalar cast(case when exists(...) as bit) sub-query. In a predicate position (PredicatePosition) it stays a bare exists(...) and must NOT be wrapped.

IsProjectionSubqueryMember
private static bool IsProjectionSubqueryMember(Expression expr)
result = ExpressionQueryTranslator.IsProjectionSubqueryMember(expr)

Gate for a PROJECTION MEMBER (NewExpression)): any correlated sub-query (Expression)) or a conditional must be wrapped as a scalar sub-query, because a SELECT-list member has to be a self-contained scalar column. This is broader than Expression) on purpose: here even a boolean existence/set operator (Any/Contains) has to become a scalar (select case when exists(...) ...) column, so it is wrapped too.

IsScalarAggregateOverSubquery
private static bool IsScalarAggregateOverSubquery(Expression expr)
result = ExpressionQueryTranslator.IsScalarAggregateOverSubquery(expr)

True when is a scalar aggregate over a correlated sub-query (Max/Min/Sum/Average/Count/LongCount). These visitors (BaseGroupFuncVisitor / CountVisitor) produce a scalar value but do NOT establish their own sub-query Context — they recurse into the sub-query source in the CURRENT context — so they must be wrapped by Expression) wherever the aggregate yields a value (projection member, binary operand, conditional branch). The existence/set operators (Any/All/Contains) are deliberately excluded: their visitors emit self-contained EXISTS/IN and manage the current query themselves, so wrapping them re-enters their visitor with a fresh (empty) context and throws.

IsSubqueryProjectionArg
private static bool IsSubqueryProjectionArg(Expression expr)
result = ExpressionQueryTranslator.IsSubqueryProjectionArg(expr)

True when — the value of a projection member in a new { ... } / positional-ctor / record projection — is a genuine correlated sub-query that must be wrapped as a scalar sub-query. A real sub-query's LINQ operator chain bottoms out at an IQueryable source (a captured queryable / Query<T>()); a scalar helper such as Math.Round(Math.Abs(col)) bottoms out at a column, and a grouping aggregate (g.Count()) at an IGrouping parameter — neither is IQueryable. Only sub-queries are routed through Expression) in NewExpression); every other arg stays on the plain visit path so its emission is unchanged (a blanket route mis-fired on nested scalar calls and stripped Convert casts).

NeedsSubqueryContext
private static bool NeedsSubqueryContext(Expression expr)
result = ExpressionQueryTranslator.NeedsSubqueryContext(expr)

Gate for a binary OPERAND / conditional BRANCH (Expression)): only a scalar aggregate over a correlated sub-query (Expression)) or a conditional needs its own sub-context here. A conditional builds its CASE into the context's SwitchPart, and Schema) returns the SwitchPart verbatim when it is non-empty — so a conditional emitted into the OUTER context would hijack the whole query and discard its SELECT/FROM. Giving it a fresh sub-context turns the CASE into a self-contained scalar column. The existence/set operators (Any/All/Contains) are NOT wrapped here: as a boolean operand of &&/|| they must stay predicates (EXISTS/IN), and wrapping re-enters their visitor with a fresh (empty) context and throws.

ParseCompositeJoinKey
private static ValueTuple<Schema, string, ValueTuple<bool, object>[]> ParseCompositeJoinKey(Expression keyLambda)
result = ExpressionQueryTranslator.ParseCompositeJoinKey(keyLambda)

Parses a multi-column key-selector lambda (anonymous-type composite key as used by GroupJoin) into per-column participants. Constants and unary-wrapped constants pass through as raw (false, value) entries.

ParseScalarJoinKey
private static ValueTuple<Schema, string, string> ParseScalarJoinKey(Expression keyLambda)
result = ExpressionQueryTranslator.ParseScalarJoinKey(keyLambda)

Parses an inner/outer key-selector lambda into its Type) components, transparently stripping Convert wrappers and forwarding bare-parameter keys (p => p) to the implicit Id column.

PreregisterJoinAliases
private void PreregisterJoinAliases(Expression source)
expressionQueryTranslator.PreregisterJoinAliases(source)

Lightweight peek that walks the LINQ method chain rooted at looking for Queryable.Join calls and records the inner alias name in PreknownJoinAliases. Lets projection-time visitors that resolve transparent-id chains see the join alias before the recursive source visit registers the full join SQL — without pre-populating JoinParts itself, which would cause the real MethodCallExpression) to see a self-collision and rename `i` → `i1`.

RegisterJoinPlan
private void RegisterJoinPlan(JoinPlan jp)
expressionQueryTranslator.RegisterJoinPlan(jp)

Registers a JoinPlan in JoinParts idempotently — a join is added only once per alias.

RegisterLambdaParameters
private void RegisterLambdaParameters(LambdaExpression lambda, string alias)
expressionQueryTranslator.RegisterLambdaParameters(lambda, alias)

Registers all parameters of against the supplied . Used by Where/Select/Join/OrderBy lambdas to seed Aliases.

ResolveJoinMember
private static ValueTuple<Schema, string, string> ResolveJoinMember(MemberExpression member, Type fallbackOwner)
result = ExpressionQueryTranslator.ResolveJoinMember(member, fallbackOwner)

Resolves a single join-key MemberExpression into the schema, alias and physical column name that participate in the ON clause. Shared by both Join and GroupJoin. The walk handles three layered cases together: Bare member access (x.Foo).FK-via-Id (x.Person.Id) — the leaf .Id is folded into the FK column name on the parent side.Transparent identifier (tdx.t.Foo) emitted by the compiler for chained joins — the join target is recovered from the inner-most non-anonymous declaring type.

ResolveOrderByColumn
private OrderByEntry ResolveOrderByColumn(Expression body, string rootAlias, bool asc)
result = expressionQueryTranslator.ResolveOrderByColumn(body, rootAlias, asc)

Resolves an ORDER BY MemberExpression into a column reference for OrderBy. Uses the join-aware MemberPathResolver when the chain rooted at a parameter can be classified, otherwise falls back to an unqualified bare column name (the legacy behaviour for projection / grouping outputs).

ResolveTransIdAlias
private string ResolveTransIdAlias(MemberExpression chain)
result = expressionQueryTranslator.ResolveTransIdAlias(chain)

Walks the transparent-identifier chain produced by C#'s desugaring of nested LINQ from/join/select clauses and returns the SQL alias the leaf .Id refers to. Each member name is checked against the registered JoinParts; if none matches, the chain's root ParameterExpression is resolved through ParameterExpression), which falls back to the main FROM alias.

TryEmitFromResolver
private bool TryEmitFromResolver(MemberExpression m)
result = expressionQueryTranslator.TryEmitFromResolver(m)

Tries to resolve a member chain through the pure MemberPathResolver, register the implicit joins it reports, and emit the qualified leaf column. Returns false when the resolver cannot recognise the chain shape so the caller can fall back to the legacy resolution branches.

UnwrapConvert
private static Expression UnwrapConvert(Expression e)
result = ExpressionQueryTranslator.UnwrapConvert(e)

Strips a single Convert/ConvertChecked wrapper — the compiler inserts one around either arm of ?? when widening a non-nullable side to the nullable result type.

VisitDecimalComparisonOperand
public void VisitDecimalComparisonOperand(Expression expression)
expressionQueryTranslator.VisitDecimalComparisonOperand(expression)

Visits one side of a decimal comparison. Where decimals are stored as text, the whole side is cast: a bound decimal parameter is text too, and arithmetic, a function or a sub-query yields a value without numeric affinity, so a cast on the columns inside it would leave the comparison textual. A literal is already a number.

VisitOperand
public void VisitOperand(Expression expr)
expressionQueryTranslator.VisitOperand(expr)

Visits a sub-expression that may itself need its own sub-context — a binary operand ((from t ...).Count() + 1, (...).Max() ?? 0) or a conditional branch (cond ? (...).Max() : 0). A correlated sub-query or conditional is wrapped via Expression) so it keeps its own FROM/alias / CASE scope; every other operand is visited as before. Without this a nested aggregate flattened into the outer statement — the correlation degenerated to [e].[Fk] = [e].[Id] and the inner FROM was lost — producing malformed or silently wrong SQL.

VisitWrapped
public void VisitWrapped(Action<bool, Query> wrap, Action visit)
expressionQueryTranslator.VisitWrapped(wrap, visit)

Runs with written around every column it emits. Wrappers nest, so afterwards exactly this one is removed, not the oldest in the queue.