forked from nhibernate/nhibernate-core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathNhLinqExpression.cs
140 lines (111 loc) · 5.48 KB
/
NhLinqExpression.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using NHibernate.Engine;
using NHibernate.Engine.Query;
using NHibernate.Hql.Ast.ANTLR.Tree;
using NHibernate.Linq.Visitors;
using NHibernate.Param;
using NHibernate.Type;
namespace NHibernate.Linq
{
public class NhLinqExpression : IQueryExpression, ICacheableQueryExpression
{
public string Key { get; protected set; }
public bool CanCachePlan { get; private set; } = true;
public System.Type Type { get; private set; }
/// <summary>
/// Entity type to insert or update when the expression is a DML.
/// </summary>
protected virtual System.Type TargetType => Type;
public IList<NamedParameterDescriptor> ParameterDescriptors { get; private set; }
public NhLinqExpressionReturnType ReturnType { get; }
public IDictionary<string, Tuple<object, IType>> ParameterValuesByName { get; }
public ExpressionToHqlTranslationResults ExpressionToHqlTranslationResults { get; private set; }
protected virtual QueryMode QueryMode { get; }
internal IDictionary<string, NamedParameter> NamedParameters { get; }
private readonly Expression _expression;
private readonly IDictionary<ConstantExpression, NamedParameter> _constantToParameterMap;
public NhLinqExpression(Expression expression, ISessionFactoryImplementor sessionFactory)
: this(QueryMode.Select, expression, sessionFactory)
{
}
internal NhLinqExpression(QueryMode queryMode, Expression expression, ISessionFactoryImplementor sessionFactory)
{
QueryMode = queryMode;
var preTransformResult = NhRelinqQueryParser.PreTransform(
expression,
new PreTransformationParameters(queryMode, sessionFactory));
_expression = preTransformResult.Expression;
// We want logging to be as close as possible to the original expression sent from the
// application. But if we log before partial evaluation done in PreTransform, the log won't
// include e.g. sub-query expressions if those are defined by the application in a variable
// referenced from the main query.
LinqLogging.LogExpression("Expression (partially evaluated)", _expression);
_constantToParameterMap = ExpressionParameterVisitor.Visit(preTransformResult);
ParameterValuesByName = _constantToParameterMap.Values.Distinct().ToDictionary(p => p.Name,
p => System.Tuple.Create(p.Value, p.Type));
NamedParameters = _constantToParameterMap.Values.Distinct().ToDictionary(p => p.Name);
Key = ExpressionKeyVisitor.Visit(_expression, _constantToParameterMap, sessionFactory);
Type = _expression.Type;
// Note - re-linq handles return types via the GetOutputDataInfo method, and allows for SingleOrDefault here for the ChoiceResultOperator...
ReturnType = NhLinqExpressionReturnType.Scalar;
if (typeof(IQueryable).IsAssignableFrom(Type))
{
Type = Type.GetGenericArguments()[0];
ReturnType = NhLinqExpressionReturnType.Sequence;
}
}
public IASTNode Translate(ISessionFactoryImplementor sessionFactory, bool filter)
{
if (ExpressionToHqlTranslationResults != null)
{
// Query has already been translated. Arguments do not really matter, because queries are anyway tied
// to a single session factory and cannot switch from being a filter query (query on a mapped collection)
// or not.
return DuplicateTree(ExpressionToHqlTranslationResults.Statement.AstNode);
}
var requiredHqlParameters = new List<NamedParameterDescriptor>();
var queryModel = NhRelinqQueryParser.Parse(_expression);
queryModel.TransformExpressions(TransparentIdentifierRemovingExpressionVisitor.ReplaceTransparentIdentifiers);
ParameterTypeLocator.SetParameterTypes(_constantToParameterMap, queryModel, TargetType, sessionFactory, true);
var visitorParameters = new VisitorParameters(sessionFactory, _constantToParameterMap, requiredHqlParameters,
new QuerySourceNamer(), TargetType, QueryMode);
ExpressionToHqlTranslationResults = QueryModelVisitor.GenerateHqlQuery(queryModel, visitorParameters, true, ReturnType);
if (ExpressionToHqlTranslationResults.ExecuteResultTypeOverride != null)
Type = ExpressionToHqlTranslationResults.ExecuteResultTypeOverride;
ParameterDescriptors = requiredHqlParameters.AsReadOnly();
CanCachePlan = CanCachePlan && visitorParameters.CanCachePlan &&
// If some constants do not have matching HQL parameters, their values from first query will
// be embedded in the plan and reused for subsequent queries: do not cache the plan.
!ParameterValuesByName
.Keys
.Except(requiredHqlParameters.Select(p => p.Name))
.Any();
// The ast node may be altered by caller, duplicate it for preserving the original one.
return DuplicateTree(ExpressionToHqlTranslationResults.Statement.AstNode);
}
internal void CopyExpressionTranslation(NhLinqExpressionCache cache)
{
ExpressionToHqlTranslationResults = cache.ExpressionToHqlTranslationResults;
ParameterDescriptors = cache.ParameterDescriptors;
// Type could have been overridden by translation.
Type = cache.Type;
}
internal IDictionary<string, Tuple<IType, bool>> GetNamedParameterTypes()
{
return _constantToParameterMap.Values.Distinct()
.ToDictionary(p => p.Name, p => System.Tuple.Create(p.Type, p.IsGuessedType));
}
private static IASTNode DuplicateTree(IASTNode ast)
{
var thisNode = ast.DupNode();
foreach (var child in ast)
{
thisNode.AddChild(DuplicateTree(child));
}
return thisNode;
}
}
}