Datei: NDODLL/SqlPersistenceHandling/SqlQueryGenerator.cs

Last Commit (b95968d)
1 using Microsoft.Extensions.DependencyInjection;
2 using NDO.Mapping;
3 using NDO.Query;
4 using NDOql.Expressions;
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Text;
9 using System.Text.RegularExpressions;
10
11 namespace NDO.SqlPersistenceHandling
12 {
13 ····class SqlQueryGenerator : IQueryGenerator
14 ····{
15 ········private readonly IMappingsAccessor mappingsAccessor;
16 ········private List<QueryInfo> subQueries = new List<QueryInfo>();
17 ········private Func<Dictionary<Relation, Class>, bool, Class, bool, object, string> selectPartCreator;
18 ········private object additionalSelectPartData = null;
19 ········private Mappings mappings;
20
21 ········public SqlQueryGenerator( IMappingsAccessor mappingsAccessor )
22 ········{
23 ············this.mappingsAccessor = mappingsAccessor;
24 ············this.mappings = mappingsAccessor.Mappings;
25 ········}
26
27 ········/// <summary>
28 ········/// Creates a Sql query string for the complete query over all types.
29 ········/// </summary>
30 ········/// <param name="queryContextsList">List of all contexts which define possible mutations of concrete classes in results and relations.</param>
31 ········/// <param name="expressionTree">The syntax tree of the NDOql query expression.</param>
32 ········/// <param name="hollow">Determines, if the query results should be hollow objects.</param>
33 ········/// <param name="orderings">List of orderings for the resultset.</param>
34 ········/// <param name="skip">Determines how many records of the resultset should be skipped. The resultset must be ordered.</param>
35 ········/// <param name="take">Determines how many records of the resultset should be returned by the query. The resultset must be ordered.</param>
36 ········/// <param name="prefetch">Query for the given prefetch relation.</param>
37 ········/// <returns>A query string.</returns>
38 ········/// <remarks>The result can be used for debugging and display purposes or with handlers, which don't support distributed databases.</remarks>
39 ········public string GenerateQueryStringForAllTypes(
40 ············List<QueryContextsEntry> queryContextsList,
41 ············OqlExpression expressionTree,
42 ············bool hollow,
43 ············List<QueryOrder> orderings,
44 ············int skip,
45 ············int take, string prefetch = null )
46 ········{
47 ············this.selectPartCreator = CreateQuerySelectPart;
48
49 ············CreateSubQueriesForAllTypes(queryContextsList, expressionTree, hollow, orderings, skip, take );
50 ············StringBuilder generatedQuery = new StringBuilder();
51
52 ············foreach (QueryInfo qi in subQueries)
53 ············{
54 ················generatedQuery.Append(qi.QueryString);
55 ················generatedQuery.Append(";\r\n");
56 ············}
57
58 ············generatedQuery.Length -= 3; // the trailing ;\r\n
59
60 ············return generatedQuery.ToString();
61 ········}
62
63 ········/// <summary>
64 ········/// Creates a SQL query string, which can be passed to the IPersistenceHandler to fetch the results for a given concrete type.
65 ········/// </summary>
66 ········/// <param name="queryContextsEntry">All contexts which define possible mutations of concrete classes in results and relations.</param>
67 ········/// <param name="expressionTree">The syntax tree of the NDOql query expression.</param>
68 ········/// <param name="hollow">Determines, if the query results should be hollow objects.</param>
69 ········/// <param name="hasSubclassResultsets">Determines, if this query is part of a composed query which spans over several tables.</param>
70 ········/// <param name="orderings">List of orderings for the resultset.</param>
71 ········/// <param name="skip">Determines how many records of the resultset should be skipped. The resultset must be ordered.</param>
72 ········/// <param name="take">Determines how many records of the resultset should be returned by the query. The resultset must be ordered.</param>
73 ········/// <param name="prefetch">Query for the given prefetch relation.</param>
74 ········/// <returns>A Query string.</returns>
75 ········public string GenerateQueryString(
76 ············QueryContextsEntry queryContextsEntry,
77 ············OqlExpression expressionTree,
78 ············bool hollow,
79 ············bool hasSubclassResultsets,
80 ············List<QueryOrder> orderings,
81 ············int skip,
82 ············int take,
83 ············string prefetch )
84 ········{
85 ············this.selectPartCreator = CreateQuerySelectPart;
86
87 ············return InnerGenerateQueryString( queryContextsEntry, expressionTree, hollow, hasSubclassResultsets, orderings, skip, take, prefetch );
88 ········}
89
90 ········private string InnerGenerateQueryString( QueryContextsEntry queryContextsEntry, OqlExpression expressionTree, bool hollow, bool hasSubclassResultsets, List<QueryOrder> orderings, int skip, int take, string prefetch )
91 ········{
92 ············CreateSubQueries( queryContextsEntry, expressionTree, hollow, hasSubclassResultsets, orderings, skip, take );
93 ············StringBuilder generatedQuery = new StringBuilder();
94
95 ············foreach (QueryInfo qi in subQueries)
96 ············{
97 ················generatedQuery.Append( qi.QueryString );
98 ················generatedQuery.Append( ";\n" );
99 ············}
100
101 ············generatedQuery.Length--; // the trailing \n
102
103 ············if (subQueries.Count == 1)
104 ················generatedQuery.Length--; // the semicolon
105
106 ············return generatedQuery.ToString();
107 ········}
108
109 ········void CreateSubQueriesForAllTypes(
110 ············List<QueryContextsEntry> queryContextsList,
111 ············OqlExpression expressionTree,
112 ············bool hollow,
113 ············List<QueryOrder> orderings,
114 ············int skip,
115 ············int take,
116 ············string prefetch = null )
117 ········{
118 ············foreach (var item in queryContextsList)
119 ············{
120 ················CreateSubQueries( item, expressionTree, hollow, queryContextsList.Count > 1, orderings, skip, take );
121 ············}
122 ········}
123
124 ········void CreateSubQueries( QueryContextsEntry queryContextsEntry,
125 ············OqlExpression expressionTree,
126 ············bool hollow,
127 ············bool hasSubclassResultsets,
128 ············List<QueryOrder> orderings,
129 ············int skip,
130 ············int take )
131 ········{
132 ············Type t = queryContextsEntry.Type;
133 ············var queryContexts = queryContextsEntry.QueryContexts;
134
135 ············if (queryContexts.Count == 0)
136 ············{
137 ················this.subQueries.Add( new QueryInfo( t, ConstructQueryString( t, new Dictionary<Relation, Class>(), expressionTree, hollow, hasSubclassResultsets, orderings, skip, take ) ) );
138 ············}
139 ············else
140 ············{
141 ················string queryString = string.Empty;
142 ················int added = 0;
143 ················foreach (var queryContext in queryContexts)
144 ················{
145 ····················if (!queryContext.Any( kvp => kvp.Value.SystemType == t ))
146 ····················{
147 ························if (added > 0)
148 ····························queryString += " UNION \r\n";
149 ························queryString += ConstructQueryString( t, queryContext, expressionTree, hollow, hasSubclassResultsets, orderings, skip, take );
150 ························added++;
151 ····················}
152 ················}
153 ················this.subQueries.Add( new QueryInfo( t, queryString ) );
154 ············}
155 ········}
156
157 ········string ConstructQueryString(
158 ············Type resultType,
159 ············Dictionary<Relation, Class> relationContext,
160 ············OqlExpression expressionTree,
161 ············bool hollow,
162 ············bool hasSubclassResultsets,
163 ············List<QueryOrder> orderings,
164 ············int skip,
165 ············int take )
166 ········{
167 ············Class cls = this.mappings.FindClass( resultType );
168
169 ············StringBuilder sb = new StringBuilder( "SELECT " );
170
171 ············var from = new FromGenerator( cls, relationContext ).GenerateFromExpression( expressionTree );
172 ············var qualifyWithTableName = from.IndexOf( "INNER JOIN" ) > -1;
173
174 ············string columnList = this.selectPartCreator( relationContext, hollow, cls, qualifyWithTableName, this.additionalSelectPartData );
175 ············sb.Append( columnList );
176
177 ············// If we need to sort a set of hollow results for different subclasses
178 ············// we need to fetch the ordering columns together with the oid columns
179 ············if (hollow && orderings.Count > 0 && hasSubclassResultsets)
180 ············{
181 ················var orderCols = (from o in orderings select cls.FindField( o.FieldName ).Column.GetQualifiedName()).ToArray();
182 ················columnList += ", " + String.Join( ", ", orderCols );
183 ············}
184
185 ············sb.Append( ' ' );
186 ············sb.Append( from );
187 ············string where = new WhereGenerator( cls, relationContext ).GenerateWhereClause( expressionTree );
188 ············if (where != string.Empty)
189 ············{
190 ················sb.Append( ' ' );
191 ················sb.Append( where );
192 ············}
193
194 ············if (orderings.Count > 0)
195 ············{
196 ················sb.Append( ' ' );
197 ················sb.Append( new OrderGenerator( cls ).GenerateOrderClause( orderings, skip, take ) );
198 ············}
199
200 ············return sb.ToString();
201 ········}
202
203 ········private string CreateQuerySelectPart( Dictionary<Relation, Class> relationContext, bool hollow, Class cls, bool qualifyWithTableName, object additionalData )
204 ········{
205 ············var generator = SqlColumnListGenerator.Get( cls );
206
207 ············// We have to hack around a special behavior of SQLite, generating
208 ············// new columns with fully specified column names, if the query
209 ············// is a UNION············
210 ············var generateAliasNames = relationContext.Count > 0 && cls.Provider.GetType().FullName.IndexOf( "Sqlite" ) > -1;
211
212 ············return generator.Result( hollow, generateAliasNames, qualifyWithTableName );
213 ········}
214
215 ········private string CreateAggregateSelectPart( Dictionary<Relation, Class> relationContext, bool hollow, Class cls, bool qualifyWithTableName, object additionalData )
216 ········{
217 ············var tuple = (Tuple<string, AggregateType>)additionalData;
218 ············string field = tuple.Item1;
219 ············AggregateType aggregateType = tuple.Item2;
220 ············bool isStar = field == "*";
221
222 ············Column column = null;
223 ············if (field.ToLower().StartsWith( "oid" ))
224 ············{
225 ················Regex regex = new Regex( @"\(\s*(\d+)\s*\)" );
226 ················Match match = regex.Match( field );
227 ················int index = 0;
228 ················if (match.Success)
229 ····················index = int.Parse( match.Groups[1].Value );
230 ················column = ((OidColumn)cls.Oid.OidColumns[index]);
231 ············}
232 ············else
233 ············{
234 ················if (!isStar)
235 ····················column = cls.FindField( field ).Column;
236 ············}
237
238 ············var provider = cls.Provider;
239 ············var colName = isStar ? "*" : column.GetQualifiedName();
240
241 ············return $"{aggregateType.ToString().ToUpper()}({colName}) AS {provider.GetQuotedName( "AggrResult" )}";
242 ········}
243
244 ········public string GenerateAggregateQueryString( string field, QueryContextsEntry queryContextsEntry, OqlExpression expressionTree, bool hasSubclassResultsets, AggregateType aggregateType )
245 ········{
246 ············this.selectPartCreator = CreateAggregateSelectPart;
247 ············this.additionalSelectPartData = new Tuple<string,AggregateType>( field, aggregateType );
248
249 ············var query = InnerGenerateQueryString( queryContextsEntry, expressionTree, true, hasSubclassResultsets, new List<QueryOrder>(), 0, 0, null );
250 ············return query;
251 ········}
252
253 ········public string GeneratePrefetchQuery( Type parentType, IEnumerable<IPersistenceCapable> parents, string prefetch )
254 ········{
255 ············Class parentCls = this.mappings.FindClass( parentType );
256
257 ············StringBuilder sb = new StringBuilder( "SELECT " );
258 ············List<Relation> relations = new List<Relation>();
259
260 ············if (prefetch.IndexOf('.') == -1)
261 ············{
262 ················var rel = parentCls.FindRelation( prefetch );
263 ················if (rel != null)
264 ····················relations.Add( rel );
265 ············}
266
267 ············new RelationContextGenerator( this.mappingsAccessor ).CreateContextForName( parentCls, prefetch, relations );
268
269 ············if (relations.Count == 0)
270 ················throw new NDOException( 76, $"Prefetch: Can't find relation mapping with name {prefetch} in class {parentCls.FullName}" );
271
272 ············Class cls = mappings.FindClass( relations[relations.Count - 1].ReferencedTypeName );
273 ············var relationContext = new Dictionary<Relation, Class>();
274 #warning Note: This code is not complete. The INNER JOIN is missing here.
275 ············string columnList = CreateQuerySelectPart( relationContext, false, cls, true, null );
276 ············sb.Append( columnList );
277
278 ············sb.Append( ' ' );
279 ············sb.Append( new FromGenerator( cls, relationContext ).GenerateFromExpression( null, relations ) );
280 ············// We can be sure, that we have only one oid column here.
281 ············var oidList = String.Join( ",", from p in parents select p.NDOObjectId.Id.Values[0].ToString() );
282 ············string where = $"WHERE {parentCls.Oid.OidColumns.First().GetQualifiedName()} IN ({oidList})";
283 ············sb.Append( ' ' );
284 ············sb.Append( where );
285
286 ············return sb.ToString();
287 ········}
288 ····}
289 }
290
New Commit (42d0ae7)
1 using Microsoft.Extensions.DependencyInjection;
2 using NDO.Mapping;
3 using NDO.Query;
4 using NDOql.Expressions;
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Text;
9 using System.Text.RegularExpressions;
10
11 namespace NDO.SqlPersistenceHandling
12 {
13 ····class SqlQueryGenerator : IQueryGenerator
14 ····{
15 ········private readonly IMappingsAccessor mappingsAccessor;
16 ········private List<QueryInfo> subQueries = new List<QueryInfo>();
17 ········private Func<Dictionary<Relation, Class>, bool, Class, bool, object, string> selectPartCreator;
18 ········private object additionalSelectPartData = null;
19 ········private Mappings mappings;
20
21 ········public SqlQueryGenerator( IMappingsAccessor mappingsAccessor )
22 ········{
23 ············this.mappingsAccessor = mappingsAccessor;
24 ············this.mappings = mappingsAccessor.Mappings;
25 ········}
26
27 ········/// <summary>
28 ········/// Creates a Sql query string for the complete query over all types.
29 ········/// </summary>
30 ········/// <param name="queryContextsList">List of all contexts which define possible mutations of concrete classes in results and relations.</param>
31 ········/// <param name="expressionTree">The syntax tree of the NDOql query expression.</param>
32 ········/// <param name="hollow">Determines, if the query results should be hollow objects.</param>
33 ········/// <param name="orderings">List of orderings for the resultset.</param>
34 ········/// <param name="skip">Determines how many records of the resultset should be skipped. The resultset must be ordered.</param>
35 ········/// <param name="take">Determines how many records of the resultset should be returned by the query. The resultset must be ordered.</param>
36 ········/// <param name="prefetch">Query for the given prefetch relation.</param>
37 ········/// <returns>A query string.</returns>
38 ········/// <remarks>The result can be used for debugging and display purposes or with handlers, which don't support distributed databases.</remarks>
39 ········public string GenerateQueryStringForAllTypes(
40 ············List<QueryContextsEntry> queryContextsList,
41 ············OqlExpression expressionTree,
42 ············bool hollow,
43 ············List<QueryOrder> orderings,
44 ············int skip,
45 ············int take, string prefetch = null )
46 ········{
47 ············this.selectPartCreator = CreateQuerySelectPart;
48
49 ············CreateSubQueriesForAllTypes(queryContextsList, expressionTree, hollow, orderings, skip, take );
50 ············StringBuilder generatedQuery = new StringBuilder();
51
52 ············foreach (QueryInfo qi in subQueries)
53 ············{
54 ················generatedQuery.Append(qi.QueryString);
55 ················generatedQuery.Append(";\r\n");
56 ············}
57
58 ············generatedQuery.Length -= 3; // the trailing ;\r\n
59
60 ············return generatedQuery.ToString();
61 ········}
62
63 ········/// <summary>
64 ········/// Creates a SQL query string, which can be passed to the IPersistenceHandler to fetch the results for a given concrete type.
65 ········/// </summary>
66 ········/// <param name="queryContextsEntry">All contexts which define possible mutations of concrete classes in results and relations.</param>
67 ········/// <param name="expressionTree">The syntax tree of the NDOql query expression.</param>
68 ········/// <param name="hollow">Determines, if the query results should be hollow objects.</param>
69 ········/// <param name="hasSubclassResultsets">Determines, if this query is part of a composed query which spans over several tables.</param>
70 ········/// <param name="orderings">List of orderings for the resultset.</param>
71 ········/// <param name="skip">Determines how many records of the resultset should be skipped. The resultset must be ordered.</param>
72 ········/// <param name="take">Determines how many records of the resultset should be returned by the query. The resultset must be ordered.</param>
73 ········/// <param name="prefetch">Query for the given prefetch relation.</param>
74 ········/// <returns>A Query string.</returns>
75 ········public string GenerateQueryString(
76 ············QueryContextsEntry queryContextsEntry,
77 ············OqlExpression expressionTree,
78 ············bool hollow,
79 ············bool hasSubclassResultsets,
80 ············List<QueryOrder> orderings,
81 ············int skip,
82 ············int take,
83 ············string prefetch )
84 ········{
85 ············this.selectPartCreator = CreateQuerySelectPart;
86
87 ············return InnerGenerateQueryString( queryContextsEntry, expressionTree, hollow, hasSubclassResultsets, orderings, skip, take, prefetch );
88 ········}
89
90 ········private string InnerGenerateQueryString( QueryContextsEntry queryContextsEntry, OqlExpression expressionTree, bool hollow, bool hasSubclassResultsets, List<QueryOrder> orderings, int skip, int take, string prefetch )
91 ········{
92 ············CreateSubQueries( queryContextsEntry, expressionTree, hollow, hasSubclassResultsets, orderings, skip, take );
93 ············StringBuilder generatedQuery = new StringBuilder();
94
95 ············foreach (QueryInfo qi in subQueries)
96 ············{
97 ················generatedQuery.Append( qi.QueryString );
98 ················generatedQuery.Append( ";\n" );
99 ············}
100
101 ············generatedQuery.Length--; // the trailing \n
102
103 ············if (subQueries.Count == 1)
104 ················generatedQuery.Length--; // the semicolon
105
106 ············return generatedQuery.ToString();
107 ········}
108
109 ········void CreateSubQueriesForAllTypes(
110 ············List<QueryContextsEntry> queryContextsList,
111 ············OqlExpression expressionTree,
112 ············bool hollow,
113 ············List<QueryOrder> orderings,
114 ············int skip,
115 ············int take,
116 ············string prefetch = null )
117 ········{
118 ············foreach (var item in queryContextsList)
119 ············{
120 ················CreateSubQueries( item, expressionTree, hollow, queryContextsList.Count > 1, orderings, skip, take );
121 ············}
122 ········}
123
124 ········void CreateSubQueries( QueryContextsEntry queryContextsEntry,
125 ············OqlExpression expressionTree,
126 ············bool hollow,
127 ············bool hasSubclassResultsets,
128 ············List<QueryOrder> orderings,
129 ············int skip,
130 ············int take )
131 ········{
132 ············Type t = queryContextsEntry.Type;
133 ············var queryContexts = queryContextsEntry.QueryContexts;
134
135 ············if (queryContexts.Count == 0)
136 ············{
137 ················this.subQueries.Add( new QueryInfo( t, ConstructQueryString( t, new Dictionary<Relation, Class>(), expressionTree, hollow, hasSubclassResultsets, orderings, skip, take ) ) );
138 ············}
139 ············else
140 ············{
141 ················string queryString = string.Empty;
142 ················int added = 0;
143 ················foreach (var queryContext in queryContexts)
144 ················{
145 ····················if (!queryContext.Any( kvp => kvp.Value.SystemType == t ))
146 ····················{
147 ························if (added > 0)
148 ····························queryString += " UNION \r\n";
149 ························queryString += ConstructQueryString( t, queryContext, expressionTree, hollow, hasSubclassResultsets, orderings, skip, take );
150 ························added++;
151 ····················}
152 ················}
153 ················this.subQueries.Add( new QueryInfo( t, queryString ) );
154 ············}
155 ········}
156
157 ········string ConstructQueryString(
158 ············Type resultType,
159 ············Dictionary<Relation, Class> relationContext,
160 ············OqlExpression expressionTree,
161 ············bool hollow,
162 ············bool hasSubclassResultsets,
163 ············List<QueryOrder> orderings,
164 ············int skip,
165 ············int take )
166 ········{
167 ············Class cls = this.mappings.FindClass( resultType );
168
169 ············StringBuilder sb = new StringBuilder( "SELECT " );
170
171 ············var from = new FromGenerator( cls, relationContext ).GenerateFromExpression( expressionTree );
172 ············var qualifyWithTableName = from.IndexOf( "INNER JOIN" ) > -1;
173
174 ············string columnList = this.selectPartCreator( relationContext, hollow, cls, qualifyWithTableName, this.additionalSelectPartData );
175 ············sb.Append( columnList );
176
177 ············// If we need to sort a set of hollow results for different subclasses
178 ············// we need to fetch the ordering columns together with the oid columns
179 ············if (hollow && orderings.Count > 0 && hasSubclassResultsets)
180 ············{
181 ················var orderCols = (from o in orderings select cls.FindField( o.FieldName ).Column.GetQualifiedName()).ToArray();
182 ················columnList += ", " + String.Join( ", ", orderCols );
183 ············}
184
185 ············sb.Append( ' ' );
186 ············sb.Append( from );
187 ············string where = new WhereGenerator( cls, relationContext ).GenerateWhereClause( expressionTree );
188 ············if (where != string.Empty)
189 ············{
190 ················sb.Append( ' ' );
191 ················sb.Append( where );
192 ············}
193
194 ············if (orderings.Count > 0)
195 ············{
196 ················sb.Append( ' ' );
197 ················sb.Append( new OrderGenerator( cls ).GenerateOrderClause( orderings, skip, take ) );
198 ············}
199
200 ············return sb.ToString();
201 ········}
202
203 ········private string CreateQuerySelectPart( Dictionary<Relation, Class> relationContext, bool hollow, Class cls, bool qualifyWithTableName, object additionalData )
204 ········{
205 ············var generator = SqlColumnListGenerator.Get( cls );
206
207 ············// We have to hack around a special behavior of SQLite, generating
208 ············// new columns with fully specified column names, if the query
209 ············// is a UNION············
210 ············var generateAliasNames = relationContext.Count > 0 && cls.Provider.GetType().FullName.IndexOf( "Sqlite" ) > -1;
211
212 ············return generator.Result( hollow, generateAliasNames, qualifyWithTableName );
213 ········}
214
215 ········private string CreateAggregateSelectPart( Dictionary<Relation, Class> relationContext, bool hollow, Class cls, bool qualifyWithTableName, object additionalData )
216 ········{
217 ············var tuple = (Tuple<string, AggregateType>)additionalData;
218 ············string field = tuple.Item1;
219 ············AggregateType aggregateType = tuple.Item2;
220 ············bool isStar = field == "*";
221
222 ············Column column = null;
223 ············if (field.ToLower().StartsWith( "oid" ))
224 ············{
225 ················Regex regex = new Regex( @"\(\s*(\d+)\s*\)" );
226 ················Match match = regex.Match( field );
227 ················int index = 0;
228 ················if (match.Success)
229 ····················index = int.Parse( match.Groups[1].Value );
230 ················column = ((OidColumn)cls.Oid.OidColumns[index]);
231 ············}
232 ············else
233 ············{
234 ················if (!isStar)
235 ····················column = cls.FindField( field ).Column;
236 ············}
237
238 ············var provider = cls.Provider;
239 ············var colName = isStar ? "*" : column.GetQualifiedName();
240
241 ············return $"{aggregateType.ToString().ToUpper()}({colName}) AS {provider.GetQuotedName( "AggrResult" )}";
242 ········}
243
244 ········public string GenerateAggregateQueryString( string field, QueryContextsEntry queryContextsEntry, OqlExpression expressionTree, bool hasSubclassResultsets, AggregateType aggregateType )
245 ········{
246 ············this.selectPartCreator = CreateAggregateSelectPart;
247 ············this.additionalSelectPartData = new Tuple<string,AggregateType>( field, aggregateType );
248
249 ············var query = InnerGenerateQueryString( queryContextsEntry, expressionTree, true, hasSubclassResultsets, new List<QueryOrder>(), 0, 0, null );
250 ············return query;
251 ········}
252
253 ········public string GeneratePrefetchQuery( Type parentType, IEnumerable<IPersistenceCapable> parents, string prefetch )
254 ········{
255 ············Class parentCls = this.mappings.FindClass( parentType );
256
257 ············StringBuilder sb = new StringBuilder( "SELECT " );
258 ············List<Relation> relations = new List<Relation>();
259
260 ············if (prefetch.IndexOf('.') == -1)
261 ············{
262 ················var rel = parentCls.FindRelation( prefetch );
263 ················if (rel != null)
264 ····················relations.Add( rel );
265 ············}
266
267 ············new RelationContextGenerator( this.mappingsAccessor ).CreateContextForName( parentCls, prefetch, relations );
268
269 ············if (relations.Count == 0)
270 ················throw new NDOException( 76, $"Prefetch: Can't find relation mapping with name {prefetch} in class {parentCls.FullName}" );
271
272 ············Class cls = mappings.FindClass( relations[relations.Count - 1].ReferencedTypeName );
273 ············var relationContext = new Dictionary<Relation, Class>();
274 //TODO: Note: This code is not complete. The INNER JOIN is missing here.
275 ············string columnList = CreateQuerySelectPart( relationContext, false, cls, true, null );
276 ············sb.Append( columnList );
277
278 ············sb.Append( ' ' );
279 ············sb.Append( new FromGenerator( cls, relationContext ).GenerateFromExpression( null, relations ) );
280 ············// We can be sure, that we have only one oid column here.
281 ············var oidList = String.Join( ",", from p in parents select p.NDOObjectId.Id.Values[0].ToString() );
282 ············string where = $"WHERE {parentCls.Oid.OidColumns.First().GetQualifiedName()} IN ({oidList})";
283 ············sb.Append( ' ' );
284 ············sb.Append( where );
285
286 ············return sb.ToString();
287 ········}
288 ····}
289 }
290